Skip to main content
The Feed API has two endpoints designed to be used together:
  • Stream active jobsPOST /api/jobs/feed. Paginated stream of full job objects (active postings only).
  • Detect expired jobsGET /api/jobs/expired. Paginated stream of IDs that have expired in the last 7 days.
Use the first to add and update jobs in your store, the second to remove them. The Sync Workflow section below shows the recommended pattern end-to-end.
Both endpoints are included with a Feed subscription. Without a subscription, /feed is metered by delivered jobs; /expired is unmetered so callers can keep downstream inventories clean.

Keeping your system in sync

A typical integration runs an initial backfill, then an incremental sync on a schedule. Use id as your primary key — it’s stable across updates, so syncs are simple upserts.

1. Initial backfill

Page through the entire feed once with cursor pagination. New scans use a stable scan by default (stable_scan: true): pages follow immutable creation time, so jobs updated or expired mid-backfill cannot shift records across page boundaries — no skips, no duplicates. Three rules make the backfill robust:
  • Persist next_cursor after every successful batch so a crash resumes mid-stream rather than restarting from page 1.
  • Paginate serially — one in-flight request per cursor. Cursors are not designed for parallel fan-out.
  • Send filters and batch_size only on the first request. The cursor embeds them; values sent alongside a cursor are ignored.
Stop when has_more is false.

2. Incremental sync

Run on a schedule (15–60 minutes is a healthy range — more frequent runs can add wallet usage without much fresh data). Each run:
  1. Read your stored last_run_started_at. Record now as this_run_started_at before the first request.
  2. Call /feed with updated_after = last_run_started_at - 15m (a small overlap protects against clock skew and late-arriving postings). updated_after also catches edits to jobs you already hold, which posted_after would miss.
  3. Page through with cursor until has_more is false. Upsert each job by id.
  4. After the loop succeeds, persist this_run_started_at as the new last_run_started_at.
updated_at lets you detect re-published edits — store it and skip writes when it hasn’t changed.

3. Handling deletions

/feed only returns active jobs, so jobs that expire silently disappear. Sweep them with /expired on the same schedule as the incremental sync:
Page through with cursor, and mark every returned id as expired in your store.
expired_since is optional — when omitted the endpoint defaults to the last 24 hours, which suits high-frequency sync schedules. /expired enforces a maximum 7-day lookback (expired_since cannot be older than 7 days). If your sync stalls for longer than a week you must run a full re-sync against /feed and reconcile — any IDs in your store that no longer appear are expired.

4. Backoff & rate limits

  • Configure a client response timeout of at least 120 seconds. The feed may spend up to 90 seconds querying the search backend before returning a retryable 503.
  • Retry only 503 and 429, with bounded backoff. On 503, wait the seconds named in Retry-After (currently 5) before retrying — the search backend’s circuit breaker reopens within ~30 s.
  • On 400 Invalid cursor, drop the cursor and restart pagination — don’t loop on the same value.
  • Watch X-Credits-Balance to alert before you run dry.

End-to-end examples

These samples implement the full workflow against a small key-value store. Replace store with your real database (Postgres upsert, etc.).

Managed jobs for your target companies

Your target companies. Fresh jobs. One feed. Use Managed Job Scraping when you need a private feed for a specific set of employers. Add the companies in the Jobo portal, then call POST /api/jobs/feed/managed. Jobo discovers and verifies their career sites, maintains collection, and delivers structured, deduplicated jobs through the same batch shape, cursor semantics, and metering as the main feed. The result is managed job collection that is white-label ready for your own product. Things to know:
  • Each job source resolves to one verified career-site identity. Add a separate source row when the same company recruits through another ATS; jobs from all linked source rows appear in the same private feed.
  • Companies not in the Jobo catalog yet remain in setup and link automatically once Jobo verifies them; no re-import is needed.
  • The locations filter is not supported on the managed endpoint (use sources, work_models, posted_after, updated_after).
  • created_at reflects the exact ingestion time; the main feed reports indexing time for jobs younger than 24 hours.
  • Requires a customer API key — sandbox and marketplace keys receive 403.

Endpoints

Full request/response reference and a live “Try it” playground live on the dedicated pages below.

Stream active jobs

POST /api/jobs/feed — cursor-paginated stream of full job objects (active postings only).

Stream managed jobs

POST /api/jobs/feed/managed — the same stream, scoped to your managed job sources.

Detect expired jobs

GET /api/jobs/expired — paginated stream of IDs that have expired in the last 7 days.

JobDto schema

Both feed endpoints (and the search endpoints) return job objects in this shape:

Company object

object

Location object

object
An entry in the locations[] array. A single posting may cover multiple cities/countries.

Compensation object

object

Qualifications object

object
Structured qualifications split into must-have and preferred buckets.Each bucket contains: