Documentation

API reference

A single REST endpoint family over normalized, deduplicated job postings. Base URL https://api.joblistingsapi.com/v1.

Authentication

Every request is authenticated with your API key in the X-API-Key header. Keys are issued from your dashboard; keep them server-side and never ship them in client code.

bash
curl https://api.joblistingsapi.com/v1/jobs \
  -H "X-API-Key: jla_live_your_key_here"

Quickstart

Set JLA_API_KEY in your environment, then fetch your first ten jobs. Each snippet runs as written.

curl
curl https://api.joblistingsapi.com/v1/jobs?limit=10 \
  -H "X-API-Key: $JLA_API_KEY"
Node 20+
// Node 20+ (global fetch, no dependencies)
const res = await fetch(
  "https://api.joblistingsapi.com/v1/jobs?limit=10",
  { headers: { "X-API-Key": process.env.JLA_API_KEY } },
);
const { jobs, next_cursor } = await res.json();
console.log(jobs.length, "jobs;", next_cursor ? "more available" : "end");
Python 3 · requests
# Python 3 — pip install requests
import os, requests

res = requests.get(
    "https://api.joblistingsapi.com/v1/jobs",
    params={"limit": 10},
    headers={"X-API-Key": os.environ["JLA_API_KEY"]},
)
res.raise_for_status()
data = res.json()
print(len(data["jobs"]), "jobs")

Listing jobs & filters

GET /jobs returns a page of JobV1 records. Combine any of the filters below; gated filters return 403 on plans that don't include them.

ParameterTypePlanNotes
limitintAll plansResults per page. Max 100 (200 on Scale).
offsetintAll plansOffset pagination; returns total.
cursorstringGrowth+Stable cursor pagination; pass back next_cursor.
posted_afterdate-timeAll plansOnly jobs listed at/after this instant.
posted_beforedate-timeAll plansOnly jobs listed before this instant.
updated_sincedate-timeAll plansRecords changed since — for delta sync.
titlestringAll plansCase-insensitive title match.
companystringAll plansCompany name match.
locationstringAll plansFree-text location match.
countrystringAll plansISO 3166-1 alpha-2, e.g. GB, US.
remote_onlybooleanAll plansRestrict to remote postings.
sourcestringAll plansFilter to one ATS, e.g. greenhouse.
role_categorystringGrowth+Normalized role taxonomy, e.g. engineering.
senioritystringGrowth+Normalized level, e.g. senior.
salary_minintGrowth+Lower salary bound (postings that disclose it).
salary_maxintGrowth+Upper salary bound.
currencystringGrowth+ISO 4217 currency for salary filters.

Pagination & delta sync

Two pagination modes. offset + total works on every plan and is simplest for shallow, one-off queries. Cursor pagination (Growth+) is stable by id — it won't skip or repeat records when the dataset shifts under you, which makes it the right choice for keeping a mirror.

For incremental sync, pass updated_since with your last successful sync timestamp. The recommended pattern for a local mirror is cursor + updated_since together:

delta sync
# 1. Initial backfill — walk the cursor to the end
GET /v1/jobs?limit=100&updated_since=2026-06-01T00:00:00Z
  → { "jobs": [...], "next_cursor": "eyJpZCI6..." }
GET /v1/jobs?limit=100&cursor=eyJpZCI6...   # repeat until next_cursor is null

# 2. Incremental — only what changed since your last sync
GET /v1/jobs?limit=100&updated_since=2026-06-11T06:00:00Z&cursor=...

Response fields

Every record is a JobV1. Taxonomy fields ship on every plan; description_html requires Starter+, and structured_description is Scale-only.

FieldTypeNotes
idintStable numeric identifier.
titlestringAlways populated.
companystringAlways populated.
locationEvery planobjectraw, city, region, country_code (ISO alpha-2).
employment_typestring?e.g. FULL_TIME, when the source declares it.
remote_policystring?remote / hybrid / onsite — meaningful on ~70%.
is_remotebooleanConvenience boolean for remote roles.
remote_scopestring?Geographic scope, e.g. "United Kingdom".
seniorityEvery planstring?Normalized level; sometimes defaulted.
role_categoryEvery planstring?Normalized role taxonomy.
role_subcategoryEvery planstring?Finer-grained role taxonomy.
salaryobject?min, max, currency, period, display. Only ~15–25% disclose it.
description_htmlStarter+string?HTML body as published by the employer — sanitize before rendering in a browser. Present on ~90% of postings.
structured_descriptionScaleobject?Parsed sections (responsibilities, requirements…).
duplicate_cluster_idEvery planstring?Same posting seen on multiple boards shares this UUID.
listed_atdate-time?When the employer first listed the role.
created_atdate-timeWhen we first ingested the record.
updated_atdate-timeLast change — drives updated_since.
valid_throughdate-time?Expiry hint for the posting.
statusenum"active" or "removed" once delisted.
urlstringOriginal posting URL on the source ATS. Always populated.
sourcestringThe ATS platform. Always populated.

Rate limits

Limits are enforced per minute, per day, and per month. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Exceed a window and you get a 429 with a Retry-After header.

PlanPer minutePer dayPer month
Free105005,000
Starter6010,000200,000
Growth300100,0002,000,000
Scale600500,00010,000,000

Errors

Errors use standard HTTP status codes with a JSON body describing what went wrong.

StatusMeaningFix
401Missing or malformed API key.Send a valid key in the X-API-Key header.
403Key valid, but this plan can't use that filter or field.Upgrade, or drop the gated parameter.
404No job with that id (or it has been purged).Check the id; removed jobs may still 200 with status "removed".
422A parameter failed validation.Read the detail array; fix the named parameter.
429Rate limit exceeded for the current window.Back off until the Retry-After header's seconds elapse.

Interactive reference

Prefer to poke at it live? The full OpenAPI reference, with a try-it console, is generated from the same schema.

Open the interactive referencehttps://api.joblistingsapi.com/v1/docs