Pagination
Page size, cursors, the sort order, polling for changes, and how timestamps, nulls and enums look.
Overview
A list returns its items a page at a time. You choose the page size with limit and follow next_cursor to the next page. Every list in version 1 works this way.
Page size
A list returns 25 items unless you set limit, which takes 1 to 100. Any other value gets invalid-request.
curl "https://attia.app/api/v1/jobs?limit=100" \ -H "Authorization: Bearer $ATTIA_API_KEY"The next page
When more items exist, next_cursor holds a string. Send it back as cursor to get the next page, and stop when next_cursor is null.
curl "https://attia.app/api/v1/jobs?limit=2&cursor=eyJ1IjoiMjAyNi0xMC0wMlQwODoxNTozMC40ODI5MTNaIiwiaSI6IjAxOTJmM2ExLTdjNDQtN2IxZS05YTUwLTNmMWQyYzZlOGEwMiJ9" \ -H "Authorization: Bearer $ATTIA_API_KEY"On the last page the answer looks like this.
{ "data": [ { "id": "0192f3a1-8d15-7f2a-b6c3-4e2a1d9f0b03", "title": "Warehouse worker", "slug": "warehouse-worker", "status": "live", "publish_status": "published", "team_id": "0192f39f-11aa-7c3e-b2d4-9e8f7a6b5c4d", "employment_type": "full_time", "contract_type": "seasonal", "workplace_type": "onsite", "locations": ["Eksempelvik"], "positions_count": 3, "application_deadline": "2026-10-31", "published_at": "2026-10-03T06:00:00.000000Z", "closed_at": null, "archived_at": null, "created_at": "2026-10-02T14:20:11.903214Z", "updated_at": "2026-10-03T06:00:00.271845Z" } ], "next_cursor": null}Treat a cursor as an opaque string. Do not build or change one. A cursor the API did not return gets invalid-request with the message "Not a cursor this API returned."
Sort order and concurrent edits
Every list sorts by updated_at, oldest first, then by id. That order keeps a cursor stable while people edit.
An item edited during your read moves behind the cursor, and you may see it again on a later page. Expect the occasional item twice and key on id. One pass can still miss a change saved while you read, so poll as the next section describes.
Poll for changes
updated_since returns only items updated at or after a time. The bound is inclusive, so an item updated at exactly that time comes back.
To poll for changes, remember the largest updated_at you have seen, and on the next run send a time five minutes before it as updated_since. A change gets its updated_at when the save starts, so a save that finishes after your poll can carry a time older than one you already read. The five-minute overlap is there to pick those up. It also returns items you already have, so drop the repeats by id. With 2026-10-01T09:42:10.118305Z as the largest, the call is this.
curl "https://attia.app/api/v1/jobs?updated_since=2026-10-01T09:37:10.118305Z" \ -H "Authorization: Bearer $ATTIA_API_KEY"You can combine updated_since with cursor and limit when one poll returns more than a page.
Timestamps, nulls and enums
A timestamp is a string in UTC, in RFC 3339 form with microseconds and a Z, such as 2026-10-02T08:15:30.482913Z. A field that holds a calendar day with no time, such as application_deadline, is YYYY-MM-DD.
A timestamp you send, such as updated_since, needs a Z or an offset. Write a + in an offset as %2B, because a bare + in a URL means a space.
curl "https://attia.app/api/v1/jobs?updated_since=2026-10-01T02:00:00%2B02:00" \ -H "Authorization: Bearer $ATTIA_API_KEY"A field with no value is present and null. The API never leaves a field out, so check for null and not for a missing key. An empty list is [].
An enum is a lower-case string such as live or full_time. Version 1 can add a value to an enum, so handle a value you do not know without failing.