Exende Docs

Jobs Data API

Jobs Data API

Search normalized job listings, explore company hiring, and read observed changes with a scoped customer key. Start with a small query, then inspect the response and credit usage.

Updated September 8, 2026

Quickstart

  1. Create an account and verify your email. New verified organizations receive 2,500 onboarding credits for 14 days.
  2. In API keys, create a key with jobs:read. Copy it while it is shown and store it in a server environment variable named EXENDE_API_KEY.
  3. Run this request. Five returned records cost five credits; an empty result costs zero.

bash

Search for up to five jobs

curl --get "https://data.exende.dev/v1/jobs/search" \
  --data-urlencode "q=software engineer" \
  --data-urlencode "limit=5" \
  -H "Authorization: Bearer $EXENDE_API_KEY"

The response is { data: Job[], nextCursor: string | null }. Each job has a canonical ID, company, title, available location and normalized fields, source URL, and observation timestamps. See the complete response below. Your request appears in Usage after it is recorded.

Download the Jobs OpenAPI 3.1 contract

Authentication & scopes

Base URL: https://data.exende.dev. Send Authorization: Bearer $EXENDE_API_KEY. Keys belong to your organization, are shown once, and can be revoked in the dashboard. Keep them out of browser code, URLs, and logs.

ScopeAccess
jobs:readJobs, search, job history, history feed, and skills.
companies:readCompany detail, company jobs, and company hiring.
signals:readOverview and hiring metrics.

Only the documented customer routes are supported. Jobs Data keys and credits are separate from the x402 infrastructure payment model.

List jobs

GET /v1/jobs

Filter current normalized job records. Keyset pagination: default 50, max 100. Ordered by last_seen_at DESC, id DESC. Pass nextCursor as cursor with unchanged filters. Concurrent catalogue updates mean this is not a frozen snapshot. Credit cost: 1 per returned record. Required scope: jobs:read.

bash

Example request

curl "https://data.exende.dev/v1/jobs?limit=5" -H "Authorization: Bearer $EXENDE_API_KEY"
ParameterType / locationMeaning
limitinteger / queryPage size. Missing or invalid numeric values fall back to 50; the response never exceeds 100 records. Maximum: 100. Minimum: 1.
qstring / queryTrimmed FTS prefix query across title, department and description. Terms are combined with AND; at least one term must have two letters or numbers. Not a company or city search. Maximum length: 160.
companyIdstring / queryExact canonical company ID. Format: uuid.
domainstring / queryCompany domain normalized by the API. Maximum length: 253.
locationstring / queryCase-insensitive source location substring; SQL LIKE % and _ wildcards retain their current meaning. Maximum length: 100.
countrystring / queryISO alpha-2 country, case-insensitive; normalized to uppercase.
titlestring / queryCase-insensitive job-title substring; SQL LIKE wildcards retain their current meaning. Maximum length: 100.
skillstring / queryNormalized skill slug or recognized alias. Unknown skill returns an empty page. Maximum length: 100.
employmentTypestring / queryNormalized employment type. Values: full_time, part_time, contract, temporary, internship, unknown.
workplaceTypestring / queryNormalized workplace type. Values: remote, hybrid, on_site, unknown.
statusstring / queryCurrent canonical job status. Values: active, closed.
firstSeenAfterstring / queryInclusive lower bound on first_seen_at. Format: date-time.
publishedAfterstring / queryInclusive lower bound on the source posted_at timestamp; missing publication dates do not match. Format: date-time.
cursorstring / queryOpaque nextCursor from the previous response. Preserve all other filters. Maximum length: 500.

200 response: Successful JSON response. Returns data: [] when there are no matching records.

Response fields and full structural example
FieldTypeMeaning
dataJob[]See response schema.
data[].idstringCanonical source-scoped job identifier.
data[].company_idstringCanonical company identifier.
data[].companystringCanonical company name.
data[].canonical_domainstring | nullCanonical company domain; not an ATS hostname.
data[].titlestringSource job title.
data[].locationstring | nullSource location text; not a normalized city identifier.
data[].country_codestring | nullISO alpha-2 country only when explicit evidence supports normalization; otherwise null.
data[].departmentstring | nullDepartment if provided.
data[].employment_typestringSee response schema.
data[].workplace_typestringSee response schema.
data[].statusstringSee response schema.
data[].source_urlstringPublic listing URL retained for attribution.
data[].first_seen_atstringFirst Exende observation, not necessarily publication.
data[].last_seen_atstringMost recent canonical observation; unchanged visibility is capped at once per UTC day.
nextCursorstring | nullOpaque keyset cursor, or null when there is no further page.

Structural example only. Placeholder names, IDs, dates, and counts illustrate the shape; these are not live catalogue records.

json

Structural example — not live data

{
  "data": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "company_id": "00000000-0000-4000-8000-000000000000",
      "company": "Company name",
      "canonical_domain": null,
      "title": "Job title from the source",
      "location": null,
      "country_code": null,
      "department": null,
      "employment_type": "full_time",
      "workplace_type": "remote",
      "status": "active",
      "source_url": "https://company.example/careers/job",
      "first_seen_at": "2000-01-01T00:00:00.000Z",
      "last_seen_at": "2000-01-01T00:00:00.000Z"
    }
  ],
  "nextCursor": null
}

Common errors: 400, 401, 402, 403, 404, 429, 500, 503. See Errors & rate limits below for handling.

Job detail

GET /v1/jobs/{id}

Retrieve one canonical job and its normalized skills. Not paginated. Credit cost: 1. Required scope: jobs:read.

bash

Set JOB_ID or COMPANY_DOMAIN from a returned record

curl "https://data.exende.dev/v1/jobs/$JOB_ID" -H "Authorization: Bearer $EXENDE_API_KEY"
ParameterType / locationMeaning
id (required)string / pathCanonical UUID from a job record.

200 response: Successful JSON response. Unknown job returns 404.

Response fields and full structural example
FieldTypeMeaning
dataJobDetailSee response schema.
data.idstringCanonical source-scoped job identifier.
data.company_idstringCanonical company identifier.
data.companystringCanonical company name.
data.canonical_domainstring | nullCanonical company domain; not an ATS hostname.
data.titlestringSource job title.
data.locationstring | nullSource location text; not a normalized city identifier.
data.country_codestring | nullISO alpha-2 country only when explicit evidence supports normalization; otherwise null.
data.departmentstring | nullDepartment if provided.
data.employment_typestringSee response schema.
data.workplace_typestringSee response schema.
data.statusstringSee response schema.
data.source_urlstringPublic listing URL retained for attribution.
data.first_seen_atstringFirst Exende observation, not necessarily publication.
data.last_seen_atstringMost recent canonical observation; unchanged visibility is capped at once per UTC day.
data.website_urlstring | nullSee response schema.
data.description_textstring | nullAvailable normalized description text; null when unavailable.
data.posted_atstring | nullSource publication date when available.
data.closed_atstring | nullRecorded closure time, if closed.
data.skillsSkill[]See response schema.
data.skills[].slugstringSee response schema.
data.skills[].canonical_namestringSee response schema.
data.skills[].categorystringSee response schema.
data.description_enrichment_statusstring | nullLegacy supplementary description-processing status. Do not use it as source freshness or a stable customer workflow signal.

Structural example only. Placeholder names, IDs, dates, and counts illustrate the shape; these are not live catalogue records.

json

Structural example — not live data

{
  "data": {
    "id": "00000000-0000-4000-8000-000000000000",
    "company_id": "00000000-0000-4000-8000-000000000000",
    "company": "Company name",
    "canonical_domain": null,
    "title": "Job title from the source",
    "location": null,
    "country_code": null,
    "department": null,
    "employment_type": "full_time",
    "workplace_type": "remote",
    "status": "active",
    "source_url": "https://company.example/careers/job",
    "first_seen_at": "2000-01-01T00:00:00.000Z",
    "last_seen_at": "2000-01-01T00:00:00.000Z",
    "website_url": null,
    "description_text": null,
    "posted_at": null,
    "closed_at": null,
    "skills": [
      {
        "slug": "skill-slug",
        "canonical_name": "Canonical name",
        "category": "category"
      }
    ],
    "description_enrichment_status": null
  }
}

Common errors: 400, 401, 402, 403, 404, 429, 500, 503. See Errors & rate limits below for handling.

Job history

GET /v1/jobs/{id}/history

Newest observed versions first, ordered by observed_at DESC, id DESC. Historical snapshot fields use camelCase. Not paginated. Credit cost: 1 per returned record. Required scope: jobs:read.

bash

Set JOB_ID or COMPANY_DOMAIN from a returned record

curl "https://data.exende.dev/v1/jobs/$JOB_ID/history?limit=5" -H "Authorization: Bearer $EXENDE_API_KEY"
ParameterType / locationMeaning
id (required)string / pathCanonical job UUID.
limitinteger / queryPage size. Missing or invalid numeric values fall back to 50; the response never exceeds 100 records. Maximum: 100. Minimum: 1.

200 response: Successful JSON response. An unknown job or a job without versions returns data: [].

Response fields and full structural example
FieldTypeMeaning
dataJobVersion[]See response schema.
data[].idstringVersion ID; not guaranteed to be a UUID.
data[].observedAtstringSee response schema.
data[].changeTypestringSee response schema.
data[].contentHashstringSee response schema.
data[].snapshotJobSnapshot | nullSee response schema.

Structural example only. Placeholder names, IDs, dates, and counts illustrate the shape; these are not live catalogue records.

json

Structural example — not live data

{
  "data": [
    {
      "id": "00000000000000000000000000000000",
      "observedAt": "2000-01-01T00:00:00.000Z",
      "changeType": "created",
      "contentHash": "0000000000000000000000000000000000000000000000000000000000000000",
      "snapshot": null
    }
  ]
}

Common errors: 400, 401, 402, 403, 404, 429, 500, 503. See Errors & rate limits below for handling.

History feed

GET /v1/history

Latest 100 immutable version events, ordered by observed_at DESC, id DESC. Query limit and cursor are not supported. Not paginated. Credit cost: 1 per returned record. Required scope: jobs:read.

bash

Example request

curl "https://data.exende.dev/v1/history" -H "Authorization: Bearer $EXENDE_API_KEY"

No query parameters.

200 response: Successful JSON response. Returns data: [] when there are no matching records.

Response fields and full structural example
FieldTypeMeaning
dataHistoryEvent[]See response schema.
data[].idstringSee response schema.
data[].observed_atstringSee response schema.
data[].change_typestringSee response schema.
data[].job_idstringSee response schema.
data[].titlestringSee response schema.
data[].companystringSee response schema.

Structural example only. Placeholder names, IDs, dates, and counts illustrate the shape; these are not live catalogue records.

json

Structural example — not live data

{
  "data": [
    {
      "id": "00000000000000000000000000000000",
      "observed_at": "2000-01-01T00:00:00.000Z",
      "change_type": "created",
      "job_id": "00000000-0000-4000-8000-000000000000",
      "title": "Job title from the source",
      "company": "Company name"
    }
  ]
}

Common errors: 400, 401, 402, 403, 404, 429, 500, 503. See Errors & rate limits below for handling.

Company detail

GET /v1/companies/{domain}

Canonical identity with current active count and observation bounds. Not paginated. Credit cost: 2. Required scope: companies:read.

bash

Set JOB_ID or COMPANY_DOMAIN from a returned record

curl "https://data.exende.dev/v1/companies/$COMPANY_DOMAIN" -H "Authorization: Bearer $EXENDE_API_KEY"
ParameterType / locationMeaning
domain (required)string / pathCanonical company domain, for example a domain returned on a job record.

200 response: Successful JSON response. A known company with no active jobs returns active_jobs: 0. Unknown company returns 404.

Response fields and full structural example
FieldTypeMeaning
dataCompanySee response schema.
data.idstringSee response schema.
data.canonical_namestringSee response schema.
data.canonical_domainstring | nullSee response schema.
data.website_urlstring | nullSee response schema.
data.active_jobsintegerSee response schema.
data.first_seen_atstring | nullEarliest job observation for the company.
data.last_seen_atstring | nullLatest job observation for the company.

Structural example only. Placeholder names, IDs, dates, and counts illustrate the shape; these are not live catalogue records.

json

Structural example — not live data

{
  "data": {
    "id": "00000000-0000-4000-8000-000000000000",
    "canonical_name": "Canonical name",
    "canonical_domain": null,
    "website_url": null,
    "active_jobs": 0,
    "first_seen_at": null,
    "last_seen_at": null
  }
}

Common errors: 400, 401, 402, 403, 404, 429, 500, 503. See Errors & rate limits below for handling.

Company jobs

GET /v1/companies/{domain}/jobs

Filters are combined with the company in the path. Keyset pagination: default 50, max 100. Ordered by last_seen_at DESC, id DESC. Pass nextCursor as cursor with unchanged filters. Concurrent catalogue updates mean this is not a frozen snapshot. Credit cost: 1 per returned record. Required scope: companies:read.

bash

Set JOB_ID or COMPANY_DOMAIN from a returned record

curl "https://data.exende.dev/v1/companies/$COMPANY_DOMAIN/jobs?limit=5" -H "Authorization: Bearer $EXENDE_API_KEY"
ParameterType / locationMeaning
domain (required)string / pathCanonical company domain or company UUID.
limitinteger / queryPage size. Missing or invalid numeric values fall back to 50; the response never exceeds 100 records. Maximum: 100. Minimum: 1.
qstring / queryTrimmed FTS prefix query across title, department and description. Terms are combined with AND; at least one term must have two letters or numbers. Not a company or city search. Maximum length: 160.
companyIdstring / queryExact canonical company ID. Format: uuid.
domainstring / queryCompany domain normalized by the API. Maximum length: 253.
locationstring / queryCase-insensitive source location substring; SQL LIKE % and _ wildcards retain their current meaning. Maximum length: 100.
countrystring / queryISO alpha-2 country, case-insensitive; normalized to uppercase.
titlestring / queryCase-insensitive job-title substring; SQL LIKE wildcards retain their current meaning. Maximum length: 100.
skillstring / queryNormalized skill slug or recognized alias. Unknown skill returns an empty page. Maximum length: 100.
employmentTypestring / queryNormalized employment type. Values: full_time, part_time, contract, temporary, internship, unknown.
workplaceTypestring / queryNormalized workplace type. Values: remote, hybrid, on_site, unknown.
statusstring / queryCurrent canonical job status. Values: active, closed.
firstSeenAfterstring / queryInclusive lower bound on first_seen_at. Format: date-time.
publishedAfterstring / queryInclusive lower bound on the source posted_at timestamp; missing publication dates do not match. Format: date-time.
cursorstring / queryOpaque nextCursor from the previous response. Preserve all other filters. Maximum length: 500.

200 response: Successful JSON response. A valid but unmatched company domain or filter returns data: [], nextCursor: null.

Response fields and full structural example
FieldTypeMeaning
dataJob[]See response schema.
data[].idstringCanonical source-scoped job identifier.
data[].company_idstringCanonical company identifier.
data[].companystringCanonical company name.
data[].canonical_domainstring | nullCanonical company domain; not an ATS hostname.
data[].titlestringSource job title.
data[].locationstring | nullSource location text; not a normalized city identifier.
data[].country_codestring | nullISO alpha-2 country only when explicit evidence supports normalization; otherwise null.
data[].departmentstring | nullDepartment if provided.
data[].employment_typestringSee response schema.
data[].workplace_typestringSee response schema.
data[].statusstringSee response schema.
data[].source_urlstringPublic listing URL retained for attribution.
data[].first_seen_atstringFirst Exende observation, not necessarily publication.
data[].last_seen_atstringMost recent canonical observation; unchanged visibility is capped at once per UTC day.
nextCursorstring | nullOpaque keyset cursor, or null when there is no further page.

Structural example only. Placeholder names, IDs, dates, and counts illustrate the shape; these are not live catalogue records.

json

Structural example — not live data

{
  "data": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "company_id": "00000000-0000-4000-8000-000000000000",
      "company": "Company name",
      "canonical_domain": null,
      "title": "Job title from the source",
      "location": null,
      "country_code": null,
      "department": null,
      "employment_type": "full_time",
      "workplace_type": "remote",
      "status": "active",
      "source_url": "https://company.example/careers/job",
      "first_seen_at": "2000-01-01T00:00:00.000Z",
      "last_seen_at": "2000-01-01T00:00:00.000Z"
    }
  ],
  "nextCursor": null
}

Common errors: 400, 401, 402, 403, 404, 429, 500, 503. See Errors & rate limits below for handling.

Company hiring

GET /v1/companies/{domain}/hiring

Deterministic role mix, history readiness and a daily timeline. Current summary and historical events have distinct meanings. Not paginated. Credit cost: 5. Required scope: companies:read.

bash

Set JOB_ID or COMPANY_DOMAIN from a returned record

curl "https://data.exende.dev/v1/companies/$COMPANY_DOMAIN/hiring" -H "Authorization: Bearer $EXENDE_API_KEY"
ParameterType / locationMeaning
domain (required)string / pathCanonical company domain.
daysinteger / queryRequested rolling observation window. Missing or invalid numeric values fall back to 30. Maximum: 90. Minimum: 7.

200 response: Successful JSON response. Unknown company returns 404. A known company can return zero counts and null acceleration.

Response fields and full structural example
FieldTypeMeaning
dataCompanyHiringSee response schema.
data.companyCompanyIdentitySee response schema.
data.company.idstringSee response schema.
data.company.canonical_namestringSee response schema.
data.company.canonical_domainstring | nullSee response schema.
data.company.website_urlstring | nullSee response schema.
data.period_daysintegerSee response schema.
data.summaryCompanyHiringSummarySee response schema.
data.summary.active_jobsintegerSee response schema.
data.summary.created_jobsintegerSee response schema.
data.summary.updated_jobsintegerSee response schema.
data.summary.closed_jobsintegerSee response schema.
data.summary.remote_jobsintegerSee response schema.
data.summary.engineering_jobsintegerSee response schema.
data.summary.sales_jobsintegerSee response schema.
data.summary.ai_jobsintegerSee response schema.
data.summary.countries_hiringintegerSee response schema.
data.summary.country_classified_jobsintegerSee response schema.
data.summary.country_unclassified_jobsintegerSee response schema.
data.summary.hiring_velocitynumberRecorded created versions divided by period days. Initial ingestion can contribute.
data.summary.hiring_accelerationnumber | nullRecent creation rate minus preceding interval rate. Null until the history spans the requested window.
data.summary.history_completebooleanThe earliest immutable version reaches the requested window start. This is a readiness gate, not proof of uninterrupted or whole-market coverage.
data.timelineCompanyHiringDay[]See response schema.
data.timeline[].daystringSee response schema.
data.timeline[].created_jobsintegerSee response schema.
data.timeline[].updated_jobsintegerSee response schema.
data.timeline[].closed_jobsintegerSee response schema.
data.timeline[].active_jobsintegerActive jobs reconstructed at the end of the UTC calendar day.

Structural example only. Placeholder names, IDs, dates, and counts illustrate the shape; these are not live catalogue records.

json

Structural example — not live data

{
  "data": {
    "company": {
      "id": "00000000-0000-4000-8000-000000000000",
      "canonical_name": "Canonical name",
      "canonical_domain": null,
      "website_url": null
    },
    "period_days": 7,
    "summary": {
      "active_jobs": 0,
      "created_jobs": 0,
      "updated_jobs": 0,
      "closed_jobs": 0,
      "remote_jobs": 0,
      "engineering_jobs": 0,
      "sales_jobs": 0,
      "ai_jobs": 0,
      "countries_hiring": 0,
      "country_classified_jobs": 0,
      "country_unclassified_jobs": 0,
      "hiring_velocity": 0,
      "hiring_acceleration": null,
      "history_complete": false
    },
    "timeline": [
      {
        "day": "2000-01-01",
        "created_jobs": 0,
        "updated_jobs": 0,
        "closed_jobs": 0,
        "active_jobs": 0
      }
    ]
  }
}

Common errors: 400, 401, 402, 403, 404, 429, 500, 503. See Errors & rate limits below for handling.

Skills

GET /v1/skills

Up to 100 skills with active jobs, ordered by active_jobs DESC, canonical_name ASC. No query parameters supported. Not paginated. Credit cost: 1 per returned record. Required scope: jobs:read.

bash

Example request

curl "https://data.exende.dev/v1/skills" -H "Authorization: Bearer $EXENDE_API_KEY"

No query parameters.

200 response: Successful JSON response. Returns data: [] when there are no matching records.

Response fields and full structural example
FieldTypeMeaning
dataSkillCount[]See response schema.
data[].slugstringSee response schema.
data[].canonical_namestringSee response schema.
data[].categorystringSee response schema.
data[].active_jobsintegerSee response schema.

Structural example only. Placeholder names, IDs, dates, and counts illustrate the shape; these are not live catalogue records.

json

Structural example — not live data

{
  "data": [
    {
      "slug": "skill-slug",
      "canonical_name": "Canonical name",
      "category": "category",
      "active_jobs": 0
    }
  ]
}

Common errors: 400, 401, 402, 403, 404, 429, 500, 503. See Errors & rate limits below for handling.

Overview metrics

GET /v1/metrics/overview

Active counts and daily visibility confirmations in the last 14 calendar days. The legacy failed_runs field is an aggregate, not a freshness guarantee. Not paginated. Credit cost: 2. Required scope: signals:read.

bash

Example request

curl "https://data.exende.dev/v1/metrics/overview" -H "Authorization: Bearer $EXENDE_API_KEY"

No query parameters.

200 response: Successful JSON response. A successful aggregate object costs 2 credits even if all counts are zero.

Response fields and full structural example
FieldTypeMeaning
dataOverviewMetricsSee response schema.
data.active_jobsintegerSee response schema.
data.active_sourcesintegerSee response schema.
data.failed_runsintegerLegacy aggregate currently returned by this authenticated route; not a freshness measure.
data.dailyobject[]See response schema.
data.daily[].daystringSee response schema.
data.daily[].observationsintegerSee response schema.

Structural example only. Placeholder names, IDs, dates, and counts illustrate the shape; these are not live catalogue records.

json

Structural example — not live data

{
  "data": {
    "active_jobs": 0,
    "active_sources": 0,
    "failed_runs": 0,
    "daily": [
      {
        "day": "2000-01-01",
        "observations": 0
      }
    ]
  }
}

Common errors: 400, 401, 402, 403, 404, 429, 500, 503. See Errors & rate limits below for handling.

Hiring metrics

GET /v1/metrics/hiring

Current role mix, version events, readiness-gated acceleration and top-12 groups. Timeline contains days with events; company-hiring timeline instead fills calendar days. Not paginated. Credit cost: 5. Required scope: signals:read.

bash

Example request

curl "https://data.exende.dev/v1/metrics/hiring" -H "Authorization: Bearer $EXENDE_API_KEY"
ParameterType / locationMeaning
daysinteger / queryRequested rolling observation window. Missing or invalid numeric values fall back to 30. Maximum: 90. Minimum: 7.

200 response: Successful JSON response. A successful aggregate object costs 5 credits even if counts are zero or grouped arrays are empty.

Response fields and full structural example
FieldTypeMeaning
dataHiringMetricsSee response schema.
data.period_daysintegerSee response schema.
data.summaryHiringSummarySee response schema.
data.summary.active_jobsintegerSee response schema.
data.summary.new_jobsintegerSee response schema.
data.summary.updated_jobsintegerSee response schema.
data.summary.closed_jobsintegerSee response schema.
data.summary.remote_jobsintegerSee response schema.
data.summary.engineering_jobsintegerSee response schema.
data.summary.sales_jobsintegerSee response schema.
data.summary.ai_jobsintegerSee response schema.
data.summary.countries_hiringintegerSee response schema.
data.summary.country_classified_jobsintegerSee response schema.
data.summary.country_unclassified_jobsintegerSee response schema.
data.summary.hiring_velocitynumberRecorded created versions divided by period days. Initial ingestion can contribute.
data.summary.hiring_accelerationnumber | nullRecent creation rate minus preceding interval rate. Null until the history spans the requested window.
data.summary.history_completebooleanThe earliest immutable version reaches the requested window start. This is a readiness gate, not proof of uninterrupted or whole-market coverage.
data.timelineHiringDay[]See response schema.
data.timeline[].daystringSee response schema.
data.timeline[].created_jobsintegerSee response schema.
data.timeline[].updated_jobsintegerSee response schema.
data.timeline[].closed_jobsintegerSee response schema.
data.companiesHiringCompany[]See response schema.
data.companies[].company_idstringSee response schema.
data.companies[].companystringSee response schema.
data.companies[].canonical_domainstring | nullSee response schema.
data.companies[].active_jobsintegerSee response schema.
data.companies[].new_jobsintegerSee response schema.
data.companies[].updated_jobsintegerSee response schema.
data.companies[].closed_jobsintegerSee response schema.
data.companies[].remote_jobsintegerSee response schema.
data.companies[].engineering_jobsintegerSee response schema.
data.companies[].sales_jobsintegerSee response schema.
data.companies[].ai_jobsintegerSee response schema.
data.companies[].countries_hiringintegerSee response schema.
data.companies[].hiring_velocitynumberRecorded created versions divided by period days. Initial ingestion can contribute.
data.companies[].hiring_accelerationnumber | nullRecent creation rate minus preceding interval rate. Null until the history spans the requested window.
data.companies[].history_completebooleanThe earliest immutable version reaches the requested window start. This is a readiness gate, not proof of uninterrupted or whole-market coverage.
data.companies[].new_jobs_per_daynumberAlias of hiring_velocity.
data.locationsobject[]See response schema.
data.locations[].locationstringSee response schema.
data.locations[].active_jobsintegerSee response schema.
data.countriesobject[]See response schema.
data.countries[].country_codestringSee response schema.
data.countries[].active_jobsintegerSee response schema.

Structural example only. Placeholder names, IDs, dates, and counts illustrate the shape; these are not live catalogue records.

json

Structural example — not live data

{
  "data": {
    "period_days": 7,
    "summary": {
      "active_jobs": 0,
      "new_jobs": 0,
      "updated_jobs": 0,
      "closed_jobs": 0,
      "remote_jobs": 0,
      "engineering_jobs": 0,
      "sales_jobs": 0,
      "ai_jobs": 0,
      "countries_hiring": 0,
      "country_classified_jobs": 0,
      "country_unclassified_jobs": 0,
      "hiring_velocity": 0,
      "hiring_acceleration": null,
      "history_complete": false
    },
    "timeline": [
      {
        "day": "2000-01-01",
        "created_jobs": 0,
        "updated_jobs": 0,
        "closed_jobs": 0
      }
    ],
    "companies": [
      {
        "company_id": "00000000-0000-4000-8000-000000000000",
        "company": "Company name",
        "canonical_domain": null,
        "active_jobs": 0,
        "new_jobs": 0,
        "updated_jobs": 0,
        "closed_jobs": 0,
        "remote_jobs": 0,
        "engineering_jobs": 0,
        "sales_jobs": 0,
        "ai_jobs": 0,
        "countries_hiring": 0,
        "hiring_velocity": 0,
        "hiring_acceleration": null,
        "history_complete": false,
        "new_jobs_per_day": 0
      }
    ],
    "locations": [
      {
        "location": "Source location text",
        "active_jobs": 0
      }
    ],
    "countries": [
      {
        "country_code": "GB",
        "active_jobs": 0
      }
    ]
  }
}

Common errors: 400, 401, 402, 403, 404, 429, 500, 503. See Errors & rate limits below for handling.

Public overview

GET /v1/public/overview

Aggregate-only public proof. No key and no credits. No listing, customer, or source-health records. Last updated is the latest canonical job observation.

bash

Example request

curl "https://data.exende.dev/v1/public/overview"

No query parameters.

200 response: Current aggregate overview.

Response fields and full structural example
FieldTypeMeaning
dataPublicOverviewSee response schema.
data.active_jobsintegerSee response schema.
data.active_companiesintegerSee response schema.
data.active_sourcesintegerSee response schema.
data.last_updated_atstring | nullMost recent canonical job observation; not the timestamp when every source was checked.

Structural example only. Placeholder names, IDs, dates, and counts illustrate the shape; these are not live catalogue records.

json

Structural example — not live data

{
  "data": {
    "active_jobs": 0,
    "active_companies": 0,
    "active_sources": 0,
    "last_updated_at": null
  }
}

Common errors: 429, 503. See Errors & rate limits below for handling.

Pagination & request limits

Jobs list, search, and company jobs use keyset pagination. The default is 50 records and the maximum is 100. Keep filters unchanged and send the returned nextCursor as cursor. Stop when it is null. Records are ordered by last_seen_at DESC, id DESC; ongoing catalogue changes mean pages are not a frozen snapshot.

Job history accepts limit (default 50, max 100) with no cursor. Skills and the history feed return at most 100 records and do not accept pagination parameters. The hiring window is 7–90 days, default 30; grouped companies, locations, and countries are capped at 12.

The current backend falls back to 50 for an invalid numeric limit and 30 for invalid numeric days. Other invalid filters and malformed cursors return 400. Unknown query parameters on customer routes are ignored, so use the parameters documented for each operation. The strict public preview instead rejects every parameter other than q.

Credit metering

Credits are not requests. Per-record operations cost one credit per returned record: 5 results = 5 credits, 50 results = 50 credits. Empty lists and failed requests cost zero. Successful fixed-cost aggregate objects still incur their fixed cost when their numeric counts are zero.

GET routeScopeCredits
/v1/jobs/searchjobs:read1 per returned record
/v1/jobsjobs:read1 per returned record
/v1/jobs/{id}jobs:read1
/v1/jobs/{id}/historyjobs:read1 per returned record
/v1/historyjobs:read1 per returned record
/v1/companies/{domain}companies:read2
/v1/companies/{domain}/jobscompanies:read1 per returned record
/v1/companies/{domain}/hiringcompanies:read5
/v1/skillsjobs:read1 per returned record
/v1/metrics/overviewsignals:read2
/v1/metrics/hiringsignals:read5

The authoritative credit debit must succeed before customer data is returned. Insufficient balance returns 402 with insufficient_credits. This is a Jobs Data credit error, not an x402 payment challenge.

Errors & rate limits

json

Example insufficient-credit error

{
  "error": {
    "code": "insufficient_credits",
    "message": "This account does not have enough credits for this operation."
  }
}
HTTPCodeAction
400invalid_requestCheck parameter values and cursor. Validation responses can include error.details.
401unauthorizedCheck the key, expiry, revocation, and organization status.
402insufficient_creditsUse a smaller result limit or add a suitable plan.
403insufficient_scopeCreate a key with the route’s required scope.
404not_foundCheck the route or record identifier. Empty lists use 200 instead.
429rate_limitedRespect Retry-After: 60. Apply backoff and avoid retry bursts.
503service_unavailableRetry with bounded backoff; a required service is unavailable.

Production has a pre-authentication abuse limiter (240/minute per connection IP) and an authenticated limiter (120/minute per key). Cloudflare limits are location-local and eventually consistent. Shared-IP traffic can share a limit. Store the X-Request-ID response header for support. Unexpected upstream failures may return a non-JSON body; handle HTTP status before parsing.

Freshness, metrics & caching

Public overview cache: max-age=60, s-maxage=300, stale-while-revalidate=600. The latest observation timestamp does not mean every source was checked at that time. Authenticated metrics can reuse a server-side five-minute calculation cache; authentication and metering still occur on each request, and client responses use private, no-store.

Hiring velocity is recorded created versions divided by days. Acceleration compares recent and preceding creation rates and is null until the earliest immutable history spans the requested window. history_complete is that readiness gate; it does not prove uninterrupted or whole-market observation. Company timelines include zero-event days; the overall hiring timeline includes days with events. Country nulls remain unclassified. See Coverage & methodology.