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.
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.
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.
- Call the API with the
lastidyou have. On the very first run, use the starting id supplied by Tender Impulse. - Read the records and the
fetchidfrom the decrypted response, and store thefetchid. - If the batch contained records, wait a short while and call again with
lastidset to the storedfetchid. - Repeat until a call returns an empty batch - you are now up to date.
- Start again the next day to pick up newly published records.
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,
fetchidcomes back equal to thelastidyou sent, so storing it is always safe. - Sending a
lastidhigher 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.
https://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
| Field | Description |
|---|---|
| tender_id | Unique Tender Impulse identifier for the tender. |
| title | Tender title / notice heading. |
| authority_name | Name of the contracting authority (buyer). |
| address | Postal address of the authority. |
| tel | Contact telephone number. |
| fax | Contact fax number. |
| Contact email address. | |
| web | Authority or source website. |
| contact_name | Named contact person for the tender. |
| contract_type | Type of contract (supply, works, services, etc.). |
| sectors | Classified sector(s) for the tender. |
| cpv_codes | CPV classification codes. |
| country | Country the tender belongs to. |
| original_source | Original publishing source / procurement portal. |
| location | Place of performance / delivery location. |
| reference | Tender reference number from the source. |
| contract_duration | Duration of the contract. |
| value_of_contract | Estimated value of the contract. |
| deadline | Submission deadline date. |
| other_information | Any additional notes or miscellaneous details. |
| filename | Local path/filename of the downloaded tender document. |
| filepath | Local 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.
https://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
| Field | Description |
|---|---|
| ca_id | Unique Tender Impulse identifier for the contract award. |
| organisation | Organisation associated with the award. |
| contracting_authority | Authority that awarded the contract. |
| contract_notice_no | Contract / notice reference number. |
| contract_awarded_to | Supplier the contract was awarded to. |
| description | Description of the awarded contract. |
| value_of_contract | Awarded contract value. |
| sectors | Classified sector(s). |
| cpv_codes | CPV classification codes. |
| country | Country the award belongs to. |
| publish_date | Date the award was published. |
| filename | Local path/filename of the document (null when none). |
| filepath | Local path to the stored document (null when none). |
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.
https://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
| Field | Description |
|---|---|
| blogid | Unique Tender Impulse identifier for the Tender News article. |
| blogtitle | Article headline. |
| shortdescription | Short summary / excerpt of the article. |
| longdescription | Full article body (HTML). |
| seourl | SEO slug for the article. |
| thumbnail_image | Absolute URL of the article image, or an empty string when none. |
| publishstatus | Publication status of the article, e.g. Active. |
| publishdate | Date the article was published. |
| metatitle | Meta title used for SEO. |
| source | Original source URL the article was taken from. |
| metakeywords | Meta keywords used for SEO. |
| blogstatus | Record status flag. |
| sectors | Classified sector(s), comma-separated. |
| createddate | Date the article record was created. |
| cpvs | CPV classification codes, comma-separated. |
| ceratedtime | Time the article record was created. Field name is spelt this way in the payload. |
| updatedate | Date the article record was last updated. |
| updatedtime | Time the article record was last updated. |
| countries | Countries the article relates to, comma-separated. |
| regions | Regions the article relates to, comma-separated. |
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.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:
- Split
dataon the:character into[ciphertext, iv]. - Base64-decode both parts.
- Take your Encryption Key as UTF-8 bytes; pad with
0or truncate to exactly 16 bytes. - Decrypt with AES-128-CBC using that key and iv to get the plaintext JSON.
- Compute
MD5(plaintext)and confirm it equalscrc(case-insensitive). A mismatch means a transmission error - discard the batch. JSON.parsethe plaintext to getstatus, the records array andfetchid.
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:
- About our data API: Global Tender API Feed
- Request credentials: Request API access
Ready to integrate?
Request your Access Token and Encryption Key to start pulling tenders, contract awards and Tender News.