Endpoints

Plain HTTP, plain JSON.

Every path below is relative to https://jobs.peopleseek.ai/api/v1's origin. Interactive documentation: https://jobs.peopleseek.ai/api/v1/docs. Specification: https://jobs.peopleseek.ai/api/v1/openapi.json.

EndpointWhat it returnsParameters
GET/api/v1/jobsSearch the index.q, occupation, occupation_id, industry_id, country, countries, region, city, work_arrangement, eligible_country, worldwide_eligible, include_unknown_eligibility, salary_min, salary_max, currency, compensation_period, employment_type, seniority, language, company, posted_since, verified_since, status, sort, limit, cursor
GET/api/v1/jobs/{id}One job by id or slug, with its description, source, attribution and verification.—
GET/api/v1/jobs/{id}/similarJobs like this one.limit
GET/api/v1/jobs/{id}/application-optionsHow to apply: the employer's URL, the method, structured requirements when the source has them. directSubmitSupported is always false.—
GET/api/v1/jobs/{id}/statusIs it still listed? Status, last seen, last verified, closing date.—
GET/api/v1/occupationsThe occupation taxonomy (ESCO over ISCO-08) with active counts.parent_id, kind, limit, cursor
GET/api/v1/occupations/searchResolve a title to occupations.q, limit
GET/api/v1/occupations/{id}One occupation: ancestors, children, active jobs.—
GET/api/v1/industriesISIC Rev. 4 industries with active counts.parent_code, level
GET/api/v1/countriesEvery country, with active and remote eligible counts.—
GET/api/v1/locations/searchResolve a place name to a country, region or city.q, limit
GET/api/v1/companiesCompanies with active jobs.q, limit, cursor
GET/api/v1/compensationAdvertised pay across a search: one currency and period, undisclosed jobs counted as excluded, never converted.the search parameters, min_sample
GET/api/v1/healthLiveness, the database, and the index's live size and freshness.—
POST/api/v1/applications/submitAlways refused (409 direct_submit_not_supported) with the job's application options in the error: no employer has authorised PeopleSeek to submit for anyone.JSON body: job_id
GET/api/v1/openapi.jsonThe OpenAPI 3.1 description of this API.—
GET/api/v1/docsAn HTML reference reading openapi.json.—

The Developers page has the same endpoints, the search parameters by group and a request builder.

Responses

One envelope for every answer.

A success is ok: true with data and, on lists, meta for pagination. A search's data is the page: jobs, total, totalCapped, nextCursor and applied; meta repeats next_cursor, total, total_capped and applied. A refusal is ok: false with error: a code, a message and sometimes details.

A search answer
{
  "ok": true,
  "data": {
    "jobs": [ /* jobs */ ],
    "total": 1234,
    "totalCapped": false,
    "nextCursor": "eyJ…",
    "applied": { "q": "nurse", "limit": 20, "sort": "relevance", "status": ["active"] }
  },
  "meta": {
    "next_cursor": "eyJ…",
    "total": 1234,
    "total_capped": false,
    "applied": { "q": "nurse", "limit": 20, "sort": "relevance", "status": ["active"] }
  }
}
A refused request
{
  "ok": false,
  "error": {
    "code": "invalid_parameters",
    "message": "One or more parameters are invalid.",
    "details": [{ "path": ["limit"], "message": "…" }]
  }
}
Job fields

What the answers mean.

Eligibility

worldwidecountriesunknownnot_applicable

A remote job is never worldwide by default. remoteEligibility is worldwide only when the employer says so, countries with the list in eligibleCountries, unknown when the posting is labelled remote but does not say, and not_applicable for jobs on site. eligible_country=PH returns jobs whose eligibility includes PH or is worldwide; add include_unknown_eligibility=true to also see the unknown ones, and say so to the person.

Source and attribution

Every job carries source (source.name, the board or feed it was read from, with source.attributionText and source.attributionUrl when the source's terms ask for them), canonicalUrl (its page there) and applyUrl (the employer's application page, when the source gives one). When you show a job, show its source; when the attribution is present, show it. Republishing the data inherits those terms.

Freshness

activeunverifiedstaleclosedexpiredremoved

verification.lastVerifiedAt is when the source last confirmed the job, and verification.status its status: active is listed on the last run, unverified means the source could not be read lately. Only active jobs are searched by default; the others are reachable by id with their status stated. Tell the person when a job was verified, and check its status before they act on an old answer.

Applications

PeopleSeek holds no employer's authorisation to submit on anyone's behalf: directSubmitSupported is false for every job. An agent may prepare an application with a person and open the employer's own page; it must never claim an application was submitted through PeopleSeek, because none can be.

Counts

Every number is a live count from the index. A total with total_capped: true is a floor, not an estimate dressed up.

Pagination

Cursor pages, with the same filters.

Lists are paged by cursor: pass limit (default 20, at most 100) and, for the next page, cursor=meta.next_cursor with the same filters. A null next_cursor is the last page. meta.total is the exact count, or the cap with total_capped: true.

Examples

curl, TypeScript and Python.

curl
curl "https://jobs.peopleseek.ai/api/v1/jobs?q=nurse&work_arrangement=remote&eligible_country=PH&limit=5"
TypeScript
const url = new URL("https://jobs.peopleseek.ai/api/v1/jobs");
url.searchParams.set("q", "data engineer");
url.searchParams.set("work_arrangement", "remote");
url.searchParams.set("eligible_country", "BR");

const res = await fetch(url);
const { data, meta } = await res.json();
for (const job of data.jobs) {
  console.log(job.title, "—", job.company.name, "via", job.source.name);
}
if (meta.next_cursor) {
  // ask again with cursor=meta.next_cursor for the next page
}
Python
import requests

res = requests.get(
    "https://jobs.peopleseek.ai/api/v1/jobs",
    params={"q": "nurse", "work_arrangement": "remote", "eligible_country": "PH"},
    timeout=10,
)
body = res.json()
for job in body["data"]["jobs"]:
    print(job["title"], "-", job["company"]["name"], "via", job["source"]["name"])
Rate limits

The same for everyone.

120REST requests a minute, per IP
60MCP requests a minute, per IP
429over the limit, with a Retry-After header

120 requests a minute per IP on https://jobs.peopleseek.ai/api/v1; 60 a minute per IP on the MCP endpoint. Over the limit the answer is 429 with a Retry-After header. The limits are the same for everyone; there is no key to raise them.