API Documentation

Integrate Tender Impulse global procurement data into your own applications. This guide covers the Tender and Contract Award 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 20,000 global tenders daily, helping organisations discover procurement opportunities from government agencies, public-sector organisations and international institutions worldwide. Two 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.

Both APIs share the same authentication, id-based paging model and AES-encrypted response format, so once you have integrated one, the other is nearly identical.

Note: API access is a paid service. The endpoints shown are the current v2 (UAT) endpoints used by all official reference clients.

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=6771840"

Decrypted payload

{
  "status": "success",
  "tenders": [
    {
      "tender_id": 13098184,
      "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": 13098184
}

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.

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": 13098184 }

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

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}`);
}

Full runnable examples and both 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']}")

Full runnable examples and both 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;
}

Full runnable examples and both 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"));
}

Full runnable examples and both 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) so the next run resumes where the last one stopped:

{ "fetchid": 6771840 }

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 live tenders and contract awards.

Request API Access
Chat with us