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.
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 https://api.joblistingsapi.com/v1/jobs?limit=10 \
-H "X-API-Key: $JLA_API_KEY"// 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 — 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.
| Parameter | Type | Plan | Notes |
|---|---|---|---|
limit | int | All plans | Results per page. Max 100 (200 on Scale). |
offset | int | All plans | Offset pagination; returns total. |
cursor | string | Growth+ | Stable cursor pagination; pass back next_cursor. |
posted_after | date-time | All plans | Only jobs listed at/after this instant. |
posted_before | date-time | All plans | Only jobs listed before this instant. |
updated_since | date-time | All plans | Records changed since — for delta sync. |
title | string | All plans | Case-insensitive title match. |
company | string | All plans | Company name match. |
location | string | All plans | Free-text location match. |
country | string | All plans | ISO 3166-1 alpha-2, e.g. GB, US. |
remote_only | boolean | All plans | Restrict to remote postings. |
source | string | All plans | Filter to one ATS, e.g. greenhouse. |
role_category | string | Growth+ | Normalized role taxonomy, e.g. engineering. |
seniority | string | Growth+ | Normalized level, e.g. senior. |
salary_min | int | Growth+ | Lower salary bound (postings that disclose it). |
salary_max | int | Growth+ | Upper salary bound. |
currency | string | Growth+ | 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:
# 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.
| Field | Type | Notes |
|---|---|---|
id | int | Stable numeric identifier. |
title | string | Always populated. |
company | string | Always populated. |
locationEvery plan | object | raw, city, region, country_code (ISO alpha-2). |
employment_type | string? | e.g. FULL_TIME, when the source declares it. |
remote_policy | string? | remote / hybrid / onsite — meaningful on ~70%. |
is_remote | boolean | Convenience boolean for remote roles. |
remote_scope | string? | Geographic scope, e.g. "United Kingdom". |
seniorityEvery plan | string? | Normalized level; sometimes defaulted. |
role_categoryEvery plan | string? | Normalized role taxonomy. |
role_subcategoryEvery plan | string? | Finer-grained role taxonomy. |
salary | object? | 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_descriptionScale | object? | Parsed sections (responsibilities, requirements…). |
duplicate_cluster_idEvery plan | string? | Same posting seen on multiple boards shares this UUID. |
listed_at | date-time? | When the employer first listed the role. |
created_at | date-time | When we first ingested the record. |
updated_at | date-time | Last change — drives updated_since. |
valid_through | date-time? | Expiry hint for the posting. |
status | enum | "active" or "removed" once delisted. |
url | string | Original posting URL on the source ATS. Always populated. |
source | string | The 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.
| Plan | Per minute | Per day | Per month |
|---|---|---|---|
| Free | 10 | 500 | 5,000 |
| Starter | 60 | 10,000 | 200,000 |
| Growth | 300 | 100,000 | 2,000,000 |
| Scale | 600 | 500,000 | 10,000,000 |
Errors
Errors use standard HTTP status codes with a JSON body describing what went wrong.
| Status | Meaning | Fix |
|---|---|---|
401 | Missing or malformed API key. | Send a valid key in the X-API-Key header. |
403 | Key valid, but this plan can't use that filter or field. | Upgrade, or drop the gated parameter. |
404 | No job with that id (or it has been purged). | Check the id; removed jobs may still 200 with status "removed". |
422 | A parameter failed validation. | Read the detail array; fix the named parameter. |
429 | Rate 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→