Developers

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.