Page-based pagination for search
GET /api/jobs · POST /api/jobs/search · GET /api/companies/{id}/jobs
The response body carries
total, page, page_size, and total_pages, mirrored in the X-Total-Count, X-Page, X-Page-Size, and X-Total-Pages response headers.
Search stops at a depth of 100,000 results (
page × page_size). A deeper page is silently clamped to the last reachable page (page 1,000 at page_size 100): page and X-Page report the clamped page, but total_pages is not capped. Cap your page controls at that depth, and use the feed for anything deeper.Stable pages over a fixed window
The default order is relevance, or most recently updated first when there is no text query. That order moves while you page: an updated job jumps to the front and shifts the rest by one, so a page-by-page pull can skip or repeat a job. To pull a window page by page without shifts:- Set
sorttodiscovered_asc. - Bound the window with
discovered_afteranddiscovered_before. Both are inclusive and compare againstcreated_at, the time Jobo first indexed the job, which never changes. discovered_descis also stable, but only withdiscovered_beforeset. Without it, newly indexed jobs push the later pages back.
id and compare the unique count with total.
Cursor-based pagination for feeds
POST /api/jobs/feed · POST /api/jobs/feed/managed · POST /api/jobs/feed/{feed_id} · GET /api/jobs/expired
The response body carries
next_cursor and has_more. Loop until has_more is false. The X-Has-More header mirrors it as True or False, so compare the header case-insensitively or read the body.
GET /api/auto-apply/applications also pages by cursor, with limit (default 20) instead of batch_size.
Cursor rules
1
Send filters and batch_size only on the first request
The cursor embeds the first request’s filters, sort order, and
batch_size. Filters and sort order sent with a cursor are ignored, so start a new scan to change them. A continuation can lower batch_size but never raise it. description_format is read on every request, continuations included.2
Paginate serially
Keep one request in flight per cursor. Several continuations fired from the same cursor at once produce undefined results.
3
Persist next_cursor after every successful batch
A crashed run then resumes mid-stream instead of from the beginning. Feed cursors do not expire, so you can resume at any time. See Sync a database for a full checkpointing loop.
4
Leave stable_scan at its default of true
Pages follow immutable creation time, so jobs updated or expired mid-run cause no skips and no duplicates.
stable_scan: false is the legacy update-recency order, and its cursors stop at 1,000,000 results.Cursor failure modes
GET /api/jobs/expired never returns invalid_cursor. It ignores a cursor it cannot decode and returns the first page of the window again, so persist next_cursor exactly as returned. Errors has the full envelopes and retry guidance.
Example
First request, then a continuation whose body carries onlycursor.
Python SDK
client.search.iter_jobs(...), client.feed.iter_jobs(...), client.feed.iter_managed_jobs(...), and client.feed.iter_expired_job_ids(...) page for you, by page number or cursor, and yield one item at a time. See Client libraries.
On 3.x and earlier,
iter_jobs on client.feed takes only locations, sources, work_models, posted_after, and batch_size, so incremental sync through the client needs 4.0.0.
For any filter the clients don’t send, call the endpoint over plain HTTPS as in the example.
