> ## Documentation Index
> Fetch the complete documentation index at: https://jobo.world/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Core concepts

> The job object, search vs. feed, freshness, expiry, and metering — the model to hold before you design an integration.

You've made a call and gotten jobs back. Before you design the integration around it, five ideas determine almost every decision you'll make: what identifies a job, which read path to use, how fresh the data is, how removals work, and what gets billed.

***

## The job object and its `id`

Every endpoint that returns jobs — search, feed, company jobs — returns the same shape. `id` is a stable UUID assigned once and never reused, even as the rest of the posting changes underneath it. It's the correct primary key for an upsert: write on `id`, and an edited posting updates your row instead of creating a duplicate.

See [The job object](/docs/api-reference/jobs/object) for the full field reference rather than restating it here.

***

## Two ways to read jobs

Search and feed cover different jobs-to-be-done. Picking the wrong one for your use case is the most common integration mistake.

|              | Search                                   | Feed                        |
| ------------ | ---------------------------------------- | --------------------------- |
| Endpoints    | `GET /api/jobs`, `POST /api/jobs/search` | `POST /api/jobs/feed`       |
| Pagination   | Page-based                               | Cursor-based                |
| Ordering     | Relevance-ranked                         | Exhaustive, no ranking      |
| Built for    | A query a human is looking at            | Keeping a datastore in sync |
| Cost per job | Higher                                   | Lower                       |

If a person is reading the results on a screen, use search. If you're mirroring the corpus into your own database, use the feed — it's cheaper per job and, paired with a stable-scan cursor, immune to records shifting between pages as the underlying data changes mid-export. Deep page-based pagination over a moving dataset can skip or repeat records; cursor pagination can't.

<CodeGroup>
  ```bash search.sh theme={null}
  curl "https://connect.jobo.world/api/jobs?q=platform+engineer&location=Berlin&page_size=25" \
    -H "X-Api-Key: $JOBO_API_KEY"
  ```

  ```bash feed.sh theme={null}
  curl -X POST "https://connect.jobo.world/api/jobs/feed" \
    -H "X-Api-Key: $JOBO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"batch_size": 1000}'
  ```
</CodeGroup>

See [Pagination](/docs/pagination) for the mechanics of both models and [Sync a database](/docs/guides/sync-a-database) for the end-to-end pattern.

***

## Freshness

Data refreshes continuously. For an incremental sync against the feed, **15–60 minutes is a healthy schedule** — more frequent runs spend wallet credits without turning up much new data.

Two timestamps matter, and they mean different things:

* `created_at` — when Jobo first ingested the job.
* `updated_at` — when the posting last changed.

Store `updated_at` per job and skip the write when the incoming value hasn't moved. That turns a sync loop into a no-op for the vast majority of jobs on any given run.

<Note>
  `created_at` is exact ingestion time on the managed feed described below, under [Global inventory vs. managed sources](#global-inventory-vs-managed-sources). On the main feed, it reports indexing time for jobs under 24 hours old, which can lag true ingestion slightly.
</Note>

***

## Expiry

<Warning>
  `POST /api/jobs/feed` returns only **active** jobs. An expired job doesn't come back with a flag set — it simply stops appearing. If you only ever call `/feed`, you will never see a removal.
</Warning>

Detect removals with a separate call: `GET /api/jobs/expired` — see [Pagination](/docs/pagination) for its cursor mechanics. It enforces a **maximum 7-day lookback** — `expired_since` cannot be older than 7 days, and when omitted it defaults to just the last 24 hours.

The consequence is mechanical: **a sync that runs less often than weekly, or that stalls for more than 7 days, will silently accumulate dead rows** that this endpoint can no longer help you find. There's no way to ask "what expired 3 weeks ago."

Recovery is a full re-sync against `/feed`: page through the whole feed again, and treat any `id` still in your store that no longer appears as expired. See [Sync a database](/docs/guides/sync-a-database) for the sweep pattern and how to run it on the same schedule as your incremental sync so you never hit the 7-day wall.

***

## Sources

Jobs are normalized from **106 ATS and job-board platforms** into the one schema described above. `source` on every job is the platform's `provider_id` — `greenhouse`, `workday`, `lever`, and so on. See [Sources](/docs/sources) for the catalogue; it isn't reproduced here because it changes as coverage expands.

***

## Global inventory vs. managed sources

`POST /api/jobs/feed` draws from Jobo's whole corpus — every job across all 106 sources. `POST /api/jobs/feed/managed` is a private, narrower feed scoped to a set of employers you nominate in the Jobo portal, rather than everything Jobo indexes. Same batch shape and cursor semantics as the main feed, different scope. See [Sync a database](/docs/guides/sync-a-database) for how the two interact in a sync loop.

***

## Metering, briefly

Search and feed meter **per delivered job, not per request** — a page of 5 results costs less than a page of 100. Geocode is a flat per-lookup charge, refunded automatically on error. `GET /api/jobs/{id}`, `GET /api/jobs/expired`, and `GET /api/companies/{id}` are free.

This page won't restate prices or the request lifecycle — see [Billing](/docs/billing) for both.

***

## Developing without spending

There's no separate sandbox host for the main API — everything above runs against `https://connect.jobo.world`. To build and test without running up a bill:

* Call the free endpoints while you're wiring up the integration: `GET /api/jobs/{id}`, `GET /api/jobs/expired`, `GET /api/companies/{id}`.
* `GET /api/companies/{id}` needs no API key at all, so it's the cheapest possible smoke test for connectivity.
* When you do need search or feed, keep `page_size` and `batch_size` small — metering is per delivered job, so a `page_size=5` request during development costs a fraction of a production-sized page.
