Docs

Working with the API

Pagination

How each list on the platform host is paged — the four ways in use, as they are — and which lists are not paged at all.

A list answers a page at a time and says where the next page starts. The platform's lists do not all say it the same way: four ways are in use, each kept as it was first published, because an integration that reads one would break if it changed under it (Versioning). This page says which list speaks which.

Every way has the same two rules. Send the value back unchanged — it is the list's own bookmark, not a value to build. Keep the same filters from one page to the next; a cursor is a place in one filtered order.

#The four ways

SendRead backListsThe last page
cursornext_cursorGET /v1/models, every Community and Cinema Studio list, GET /v1/jobs and GET /v1/chat/conversationsnext_cursor is null — or, on GET /v1/jobs, absent
beforenext_beforeGET /v1/jobs and GET /v1/chat/conversations, the older waynext_before is absent on GET /v1/jobs, null beside has_more: false on threads
afternext_afterGET /v1/chat/conversations/{id}/messagesnext_after is null beside has_more: false
nothing—GET /v1/changelog, GET /v1/webhooks, GET /v1/community/me/collectionsEvery row is in the one answer

Each paged list takes limit, the page's size, up to a ceiling of its own; its page in the API reference says both.

#cursor and next_cursor

The value is opaque: a place in the list's order, not an id to read anything by. On the Community's and Cinema Studio's lists it is the database's own, and one that is not a place in that list is refused 400 invalid_cursor.

GET /v1/models is not paged unless you ask: with no limit it answers every model, and next_cursor is null. Ask for limit and page by cursor; expand=openapi asks for a page by itself. The cursor there is a sort position, so a model withdrawn between two pages does not break the walk, and one added before the cursor between two pages is not seen.

On GET /v1/jobs and GET /v1/chat/conversations the cursor names the last row a page read, to the microsecond, and the row's own id. The next page starts right after that row, so two jobs or two threads made in the same microsecond on either side of a page's edge are both listed. A cursor that is not one a page answered — or one sent beside before — is 400 invalid_cursor.

#before and next_before

The older way on the same two lists, still answered exactly as it was: next_before is the created_at of the last row the page read, and the next page starts strictly older than it. A job or a thread made in the same microsecond as that row, and left past the page's edge, is not listed on either page — which is why cursor was added beside it. Prefer cursor. GET /v1/jobs prints next_before only when there is another page and has no has_more; threads print has_more and a next_before that is null on the last page.

A typed client that reads next_before as a date can also lose its microseconds on the way back, and with them any row of the same millisecond. next_cursor is a string, and nothing parses it.

#after and next_after

A thread's messages are in the order they were written, oldest first, and next_after is the seq of the last message on the page — a whole number. Send it back as after. A rewound message is not the thread's any more and is never listed.

#Lists with no pages

GET /v1/changelog answers the newest entries we published, up to a fixed number, and takes no cursor. GET /v1/webhooks answers every endpoint the account registered, and GET /v1/community/me/collections every collection the account keeps, private ones included — its next_cursor is always null.

Start typing to search every page of the documentation.

Up arrow Down arrow to moveEnter to openEscape to close