--- url: https://docs.tenderapi.eu/pagination.md description: >- How cursor pagination works on TenderAPI search endpoints, and how to walk through every page of results. --- # 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-tenders), [Search Organisations](/search-organisations) and [Organisation Tenders](/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 } } } ``` | Field | Type | Description | |---|---|---| | `page.nextCursor` | `string \| null` | Pass as `cursor` to get the next page. `null` on the last page. | | `page.total` | `object \| null` | Number of matching results. Only present on the first page, `null` when you send a cursor. | | `page.total.count` | `integer` | Number of matches, counted up to 10 000. | | `page.total.capped` | `boolean` | `true` 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 ::: code-group ```bash [cURL] # 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 [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 [TypeScript] const url = "https://api.tenderapi.eu/api/v1/tenders/search"; const body: Record = { 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. | `sortBy` | Default `sortOrder` | Sorts by | |---|---|---| | `publicationDate` | `desc` | Date the notice was published. This is the default. | | `deadline` | `asc` | Submission deadline. | | `estimatedValue` | `desc` | Estimated 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 } ```