API Documentation

Integrate Tender Impulse global procurement data into your own applications. This guide covers the Tender, Contract Award and Tender News REST APIs - authentication, id-based paging, encrypted responses, data fields and ready-to-run examples in Node.js, Python, PHP and Java.

Overview

Tender Impulse provides access to over 40,000 global tenders daily, helping organisations discover procurement opportunities from government agencies, public-sector organisations and international institutions worldwide. Three REST APIs are available:

  • Tender API - live global tender notices, including full authority details, CPV codes, sectors, deadlines and downloadable documents.
  • Contract Award API - awarded-contract records, including the winning supplier, contract value, notice number and downloadable documents.
  • Tender News API - procurement and industry news articles, including the full article body, image, source URL and the sectors, regions, countries and CPV codes each article is classified against.

All three APIs share the same authentication, id-based paging model and AES-encrypted response format, so once you have integrated one, the others are nearly identical.

Note: API access is a paid service. Every endpoint shown in this documentation is a UAT (test) endpoint - use these to build and validate your integration. The production endpoint URLs are issued separately, together with your production credentials, once commercial terms have been finalised. Apart from the URL, production behaves identically to UAT, so no code changes are needed when you switch over.

Getting Access

To obtain your API credentials - an Access Token and an Encryption Key - submit a request through our callback form. Once approved, you will receive the credentials required to authenticate and decrypt API responses, along with the starting lastid for your first call.

Request API access →

Authentication

Every request is authenticated with your Access Token sent as a Bearer token in the Authorization header:

Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

Requests are made over HTTPS with GET. Responses are AES-128-CBC encrypted and must be decrypted with your Encryption Key (see Response & Encryption).

How Paging Works

Both APIs return records in batches, and you page through them using an id rather than a date. Each call takes a lastid and returns the records that come after it, along with a fetchid - the id of the last record in that batch. The fetchid is what you pass as the lastid of your next call.

  1. Call the API with the lastid you have. On the very first run, use the starting id supplied by Tender Impulse.
  2. Read the records and the fetchid from the decrypted response, and store the fetchid.
  3. If the batch contained records, wait a short while and call again with lastid set to the stored fetchid.
  4. Repeat until a call returns an empty batch - you are now up to date.
  5. Start again the next day to pick up newly published records.
Important: Store the fetchid only after you have finished handling the batch. If you store it first and then fail, those records are skipped permanently - there is no way to ask for them again.
  • Each call returns a limited number of records, so a full catch-up normally takes several calls.
  • When a batch is empty, fetchid comes back equal to the lastid you sent, so storing it is always safe.
  • Sending a lastid higher than the server's own last fetch id is rejected with a message telling you the maximum allowed value.

Tender API

Returns global tender notices published after the supplied lastid.

GEThttps://tenderimpulse.com/web-api/tender/v2/uat.php?lastid={lastid}

Example request

curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
     -H "Content-Type: application/json" \
     "https://tenderimpulse.com/web-api/tender/v2/uat.php?lastid=8156393"

Decrypted payload

{
  "status": "success",
  "tenders": [
    {
      "tender_id": 8156394,
      "title": "Request for Information - IT Managed Services",
      "authority_name": "...",
      "country": "Australia",
      "cpv_codes": "...",
      "deadline": "2026-06-22",
      "value_of_contract": "...",
      "filepath": "https://tenderimpulse.com/.../document.pdf",
      "filename": "document.pdf"
    }
  ],
  "fetchid": 8156394
}

Tender fields

FieldDescription
tender_idUnique Tender Impulse identifier for the tender.
titleTender title / notice heading.
authority_nameName of the contracting authority (buyer).
addressPostal address of the authority.
telContact telephone number.
faxContact fax number.
emailContact email address.
webAuthority or source website.
contact_nameNamed contact person for the tender.
contract_typeType of contract (supply, works, services, etc.).
sectorsClassified sector(s) for the tender.
cpv_codesCPV classification codes.
countryCountry the tender belongs to.
original_sourceOriginal publishing source / procurement portal.
locationPlace of performance / delivery location.
referenceTender reference number from the source.
contract_durationDuration of the contract.
value_of_contractEstimated value of the contract.
deadlineSubmission deadline date.
other_informationAny additional notes or miscellaneous details.
filenameLocal path/filename of the downloaded tender document.
filepathLocal path to the stored tender document.

Each tender includes a filepath (document URL) and filename; the reference client downloads the file to your local store path and rewrites both fields to the saved location.

Contract Award API

Returns awarded-contract records published after the supplied lastid. Same authentication and paging as the Tender API.

GEThttps://tenderimpulse.com/web-api/contract-awards/v2/uat.php?lastid={lastid}

Example request

curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
     -H "Content-Type: application/json" \
     "https://tenderimpulse.com/web-api/contract-awards/v2/uat.php?lastid=261374"

Decrypted payload

{
  "status": "success",
  "contracts": [
    {
      "ca_id": 261375,
      "organisation": "...",
      "contracting_authority": "...",
      "contract_notice_no": "...",
      "contract_awarded_to": "...",
      "value_of_contract": "...",
      "country": "...",
      "publish_date": "2026-06-01",
      "filepath": "https://tenderimpulse.com/.../award.pdf",
      "filename": "award.pdf"
    }
  ],
  "fetchid": 261375
}

Contract award fields

FieldDescription
ca_idUnique Tender Impulse identifier for the contract award.
organisationOrganisation associated with the award.
contracting_authorityAuthority that awarded the contract.
contract_notice_noContract / notice reference number.
contract_awarded_toSupplier the contract was awarded to.
descriptionDescription of the awarded contract.
value_of_contractAwarded contract value.
sectorsClassified sector(s).
cpv_codesCPV classification codes.
countryCountry the award belongs to.
publish_dateDate the award was published.
filenameLocal path/filename of the document (null when none).
filepathLocal path to the stored document (null when none).
Note: A contract award may have no attached document - in that case filename and filepath are null and no file is downloaded.

Tender News API

Returns procurement and industry news articles published after the supplied lastid. Same authentication, paging and encryption as the Tender and Contract Award APIs.

GEThttps://tenderimpulse.com/web-api/news/v2/uat.php?lastid={lastid}

Example request

curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
     -H "Content-Type: application/json" \
     "https://tenderimpulse.com/web-api/news/v2/uat.php?lastid=18000"

Decrypted payload

{
  "status": "success",
  "news": [
    {
      "blogid": 18016,
      "blogtitle": "Navy Leader Wants to Move Faster and Leaner ...",
      "shortdescription": "...",
      "longdescription": "<p>...</p>",
      "seourl": "navy-leader-wants-move-faster-and-leaner ...",
      "thumbnail_image": "https://tenderimpulse.com/news/news-images/ApNewsroom_Navy_Leader.jpg",
      "publishstatus": "Active",
      "publishdate": "2026-02-10",
      "metatitle": "...",
      "source": "https://www.military.com/daily-news/2026/02/10/...",
      "metakeywords": "...",
      "blogstatus": "1",
      "sectors": "Defence Tenders",
      "createddate": "2026-02-11",
      "cpvs": "35410000 : Armoured military vehicles,35610000 : Military aircrafts",
      "ceratedtime": "11:37:36",
      "updatedate": "2026-02-12",
      "updatedtime": "17:23:20",
      "countries": "USA",
      "regions": "Americas"
    }
  ],
  "fetchid": 18016
}

Tender News fields

FieldDescription
blogidUnique Tender Impulse identifier for the Tender News article.
blogtitleArticle headline.
shortdescriptionShort summary / excerpt of the article.
longdescriptionFull article body (HTML).
seourlSEO slug for the article.
thumbnail_imageAbsolute URL of the article image, or an empty string when none.
publishstatusPublication status of the article, e.g. Active.
publishdateDate the article was published.
metatitleMeta title used for SEO.
sourceOriginal source URL the article was taken from.
metakeywordsMeta keywords used for SEO.
blogstatusRecord status flag.
sectorsClassified sector(s), comma-separated.
createddateDate the article record was created.
cpvsCPV classification codes, comma-separated.
ceratedtimeTime the article record was created. Field name is spelt this way in the payload.
updatedateDate the article record was last updated.
updatedtimeTime the article record was last updated.
countriesCountries the article relates to, comma-separated.
regionsRegions the article relates to, comma-separated.
Note: sectors, regions, countries and cpvs are returned as a single comma-separated string - for example "Defence Tenders,Healthcare" - the same convention the Tender and Contract Award APIs use for sectors and cpv_codes. Split on , to get the individual values. A field the article is not classified against is null.
Note: The Tender News API returns no downloadable documents, so there are no filename / filepath fields. thumbnail_image is an absolute image URL, or an empty string when the article has no image.

Response & Encryption

Every endpoint returns a small JSON envelope: an encrypted data payload plus a crc checksum used to verify integrity.

{
  "data": "<base64-ciphertext>:<base64-iv>",
  "crc":  "<md5-hex-of-decrypted-json>"
}

To read a response, decrypt data and validate it against crc:

  1. Split data on the : character into [ciphertext, iv].
  2. Base64-decode both parts.
  3. Take your Encryption Key as UTF-8 bytes; pad with 0 or truncate to exactly 16 bytes.
  4. Decrypt with AES-128-CBC using that key and iv to get the plaintext JSON.
  5. Compute MD5(plaintext) and confirm it equals crc (case-insensitive). A mismatch means a transmission error - discard the batch.
  6. JSON.parse the plaintext to get status, the records array and fetchid.

After a successful decrypt, every official client (Node.js, Python, PHP and Java) returns the same normalised shape:

// Tender client
{ "status": "success", "tenders":   [ ... ], "last_fetch_id": 8156394 }

// Contract award client
{ "status": "success", "contracts": [ ... ], "last_fetch_id": 261375 }

// Tender News client
{ "status": "success", "tender_news": [ ... ], "last_fetch_id": 18016 }

In the Java client the Tender News array is exposed as news rather than tender_news; the Node.js, Python and PHP clients all use tender_news.

All responses are encrypted using AES-128-CBC, verified using MD5 checksum validation, and authenticated using Bearer access tokens.

Error Handling

When something goes wrong, the decrypted payload carries status: "error" and a message:

{ "status": "error", "msg": "Error description" }

The reference clients catch every failure - network, decryption, checksum or API error - and return a uniform shape so your code only checks one thing:

{ "status": "error", "msg": "Error description" }

Common causes include an invalid or expired Access Token, a wrong Encryption Key (decryption fails), a checksum mismatch (Message transmission error), or a lastid higher than the server's current maximum.

Quick Start

Official reference implementations are available for Node.js, Python, PHP and Java. Each one handles authentication, decryption, checksum validation, document downloads and fetchid tracking for you - pick your language below.

Requires Node.js 16+. Install:

npm install axios

Tenders:

const TenderImpulseClient = require("./tenderImpulseClient");

const client = new TenderImpulseClient(
    "tender-documents",   // local folder for downloaded documents
    accessToken,          // provided by Tender Impulse
    key                   // AES encryption key provided by Tender Impulse
);

const result = await client.getTenders(lastId);

if (result.status === "success") {
    console.log(`Tenders fetched: ${result.tenders.length}`);
    console.log(`Last fetch id: ${result.last_fetch_id}`);
    // store result.last_fetch_id AFTER handling the batch
} else {
    console.error(result.msg);
}

Contract awards use the same pattern, returning the records under contracts:

const TenderImpulseContractAwardClient = require("./tenderImpulseContractAwardClient");

const client = new TenderImpulseContractAwardClient(
    "contract-award-documents", accessToken, key
);

const result = await client.getContractAwards(lastId);

if (result.status === "success") {
    console.log(`Contract awards fetched: ${result.contracts.length}`);
    console.log(`Last fetch id: ${result.last_fetch_id}`);
}

Tender News works the same way. Note the client takes only your token and key - there is no documents folder, because the Tender News API returns no downloadable files:

const TenderImpulseTenderNewsClient = require("./tenderImpulseTenderNewsClient");

const client = new TenderImpulseTenderNewsClient(
    accessToken,          // provided by Tender Impulse
    key                   // AES encryption key provided by Tender Impulse
);

const result = await client.getTenderNews(lastId);

if (result.status === "success") {
    console.log(`Tender News fetched: ${result.tender_news.length}`);
    console.log(`Last fetch id: ${result.last_fetch_id}`);
} else {
    console.error(result.msg);
}

Full runnable examples and clients: https://github.com/tenderimpulse/TenderImpulseAPIsNode

Requires Python 3.8+. Install:

pip install requests pycryptodome

Tenders:

from tender_impulse_client import TenderImpulseClient

client = TenderImpulseClient(
    "tender-documents",   # local folder for downloaded documents
    access_token,         # provided by Tender Impulse
    key                   # AES decryption key provided by Tender Impulse
)

result = client.get_tenders(last_id)

if result["status"] == "success":
    print(f"Tenders fetched: {len(result['tenders'])}")
    print(f"Last fetch id: {result['last_fetch_id']}")
    # store result["last_fetch_id"] AFTER handling the batch
else:
    print(f"Error: {result['msg']}")

Contract awards use the same pattern, returning the records under contracts:

from tender_impulse_contract_award_client import TenderImpulseContractAwardClient

client = TenderImpulseContractAwardClient(
    "contract-award-documents", access_token, key
)

result = client.get_contract_awards(last_id)

if result["status"] == "success":
    print(f"Contract awards fetched: {len(result['contracts'])}")
    print(f"Last fetch id: {result['last_fetch_id']}")

Tender News works the same way. Note the client takes only your token and key - there is no documents folder, because the Tender News API returns no downloadable files:

from tender_impulse_tender_news_client import TenderImpulseTenderNewsClient

client = TenderImpulseTenderNewsClient(
    access_token,         # provided by Tender Impulse
    key                   # AES decryption key provided by Tender Impulse
)

result = client.get_tender_news(last_id)

if result["status"] == "success":
    print(f"Tender News fetched: {len(result['tender_news'])}")
    print(f"Last fetch id: {result['last_fetch_id']}")
else:
    print(f"Error: {result['msg']}")

Full runnable examples and clients: https://github.com/tenderimpulse/TenderImpulseAPIsPython

Requires PHP 7.4+ (cURL, OpenSSL, JSON). Install:

; No third-party packages required - enable these in php.ini
extension=curl
extension=openssl

Tenders:

require_once 'TenderImpulseClient.php';

$client = new TenderImpulseClient(
    __DIR__ . '/tender-documents',   // local folder for downloaded documents
    $accessToken,                    // provided by Tender Impulse
    $key                             // AES decryption key provided by Tender Impulse
);

$result = $client->getTenders($lastId);

if ($result['status'] === 'success') {
    echo "Tenders fetched: " . count($result['tenders']) . PHP_EOL;
    echo "Last fetch id: " . $result['last_fetch_id'] . PHP_EOL;
    // store $result['last_fetch_id'] AFTER handling the batch
} else {
    echo "Error: " . $result['msg'] . PHP_EOL;
}

Contract awards use the same pattern, returning the records under contracts:

require_once 'TenderImpulseContractAwardClient.php';

$client = new TenderImpulseContractAwardClient(
    __DIR__ . '/contract-award-documents', $accessToken, $key
);

$result = $client->getContractAwards($lastId);

if ($result['status'] === 'success') {
    echo "Contract awards fetched: " . count($result['contracts']) . PHP_EOL;
    echo "Last fetch id: " . $result['last_fetch_id'] . PHP_EOL;
}

Tender News works the same way. Note the client takes only your token and key - there is no documents folder, because the Tender News API returns no downloadable files:

require_once 'TenderImpulseTenderNewsClient.php';

$client = new TenderImpulseTenderNewsClient(
    $accessToken,   // provided by Tender Impulse
    $key            // AES decryption key provided by Tender Impulse
);

$result = $client->getTenderNews($lastId);

if ($result['status'] === 'success') {
    echo "Tender News fetched: " . count($result['tender_news']) . PHP_EOL;
    echo "Last fetch id: " . $result['last_fetch_id'] . PHP_EOL;
} else {
    echo "Error: " . $result['msg'] . PHP_EOL;
}

Full runnable examples and clients: https://github.com/tenderimpulse/TenderImpulseAPIsPHP

Requires Java 17+ / Maven 3.6+. Install:

mvn clean compile

Tenders:

import client.TenderImpulseClient;
import org.json.JSONArray;
import org.json.JSONObject;

TenderImpulseClient client = new TenderImpulseClient(
    "tender-documents",   // local folder for downloaded documents
    ACCESS_TOKEN,         // provided by Tender Impulse
    KEY                   // AES decryption key provided by Tender Impulse
);

JSONObject result = client.getTenders(lastId);

if ("success".equals(result.getString("status"))) {
    JSONArray tenders = result.getJSONArray("tenders");
    System.out.println("Tenders fetched: " + tenders.length());
    System.out.println("Last fetch id: " + result.getLong("last_fetch_id"));
    // store last_fetch_id AFTER handling the batch
} else {
    System.out.println("Error: " + result.getString("msg"));
}

Contract awards use the same pattern, returning the records under contracts:

import client.TenderImpulseContractAwardClient;
import org.json.JSONArray;
import org.json.JSONObject;

TenderImpulseContractAwardClient client =
    new TenderImpulseContractAwardClient("contract-award-documents", ACCESS_TOKEN, KEY);

JSONObject result = client.getContractAwards(lastId);

if ("success".equals(result.getString("status"))) {
    JSONArray contracts = result.getJSONArray("contracts");
    System.out.println("Contract awards fetched: " + contracts.length());
    System.out.println("Last fetch id: " + result.getLong("last_fetch_id"));
}

Tender News works the same way. Note the client takes only your token and key - there is no documents folder, because the Tender News API returns no downloadable files:

import client.TenderImpulseTenderNewsClient;
import org.json.JSONArray;
import org.json.JSONObject;

TenderImpulseTenderNewsClient client =
    new TenderImpulseTenderNewsClient(ACCESS_TOKEN, KEY);

JSONObject result = client.getTenderNews(lastId);

if ("success".equals(result.getString("status"))) {
    // NOTE: the Java client exposes this array as "news"
    JSONArray tenderNews = result.getJSONArray("news");
    System.out.println("Tender News fetched: " + tenderNews.length());
    System.out.println("Last fetch id: " + result.getLong("last_fetch_id"));
} else {
    System.out.println("Error: " + result.getString("msg"));
}

Full runnable examples and clients: https://github.com/tenderimpulse/TenderImpulseAPIsJava

Whichever language you use, each example stores the returned fetchid in a small JSON state file (tender-state.json / contract-award-state.json / tender-news-state.json) so the next run resumes where the last one stopped:

{ "fetchid": 8156393 }

When the state file is missing, the example falls back to an initial-last-id constant - set that to the starting id supplied by Tender Impulse. In a real integration, store the fetchid in your database, updated in the same transaction that saves the batch.

Resources

Reference clients and runnable examples, one repository per language:

Ready to integrate?

Request your Access Token and Encryption Key to start pulling tenders, contract awards and Tender News.

Request API Access
Chat with us