JobFront Docs

Get started

Get a JobFront API token, make your first authenticated call to /v4/jobs, read the response envelope, and check what your plan returns — in under two minutes.

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 limit and offset (?limit=25&offset=25 for 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 offset and follow the top-level cursor from each response as ?cursor=.... Stop when a response has no cursor. 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 a 400 with error.code = invalid_cursor.
  • Counting first — add ?count=true to 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, -excluded and a|b operators, 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.