API reference
The JobIndex API
Every open job we index, over plain HTTPS and JSON. Access comes with the API plan, which includes a commercial licence and the change feed, or the Data plan, which adds the bulk download.
Getting started
The base address is:
https://api.jobindex.space
Your first request, the five newest remote design jobs that state pay:
curl -H "Authorization: Bearer $JOBINDEX_KEY" "https://api.jobindex.space/v1/jobs?specialization=design&remote=remote&has_salary=true&sort=recent&limit=5"
Every response is JSON in UTF-8. Times are ISO 8601 in UTC. Salaries keep the currency and period the employer published (hour, day, week, month, year); we never convert them. apply_url always points at the employer or board that published the job: link to it, do not hide it.
Authentication
With an API or Data plan, create a key in your workspace and send it on every request:
curl -H "Authorization: Bearer jik_…" "https://api.jobindex.space/v1/jobs?q=nurse"
Keys start with jik_. They are shown once, when you create them; we keep only a hash. Revoke a key from the workspace at any time, and keep it out of browsers and public repositories: call the API from your server.
Rate limits
Limits are per minute. With a key they count per account, so all your keys share one budget.
| Plan | Requests per minute | Also includes |
|---|
| API plan | 600 per account | Commercial licence, change feed, Pro |
| Data plan | 1,200 per account | Everything in API, bulk download |
Every response says where you stand:
x-ratelimit-limit: 600
x-ratelimit-remaining: 587
x-ratelimit-reset: 42
Past the limit you get 429. Wait x-ratelimit-reset seconds, when the minute ends, and carry on.
Paging
Lists take page (1–1000) and limit (1–100) and answer with total, the exact number of matches, and has_more:
{ "data": [ … ], "page": 2, "limit": 100, "total": 13111, "has_more": true }To copy more than a search can page through, use the bulk download once and then the change feed, which pages by cursor with no depth limit.
Errors
Errors use the HTTP status and a JSON body with a message you can show or log:
{ "error": "querystring/limit Too big: expected number to be <=100", "request_id": "req-9p" }400 | A parameter is missing or invalid. error names the parameter: "querystring/limit Too big: expected number to be <=100". |
401 | The endpoint needs an API key and none was sent, or the key is revoked or unknown. |
402 | The endpoint needs a plan your account does not have. |
404 | No job or company with that id. |
429 | Rate limit reached. Wait for x-ratelimit-reset seconds, or until the running download finishes. |
5xx | Something failed on our side. Retry with backoff; include request_id if you contact us. |
Jobs
Search the open roles and read one job with every place it was posted.
GET/v1/jobs
Every open job that matches all the filters you pass. Without q, results are newest first.
Query
q | string ≤ 200 Full-text search over title, company, place and description. Ranked by relevance unless you pass sort. |
title | string ≤ 200 Title contains any of these terms, separated by OR or commas: nurse OR caregiver. |
company | string ≤ 200 Company name contains this text. |
company_id | integer Only this company’s jobs (exact, from /v1/companies). |
location | string ≤ 200 Place text: a city, region or country name (Berlin, Bavaria, Germany). |
remote | onsite | hybrid | remote Work arrangement stated by the posting. |
employment_type | enum full_time, part_time, contract, temporary, internship, volunteer, other. |
specialization | enum Field of work, sorted from the title: software-engineering, data, engineering, healthcare, sales, marketing, design, customer-support, finance, operations-logistics, skilled-trades, hospitality, education, people-hr. |
platform | string ≤ 50 Only jobs read from this platform (greenhouse, workday, lever …). Ids are listed by /v1/source-platforms. |
posted_after | ISO date or timestamp Posted on or after this moment: 2026-11-01 or 2026-11-01T09:00:00Z. |
has_salary | boolean Only jobs whose posting states pay. Default false. |
include_closed | boolean Also return jobs that have left their source. Default false. |
sort | relevance | recent | added relevance (the default with q), recent (posting date) or added (when we first saw it). |
page | integer 1–1000 Page number. Default 1. |
limit | integer 1–100 Results per page. Default 20. |
Example
curl "https://api.jobindex.space/v1/jobs?q=designer&remote=remote&has_salary=true&limit=1"
Response
{
"data": [
{
"id": 4152,
"title": "Designer",
"company": "The Planet Group",
"company_id": 4138,
"company_slug": "the-planet-group",
"location": "New Orleans, Louisiana",
"countries": ["US"],
"remote": null,
"employment_type": "contract",
"salary": { "min": 25, "max": 40, "currency": "USD", "period": "hour" },
"posted_at": "2026-10-30T00:00:00.000Z",
"first_seen_at": "2026-10-31T20:46:28.027Z",
"last_seen_at": "2026-11-02T10:52:56.404Z",
"is_open": true,
"apply_url": "mailto:applications@staffingfuture.com",
"source_count": 1,
"has_logo": true,
"blurb": "Distribution Designer, contract to hire …",
"tags": ["Excel", "SAP", "Agile"]
}
],
"page": 1,
"limit": 1,
"total": 13111,
"has_more": true
}- total is exact. has_more tells you whether another page exists; the deepest page is 1000.
- For more than 100,000 results, narrow the filters, or use the change feed or the bulk download.
GET/v1/jobs/{id}
One job with every source posting behind it (each board it was found on), the full description and its change history.
Path
idrequired | integer The job id from search. |
Example
curl "https://api.jobindex.space/v1/jobs/4152"
Response
{
"id": 4152,
"title": "Designer",
"company": "The Planet Group",
"salary_min": 25, "salary_max": 40, "salary_currency": "USD", "salary_period": "hour",
"is_open": true,
"postings": [
{
"platform": "wpjobmanager",
"source_key": "careers.theplanetgroup.com",
"external_id": "12426",
"locations": [{ "raw": "New Orleans, Louisiana", "city": "New Orleans", "region": "Louisiana", "country": "US" }],
"source_url": "https://careers.theplanetgroup.com/job/653020-designer-new-orleans-louisiana/",
"salary": { "min": 25, "max": 40, "raw": "$25-$40", "period": "hour", "currency": "USD" },
"department": null,
"last_checked_at": "2026-11-05T05:47:35.932Z",
"closed_at": null,
"description_html": "<p>Distribution Designer …</p>"
}
],
"history": [ … ]
}- Descriptions are the employer’s own text. Treat them as untrusted input: sanitise HTML before you show it.
GET/v1/jobs/by-url
The indexed job behind a posting page or apply link, if we have it.
Query
urlrequired | URL A posting or apply address. |
Example
curl "https://api.jobindex.space/v1/jobs/by-url?url=https://boards.greenhouse.io/acme/jobs/123"
GET/v1/feed/recent
Server-sent events: one event for each job as it is added to the index. Keep the connection open.
Example
curl -N "https://api.jobindex.space/v1/feed/recent"
GET/v1/suggest
Title completions, matching open jobs and companies for a partly typed query.
Query
qrequired | string ≤ 100 What the user has typed so far. |
Example
curl "https://api.jobindex.space/v1/suggest?q=data%20eng"
Companies and facets
The employers behind the jobs, and the values you can filter by, with counts.
GET/v1/companies
Companies with open jobs, with hiring signals: open, new, remote and pay-stating roles, countries and top fields.
Query
q | string ≤ 200 Company name contains this text. |
country | ISO 3166 code Companies with an open job in this country: DE, US, KE … |
remote | boolean Companies with at least one open remote job. Default false. |
hiring_now | boolean Companies that added a job in the last 7 days. Default false. |
has_salary | boolean Companies with at least one open job that states pay. Default false. |
specialization | enum Companies with an open job in this field. |
page | integer 1–1000 Page number. Default 1. |
limit | integer 1–100 Results per page. Default 20. |
Example
curl "https://api.jobindex.space/v1/companies?country=DE&hiring_now=true&limit=1"
Response
{
"data": [
{
"id": 92727,
"name": "Svetness Personal Training",
"slug": "svetness-personal-training",
"domain": null,
"open_jobs": 5032,
"new_jobs": 8,
"remote_jobs": 0,
"salary_jobs": 5020,
"countries": ["US"],
"specializations": [{ "id": "education", "label": "Education", "count": 10 }],
"latest": { "id": 94485, "title": "Personal Trainer", "posted_at": "2026-11-05T09:47:54.486Z" },
"last_seen_at": "2026-11-05T09:47:54.486Z",
"has_logo": false
}
],
"total": 41147, "page": 1, "limit": 1, "has_more": true
}GET/v1/companies/{id}
One company by id. /v1/companies/by-slug/{slug} does the same by its slug.
Path
idrequired | integer Company id. |
Example
curl "https://api.jobindex.space/v1/companies/by-slug/the-planet-group"
GET/v1/companies/{id}/logo
The company’s logo as an image, when has_logo is true. Cached for a day.
Path
idrequired | integer Company id. |
Example
curl -o logo.png "https://api.jobindex.space/v1/companies/4138/logo"
GET/v1/filter-values
The values of one facet across the jobs matching your other filters, most common first. Use it to build filter menus.
Query
filterrequired | countries | remote | employment_type | platform | company The facet to count. |
contains | string ≤ 100 Only values containing this text. |
… | Any job filter from Search jobs narrows the counts. |
Example
curl "https://api.jobindex.space/v1/filter-values?filter=countries&q=nurse"
Response
{ "filter": "countries", "values": [{ "value": "US", "count": 18204 }, { "value": "GB", "count": 2210 }] }GET/v1/specializations
Open-job counts per specialization.
Example
curl "https://api.jobindex.space/v1/specializations"
GET/v1/market
Counts, top employers and places for any search. Takes the same filters as Search jobs.
Example
curl "https://api.jobindex.space/v1/market?specialization=data&location=Germany"
GET/v1/salaries
Pay ranges stated in the jobs that match, by currency and period. Takes the same filters as Search jobs.
Example
curl "https://api.jobindex.space/v1/salaries?title=backend%20engineer&location=Berlin"
Sync and bulk
Keep your own copy of the catalogue current. Both need a paid plan and an API key.
GET/v1/changes
Jobs added, closed or changed since a cursor, oldest change first, in the same shape as search results plus change_seq.
Query
since | integer The next_cursor from your previous call. 0 starts at the beginning of the feed. Default 0. |
limit | integer 1–500 Changes per call. Default 100. |
Example
curl -H "Authorization: Bearer $JOBINDEX_KEY" "https://api.jobindex.space/v1/changes?since=0&limit=500"
Response
{
"data": [
{ "id": 4152, "title": "Designer", "is_open": false, "change_seq": 1882041, … }
],
"next_cursor": 1882041,
"has_more": true
}- Store next_cursor and pass it as since next time. Repeat while has_more is true, then poll every few minutes.
- A job that changed twice appears once, at its latest position. is_open: false means it has closed.
- Start from the bulk download or a search, then follow the feed: the feed only holds changes since it began.
GET/v1/export/jobs.ndjson.gz
Every open job, one JSON object per line, gzipped and streamed. Load it once, then stay current with the change feed.
Example
curl -H "Authorization: Bearer $JOBINDEX_KEY" -o jobs.ndjson.gz "https://api.jobindex.space/v1/export/jobs.ndjson.gz"
gunzip -c jobs.ndjson.gz | head -1
- Four downloads per account per day. One download runs at a time across the service; if another is running you get 429, try again in a few minutes.
- Each line has the search-result shape. Note the largest change_seq you have seen and follow /v1/changes from there.
Index and status
GET/v1/stats
Exact counts of open jobs, companies and sources, and when they were computed.
Example
curl "https://api.jobindex.space/v1/stats"
Response
{ "unique_jobs": 906444, "companies": 40934, "sources": 166, "ats_platforms": 101, "computed_at": "2026-11-06T09:05:57.259Z" }GET/v1/source-platforms
Every platform the index reads, with its boards, open jobs, everything ever ingested and when it was last read.
Example
curl "https://api.jobindex.space/v1/source-platforms"
GET/v1/pulse
Flow, freshness, fields, places, pay and sources of the open roles, as shown on the market pulse page.
Example
curl "https://api.jobindex.space/v1/pulse"
GET/v1/status
Uptime of the API and the website, and recent incidents.
Example
curl "https://api.jobindex.space/v1/status"
MCP for AI agents
The same search is available to AI assistants over the Model Context Protocol, at https://api.jobindex.space/mcp (public, no account) and https://api.jobindex.space/mcp/account (your saved jobs and searches, after you connect). Setup for Claude, ChatGPT and others.
OpenAPI
A machine-readable description of every public endpoint, for code generators and API clients:
https://api.jobindex.space/docs/json
Using the data
The API and Data plans let you show JobIndex data in your own products and services. Whichever plan you use, keep each job’s link to where it was published, and do not present jobs as your own listings. See the terms.