Skip to content

Pagination ​

Search endpoints return results one page at a time. Instead of page numbers, they use a cursor: an opaque string that marks where the last page ended. This applies to Search Tenders, Search Organisations and Organisation Tenders.

How it works ​

  1. Send your request with an optional limit. Default is 25, maximum is 100.
  2. The response contains a page object. If there are more results, nextCursor holds a string.
  3. Send the exact same request again with cursor set to that string.
  4. Repeat until nextCursor is null.
json
{
  "tenders": [ ... ],
  "page": {
    "nextCursor": "eyJkIjoiMjAyNS0wMy0xNSIsImlkIjoiLi4uIn0",
    "total": { "count": 4821, "capped": false }
  }
}
FieldTypeDescription
page.nextCursorstring | nullPass as cursor to get the next page. null on the last page.
page.totalobject | nullNumber of matching results. Only present on the first page, null when you send a cursor.
page.total.countintegerNumber of matches, counted up to 10 000.
page.total.cappedbooleantrue when there are more than 10 000 matches and count stopped at 10 000.

Cursors are tied to the request ​

A cursor only works with the same filters, sort field and sort order it was created with. If you change anything, drop the cursor and start from the first page. Otherwise you get a 400 error with the message invalid cursor.

Example: fetch all pages ​

bash
# First page
curl -X POST https://api.tenderapi.eu/api/v1/tenders/search \
  -H "Content-Type: application/json" \
  -d '{"buyerCountries": ["SVN"], "limit": 100}'

# Next page, using nextCursor from the previous response
curl -X POST https://api.tenderapi.eu/api/v1/tenders/search \
  -H "Content-Type: application/json" \
  -d '{"buyerCountries": ["SVN"], "limit": 100, "cursor": "PASTE_NEXT_CURSOR_HERE"}'
python
import requests

url = "https://api.tenderapi.eu/api/v1/tenders/search"
body = {"buyerCountries": ["SVN"], "limit": 100}
tenders = []

while True:
    data = requests.post(url, json=body).json()
    tenders.extend(data["tenders"])
    cursor = data["page"]["nextCursor"]
    if not cursor:
        break
    body["cursor"] = cursor

print(len(tenders))
ts
const url = "https://api.tenderapi.eu/api/v1/tenders/search";
const body: Record<string, unknown> = { buyerCountries: ["SVN"], limit: 100 };
const tenders = [];

while (true) {
  const res = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  const data = await res.json();
  tenders.push(...data.tenders);
  if (!data.page.nextCursor) break;
  body.cursor = data.page.nextCursor;
}

console.log(tenders.length);

Sorting ​

Tender endpoints accept sortBy and sortOrder. Sorting changes the order results come in, and therefore the cursor.

sortByDefault sortOrderSorts by
publicationDatedescDate the notice was published. This is the default.
deadlineascSubmission deadline.
estimatedValuedescEstimated contract value, compared in EUR.

sortOrder can be asc or desc. When you sort by deadline or estimatedValue, tenders that have no deadline or no estimated value are left out of the results.

json
{
  "statuses": ["open"],
  "sortBy": "deadline",
  "sortOrder": "asc",
  "limit": 50
}

Beta. Free to use, no API key required.