1. Get an API token
API tokens are managed in your JobFront dashboard under Settings → API. Create one there and copy it somewhere safe — you send it with every request.
- A token is tied to your organization. Every request is automatically scoped to your organization's data, so you never pass an organization id.
- You can create multiple tokens — one per integration, say — and revoke any of them at any time. They all share your organization's plan and quota.
See Authentication for the full token lifecycle, including zero-downtime rotation.
2. Make your first call
Send the token as a Bearer credential in the Authorization header. Base URL is
https://api.jobfront.com, and every v4 path is prefixed with /v4/.
curl "https://api.jobfront.com/v4/jobs?search=data+engineer&is_remote=true&limit=10" \
-H "Authorization: Bearer YOUR_API_TOKEN"
resp = requests.get(
"https://api.jobfront.com/v4/jobs",
headers={"Authorization": "Bearer YOUR_API_TOKEN"},
params={"search": "data engineer", "is_remote": "true", "limit": 10},
)
resp.raise_for_status()
data = resp.json()
print(data["metrics"]["count_jobs"], "matches")
for job in data["data"]:
print(job["title"], "—", job["source"]["name"])
const params = new URLSearchParams({
search: "data engineer",
is_remote: "true",
limit: "10",
});
const res = await fetch(`https://api.jobfront.com/v4/jobs?${params}`, {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
if (!res.ok) throw new Error(`JobFront API ${res.status}`);
const data = await res.json();
console.log(data.metrics.count_jobs, "matches");
for (const job of data.data) {
console.log(job.title, "—", job.source.name);
}
If the token is missing or invalid you get an HTTP 401 with
error.code = unauthorized. Every error uses
the same envelope — switch on
error.code, never on the prose in error.message.
Note
All read endpoints are GET and take query-string parameters; every parameter is
optional unless noted. Multi-value filters take a comma-separated list, e.g.
?commitments=full_time,contract. Boolean filters accept 1, true, yes or on
as true. See Conventions for encoding rules.
3. Read the response envelope
Every list endpoint returns the same envelope, so one parser handles all of them.
{
"offset": 0,
"limit": 10,
"data": [ ],
"cursor": "WzE3NTI3MTA0MDAsImExYjJjM2Q0Il0",
"metrics": { "count_jobs": 342, "updated_at": 1752768000 }
}
| Field | What it is |
|---|---|
offset |
The offset applied to this page. Zero-based, default 0. |
limit |
The page size applied. Default 25, capped at 25. |
data |
The array of result objects for this page — Job or Source records. |
cursor |
Opaque position token. Returned on /v4/jobs only, and only when another page may exist. |
metrics.count_jobs |
Total matches across all pages (count_sources on company searches). |
metrics.updated_at |
When the result was produced. Data is live, so this is effectively "now". |
All *_at fields are Unix epoch seconds in UTC. Single-object endpoints such as
GET /v4/jobs/<job_id> return the record directly rather than an envelope — or {} if
it does not exist or is not in your scope. List endpoints return an empty data array
in the same situation; the API prefers empty results over 404s.
The field names inside each record are documented in the data dictionary.
Paging past the first 25
Every page returns at most 25 records, on every endpoint and every billing mode.
For everyday use, limit/offset is all you need — reach for a cursor only once
a pull has to go past the first 10,000 results.
- Offset paging — pass
limitandoffset(?limit=25&offset=25for records 26–50). Offset paging reaches the first 10,000 results; beyond that the offset is clamped to keep the window in range, which is not an error. - Cursor paging — for a whole result set, omit
offsetand follow the top-levelcursorfrom each response as?cursor=.... Stop when a response has nocursor. A cursor is only returned when a page comes back full, so a short page means you have reached the end. Do not build or edit a cursor: a malformed one returns a400witherror.code=invalid_cursor. - Counting first — add
?count=trueto any list endpoint to get just{ "count": N }with no records, which is the cheap way to size a result set before paging through it.
Every response also carries rate-limit headers for two windows — a one-minute floor
(X-RateLimit-Limit / -Remaining / -Reset) and your primary five-hour budget
(the same three with a -5h suffix) — so your client can self-throttle. See
Rate limits and billing.
4. Check your plan
GET /v4/me tells you who the token belongs to, your rate limits, your billing mode,
and the one thing that changes what a query returns: your field tier.
curl https://api.jobfront.com/v4/me \
-H "Authorization: Bearer YOUR_API_TOKEN"
{
"organization_id": "O_abc",
"fields": {
"tier": "default",
"jobs": ["categories", "commitment", "created_at", "description", "…"],
"sources": ["brand_name", "id", "industry", "name", "…"],
"filters": ["cbsa", "categories", "commitments", "is_remote", "…"],
"facets": ["categories", "cbsas", "commitments", "industries", "…"]
},
"rate_limit": 600,
"period_seconds": 60,
"remaining": 599,
"rate_limit_5h": 50000,
"period_seconds_5h": 18000,
"remaining_5h": 49821,
"billing_mode": "subscription"
}
The four lists under fields are truncated here; the real response spells each one out
in full.
Your field tier
fields.tier is one of min, default or max, and it decides which keys
survive on a record — restricted fields are omitted, not nulled, so an absent key can
mean either "not in the data" or "not in your tier". The lists beside it remove that
ambiguity: jobs and sources are the keys you get back, filters are the query
parameters you may filter on, and facets are the aggregation keys /v4/jobs/options
will return. Ask the API rather than inferring from a missing key.
What your token can reach
The whole dataset. /v4/jobs with no filters searches the entire JobFront corpus,
newest first; adding filters narrows it, and free-text search applies on top of
whatever filters are active. An explicit collections parameter narrows to the sources
in those collections.
The one thing excluded without your asking is your own blocked sources and hidden jobs — see Data controls. There is no access mode to check and nothing to upgrade for reach.
What next
- Search and filters — the
+required,-excludedanda|boperators, and how filters combine. - Conventions — pagination, timestamps, scoping and not-found semantics in full.
- V4 API reference — every endpoint and parameter, callable from the browser.
- Exports — scheduled flat files, each run a full snapshot.
- MCP server — connect Claude, ChatGPT and other MCP clients.
- Errors — status codes and what to retry.