Job

JobEmployment DataData ProvenanceJob BoardAI Agents

Properties

Name Type Description
agency string Who the employer of record is: `direct` — the advertiser; `agency` — a temporary-work agency (the engagement is then employee, per EU and national law, and contract_category describes the contract wit
apply_url string
attribution string Present only on roles an employer pushed to us directly: show the listing with these words. Absent on every other row.
canonical_url string Present only on roles an employer pushed to us directly: the role's own page, where people apply and which search engines should index. Link to it, not to our page. Absent on every other row.
category string
category_confidence numbernull Classifier confidence in `category`. Below 0.6 no category is published at all, so this is always null or >= 0.6.
city string
closed_at stringnull JOA records as the closure date the day its daily verification confirmed that an advert had gone. Before late September 2026 that verification ran in large catch-up batches, so closure dates recorded
closed_reason stringnull
company string
company_logo string
company_slug string
contract_category array The contract kind the posting establishes (db/232): `permanent` is returned only when the posting STATES an open-ended contract (CDI, unbefristet, tempo indeterminato, indefinido, ...); `fixed_term` e
contract_category_confidence number Present ONLY when field_sources.contract_category is `inferred`: the measured precision of the rule, 0..1.
country string
country_confidence string Present ONLY when WE filled the country (`field_sources.country` is `inferred` after a gazetteer-plus-model agreement, or `llm_inferred`). `high`: gazetteer and a language model agree. `low`: a langua
description string The full advert text. Present ONLY when the request set include_description=true, and only on the 93.9% of rows that have one (1,953,989 live rows). Personal email addresses are removed: an email addr
description_length number Size of description in bytes. Always present: 0 means the row carries no advert text, exactly as has_description:false does. Computed with octet_length, which reads the length out of the TOAST header
description_truncated boolean True while the stored advert text is known to be cut short at its source: harvested under an old 4,000-character limit and not yet re-fetched, or longer than the 100,000-character safety ceiling. JOA
employer_website string The employer's own site as THIS posting states it — hiringOrganization.url or sameAs, or the ATS's own field (db/144). A different claim from `website` on /v1/companies, which is verified against the
employment_type string One of Full-time, Part-time, Contract, Temporary, Internship. A row that already carried one keeps it; otherwise this is the employmentType the page itself declares (db/144), normalised to the same fi
engagement array The employment relationship the posting establishes (db/232, plan v2.1): `employee` — an employment contract with some employer of record running payroll (the advertiser, a temp agency or an umbrella
engagement_confidence number Present ONLY when field_sources.engagement is `inferred`: the measured precision of the rule that established the value, 0..1. A published value carries no confidence — it is the source's words.
expires_at string When the vacancy stops being valid, from either source: the timestamp the radar PROVED it dead (upstream_expired_at), or the date the posting itself states (db/144, schema.org validThrough). A row tha
field_sources object
first_seen_at string When the vacancy first entered this ledger. Populated on 100% of rows, unlike posted_at which the source often omits — so this is the field to sort or backfill by when you need every row to have a dat
has_description boolean
id string
language stringnull ISO 639-1 code of the advert text, detected automatically from the description (or the title when there is no description). null when the text is mixed, too short or the detection is not confident. Me
language_basis stringnull What evidence the detection used: "text" (the description), "title" (the advert text was missing or too short), "title+country" (the title is English, the advert text is missing or too short, and the
language_confidence numbernull The detector's own confidence (0.00-1.00) for the published language; null whenever language is null. A label is stored only at >=0.80 with a description and >=0.90 title-only; labels with language_ba
last_verified_at string When we last confirmed this vacancy still exists at its source.
location string
posted_at string
posted_at_precision string Present only when the source publishes a calendar date and no time (Workday). posted_at then carries that date at a fixed 12:00:00Z, so the day is the same in every timezone from UTC-11 to UTC+11; the
provider_type string The provider-level class, in the original vocabulary. Kept so a query written against it still resolves.
region string State/province code where one could be established — US two-letter codes today, absent on every non-US row and on cities that resolve to more than one state. db/078 resolves it from the source's decla
relevance object Present only on rows served with `sort=relevance`. `score` is an ordering-only integer (comparable within one response, never across queries); `tier` is the match class: exact, synonym, abbreviation o
remote string
remote_inferred boolean **Always present.** True when we derived `remote` rather than read it. It used to be omitted when false, which made a stated value and an absent one identical on the wire.
salary_currency string ISO code, as the source states it. Same sources as salary_min.
salary_max number The top of the range, same sources as salary_min. Absent when the source states a single figure or none at all.
salary_min number The source's own figure. A row that already carried one keeps it; otherwise this is the baseSalary the page declares, or the ATS's salaryRange (db/144). Absent — not null — when there is no figure, wh
salary_min_annual_eur number salary_min converted to an annual EUR figure, so a row quoting USD/hour and one quoting GBP/year are comparable. NULL when the period or the currency is unrecognised — never guessed, because reading a
salary_period string hour, day, week, month or year, as the source states it. Same sources as salary_min.
salary_source string Where the figure came from. `structured` is a field the source itself published. `parsed_description` is a real figure quoted in the advert text that WE read out and normalised — we chose the number a
seniority string
slug string
source string The provider id, e.g. greenhouse, company_site, workday. The full published list is /public/providers; eures, arbeitsagentur and france_travail were named here until 2026-08-15, when they became disco
source_requisition_id string The identifier the SOURCE uses for this vacancy — an ATS job id, a Workday requisition id, a schema.org identifier — so a support ticket or a buyer's own ATS lookup can quote it. Absent on every row t
source_type string Per-row provenance where the source has been classified per row, otherwise the provider's class. Snapshot of the distribution on 5 Aug 2026: ats 1,063,129, career_site 672,526, public_agency 320,727;
status string
title string
upstream_expired_at string When the radar proved the vacancy dead, as opposed to when we removed it. Still the radar's column and nothing else; `expires_at` is the key that also carries the posting's own validThrough date.
working_schedule string The stated schedule. `flexible` covers adverts offering either schedule. OMITTED when not established (`?working_schedule=unknown`). Distinct from, and independent of, the frozen employment_type field
working_schedule_confidence number Present ONLY when field_sources.working_schedule is `inferred`.
View JSON Schema on GitHub

JSON Schema

jobopportunitiesapi-org-job-schema.json Raw ↑
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://raw.githubusercontent.com/api-evangelist/jobopportunitiesapi-org/main/json-schema/jobopportunitiesapi-org-job-schema.json",
  "title": "Job",
  "x-generated": "2026-10-20",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/jobopportunitiesapi-org-openapi.json#/components/schemas/Job",
  "properties": {
    "agency": {
      "description": "Who the employer of record is: `direct` — the advertiser; `agency`\n— a temporary-work agency (the engagement is then employee, per EU\nand national law, and contract_category describes the contract with\nthe agency — permanent agency contracts exist); `umbrella` — an\numbrella / portage company. OMITTED when not established\n(`?agency=unknown`). No confidence key by design.\n",
      "enum": [
        "direct",
        "agency",
        "umbrella"
      ],
      "type": "string"
    },
    "apply_url": {
      "type": "string"
    },
    "attribution": {
      "description": "Present only on roles an employer pushed to us directly: show the listing with these words. Absent on every other row.\n",
      "type": "string"
    },
    "canonical_url": {
      "description": "Present only on roles an employer pushed to us directly: the role's own page, where people apply and which search engines should index. Link to it, not to our page. Absent on every other row.\n",
      "type": "string"
    },
    "category": {
      "type": "string"
    },
    "category_confidence": {
      "description": "Classifier confidence in `category`. Below 0.6 no category is published at all, so this is always null or >= 0.6.",
      "type": [
        "number",
        "null"
      ]
    },
    "city": {
      "type": "string"
    },
    "closed_at": {
      "description": "JOA records as the closure date the day its daily verification confirmed that an advert had gone. Before late September 2026 that verification ran in large catch-up batches, so closure dates recorded before then show when JOA confirmed a closure, not when the employer closed the role; daily verification applies from late September 2026.",
      "type": [
        "string",
        "null"
      ]
    },
    "closed_reason": {
      "enum": [
        "expired_upstream",
        "not_seen",
        "employer_closed",
        "deadline_passed",
        null
      ],
      "type": [
        "string",
        "null"
      ]
    },
    "company": {
      "type": "string"
    },
    "company_logo": {
      "type": "string"
    },
    "company_slug": {
      "type": "string"
    },
    "contract_category": {
      "description": "The contract kind the posting establishes (db/232): `permanent` is\nreturned only when the posting STATES an open-ended contract (CDI,\nunbefristet, tempo indeterminato, indefinido, ...); `fixed_term`\nends on a date or fixed period; `project_based` ends on completion\nof a defined project; `seasonal` when the source says seasonal;\n`apprenticeship` is a statutory work-and-study contract;\n`internship` includes traineeships; `other` is a STATED form none\nof these represent (zero-hours, casual, per diem, Minijob,\nWerkstudent, ...). An ARRAY (dual-option adverts), main option\nfirst. OMITTED when the posting states none — most postings;\nfilterable as `?contract_category=unknown`. For vendors the\ncategory only describes the term of the engagement.\n",
      "items": {
        "enum": [
          "permanent",
          "fixed_term",
          "project_based",
          "seasonal",
          "apprenticeship",
          "internship",
          "other"
        ],
        "type": "string"
      },
      "type": "array"
    },
    "contract_category_confidence": {
      "description": "Present ONLY when field_sources.contract_category is `inferred`:\nthe measured precision of the rule, 0..1.\n",
      "type": "number"
    },
    "country": {
      "type": "string"
    },
    "country_confidence": {
      "description": "Present ONLY when WE filled the country (`field_sources.country` is `inferred` after\na gazetteer-plus-model agreement, or `llm_inferred`). `high`: gazetteer and a language\nmodel agree. `low`: a language model alone read the location text (`llm_inferred`;\nmeasured precision about 95%, see field_sources.country). Omitted on every other row.\n",
      "enum": [
        "high",
        "low"
      ],
      "type": "string"
    },
    "description": {
      "description": "The full advert text. Present ONLY when the request set include_description=true, and only on the 93.9% of rows that have one (1,953,989 live rows).\n\nPersonal email addresses are removed: an email address that identifies one person (a name-based address such as jane.doe@...) is replaced with `[personal contact removed]`. Company mailboxes stay in the text: role mailboxes (jobs@, careers@, hr@, info@ and their equivalents in other languages). Phone numbers are not changed on this path. A role an employer listed with us directly keeps its text exactly as the employer published it. Nothing else in the text, `apply_url` and every other field are unchanged. `description_length` counts the stored text, before this replacement. The bulk `GET /v1/export` removes every email address and phone number instead.\n",
      "type": "string"
    },
    "description_length": {
      "description": "Size of description in bytes. Always present: 0 means the row carries\nno advert text, exactly as has_description:false does. Computed with\noctet_length, which reads the length out of the TOAST header without\nfetching the text — the text itself still needs\ninclude_description=true.\n",
      "type": "number"
    },
    "description_truncated": {
      "description": "True while the stored advert text is known to be cut short at its\nsource: harvested under an old 4,000-character limit and not yet\nre-fetched, or longer than the 100,000-character safety ceiling.\nJOA does not shorten the text it serves; this flag says the\nledger's own copy is incomplete. Always present.\n",
      "type": "boolean"
    },
    "employer_website": {
      "description": "The employer's own site as THIS posting states it —\nhiringOrganization.url or sameAs, or the ATS's own field (db/144).\nA different claim from `website` on /v1/companies, which is verified\nagainst the domain's own page.\n",
      "type": "string"
    },
    "employment_type": {
      "description": "One of Full-time, Part-time, Contract, Temporary, Internship. A row\nthat already carried one keeps it; otherwise this is the\nemploymentType the page itself declares (db/144), normalised to the\nsame five values (db/220; a declared value that maps to none of them\nis not shown). Either way the value is the source's own, which is\nwhy field_sources.employment_type is published whenever the key is\npresent, and ?employment_type= filters on exactly this value.\nUNCHANGED and frozen since the contract fields (engagement,\ncontract_category, agency, working_schedule) shipped: existing\nintegrations keep byte-identical behaviour. Rough old-to-new\nreading: Contract/Temporary mostly correspond to engagement=vendor\nor contract_category=fixed_term, Internship to\ncontract_category=internship — but the new fields come from what\nthe posting STATES, so they are populated independently, not\ntranslated from this one.\n",
      "type": "string"
    },
    "engagement": {
      "description": "The employment relationship the posting establishes (db/232, plan\nv2.1): `employee` — an employment contract with some employer of\nrecord running payroll (the advertiser, a temp agency or an\numbrella company); `vendor` — a commercial or civil-law service\nrelationship (freelance, B2B, 1099, C2C, Brazilian PJ: the person\ninvoices); `other` — a stated hybrid national form that everyday\nunderstanding puts on neither side (Polish umowa zlecenie /\numowa o dzieło, Italian co.co.co., Spanish TRADE, umbrella/portage\narrangements, statutory non-employment internships such as the\nFrench stage, volunteering). The raw national term is retained\ninternally. An ARRAY because some adverts offer two arrangements\n(\"CLT ou PJ\", \"UoP lub B2B\"): main option first, at most 3 values,\nusually exactly 1. OMITTED when the posting does not establish it —\nwhich is most postings; absence is filterable as\n`?engagement=unknown` and never means \"employee by default\".\n",
      "items": {
        "enum": [
          "employee",
          "vendor",
          "other"
        ],
        "type": "string"
      },
      "type": "array"
    },
    "engagement_confidence": {
      "description": "Present ONLY when field_sources.engagement is `inferred`: the\nmeasured precision of the rule that established the value, 0..1. A\npublished value carries no confidence — it is the source's words.\n",
      "type": "number"
    },
    "expires_at": {
      "description": "When the vacancy stops being valid, from either source: the timestamp\nthe radar PROVED it dead (upstream_expired_at), or the date the\nposting itself states (db/144, schema.org validThrough). A row that\nalready carried the first keeps it. Absent while the vacancy is live.\nSame shape as every other timestamp here — a date from the posting\nrenders as midnight of that date.\n",
      "type": "string"
    },
    "field_sources": {
      "$ref": "#/$defs/FieldSources"
    },
    "first_seen_at": {
      "description": "When the vacancy first entered this ledger. Populated on 100% of rows, unlike posted_at which the source often omits — so this is the field to sort or backfill by when you need every row to have a date.\n",
      "type": "string"
    },
    "has_description": {
      "type": "boolean"
    },
    "id": {
      "format": "uuid",
      "type": "string"
    },
    "language": {
      "description": "ISO 639-1 code of the advert text, detected automatically from the description (or the title when there is no description). null when the text is mixed, too short or the detection is not confident. Measured precision >= 99% on a random sample; null does not mean English.\n",
      "type": [
        "string",
        "null"
      ]
    },
    "language_basis": {
      "description": "What evidence the detection used: \"text\" (the description), \"title\" (the advert text was missing or too short), \"title+country\" (the title is English, the advert text is missing or too short, and the job is in the US, GB, CA, AU, IE, NZ, SG or IN; measured precision 99.4% against description-based labels on 28,797 rows) or \"none\". null whenever language is null. field_sources.language reports \"detected\" when a value is published - our detection of the advert text, never the source's declaration.\n",
      "type": [
        "string",
        "null"
      ]
    },
    "language_confidence": {
      "description": "The detector's own confidence (0.00-1.00) for the published language; null whenever language is null. A label is stored only at >=0.80 with a description and >=0.90 title-only; labels with language_basis \"title+country\" carry a score between 0.50 and 0.85 (the title score, capped at 0.85), so they are always below the other bases.\n",
      "type": [
        "number",
        "null"
      ]
    },
    "last_verified_at": {
      "description": "When we last confirmed this vacancy still exists at its source.",
      "type": "string"
    },
    "location": {
      "type": "string"
    },
    "posted_at": {
      "type": "string"
    },
    "posted_at_precision": {
      "description": "Present only when the source publishes a calendar date and no time (Workday). posted_at then carries that date at a fixed 12:00:00Z, so the day is the same in every timezone from UTC-11 to UTC+11; the time part carries no information. Omitted when posted_at is a full timestamp.\n",
      "enum": [
        "date"
      ],
      "type": "string"
    },
    "provider_type": {
      "description": "The provider-level class, in the original vocabulary. Kept so a query written against it still resolves.",
      "enum": [
        "employer_ats",
        "government",
        "direct",
        "aggregator"
      ],
      "type": "string"
    },
    "region": {
      "description": "State/province code where one could be established — US two-letter\ncodes today, absent on every non-US row and on cities that resolve to\nmore than one state. db/078 resolves it from the source's declared\nregion first, then a state named in the free-text location, then an\nunambiguous city; db/144 adds the region the page itself declares\n(schema.org addressRegion) where the listing column has none.\nfield_sources.region reports published whenever the row carries one\nand does not separate those derivations, so read it as \"we\nestablished a region\", not as a quotation.\n",
      "type": "string"
    },
    "relevance": {
      "description": "Present only on rows served with `sort=relevance`. `score` is an\nordering-only integer (comparable within one response, never\nacross queries); `tier` is the match class: exact, synonym,\nabbreviation or typo.\n",
      "properties": {
        "score": {
          "type": "integer"
        },
        "tier": {
          "enum": [
            "exact",
            "synonym",
            "abbreviation",
            "typo"
          ],
          "type": "string"
        }
      },
      "type": "object"
    },
    "remote": {
      "enum": [
        "remote",
        "hybrid",
        "on_site"
      ],
      "type": "string"
    },
    "remote_inferred": {
      "description": "**Always present.** True when we derived `remote` rather than read\nit. It used to be omitted when false, which made a stated value and\nan absent one identical on the wire.\n",
      "type": "boolean"
    },
    "salary_currency": {
      "description": "ISO code, as the source states it. Same sources as salary_min.\n",
      "type": "string"
    },
    "salary_max": {
      "description": "The top of the range, same sources as salary_min. Absent when the\nsource states a single figure or none at all.\n",
      "type": "number"
    },
    "salary_min": {
      "description": "The source's own figure. A row that already carried one keeps it;\notherwise this is the baseSalary the page declares, or the ATS's\nsalaryRange (db/144). Absent — not null — when there is no figure,\nwhich is the case for most employers.\n",
      "type": "number"
    },
    "salary_min_annual_eur": {
      "description": "salary_min converted to an annual EUR figure, so a row quoting USD/hour and one quoting GBP/year are comparable. NULL when the period or the currency is unrecognised — never guessed, because reading an hourly rate as a salary is wrong by a factor of 2080. Converted with the indicative rates in /v1/meta/freshness, not a settlement rate.\n",
      "type": "number"
    },
    "salary_period": {
      "description": "hour, day, week, month or year, as the source states it. Same sources\nas salary_min.\n",
      "type": "string"
    },
    "salary_source": {
      "description": "Where the figure came from. `structured` is a field the source itself published. `parsed_description` is a real figure quoted in the advert text that WE read out and normalised — we chose the number and the period, so `field_sources.salary` reports it as `inferred`, never `published`.\n\nThis marker describes the LISTING row only. A figure that arrived\nfrom the extracted side table (db/144) is the source's own baseSalary\nor salaryRange field, and `field_sources.salary` reports it as\n`published` while salary_source itself is absent.\n",
      "enum": [
        "structured",
        "parsed_description"
      ],
      "type": "string"
    },
    "seniority": {
      "type": "string"
    },
    "slug": {
      "type": "string"
    },
    "source": {
      "description": "The provider id, e.g. greenhouse, company_site, workday. The full published list is /public/providers; eures, arbeitsagentur and france_travail were named here until 2026-08-15, when they became discovery-only and left that list.",
      "type": "string"
    },
    "source_requisition_id": {
      "description": "The identifier the SOURCE uses for this vacancy — an ATS job id, a\nWorkday requisition id, a schema.org identifier — so a support ticket\nor a buyer's own ATS lookup can quote it. Absent on every row the\nverify sweep has not extracted yet (db/144); joa.listings has no\ncolumn for it, which is why the payload carried it for 0% of rows\nbefore.\n",
      "type": "string"
    },
    "source_type": {
      "description": "Per-row provenance where the source has been classified per row,\notherwise the provider's class. Snapshot of the distribution on\n5 Aug 2026: ats 1,063,129, career_site 672,526, public_agency 320,727;\nthe current split by source class is in /public/coverage (sources).\n\nRESERVED VALUES: `aggregator` and `agency` are accepted by the filter\nbut currently match no rows. Every aggregator source is marked\nnon-redistributable, so that inventory never enters the ledger at\nall, and no provider is classified `agency` yet. Filtering on either\nreturns an empty page — that is the data, not a fault.\n",
      "enum": [
        "ats",
        "career_site",
        "public_agency",
        "aggregator",
        "agency",
        "unknown"
      ],
      "type": "string"
    },
    "status": {
      "enum": [
        "live",
        "closed"
      ],
      "type": "string"
    },
    "title": {
      "type": "string"
    },
    "upstream_expired_at": {
      "description": "When the radar proved the vacancy dead, as opposed to when we removed it. Still the radar's column and nothing else; `expires_at` is the key that also carries the posting's own validThrough date.",
      "type": "string"
    },
    "working_schedule": {
      "description": "The stated schedule. `flexible` covers adverts offering either\nschedule. OMITTED when not established\n(`?working_schedule=unknown`). Distinct from, and independent of,\nthe frozen employment_type field above.\n",
      "enum": [
        "full_time",
        "part_time",
        "flexible"
      ],
      "type": "string"
    },
    "working_schedule_confidence": {
      "description": "Present ONLY when field_sources.working_schedule is `inferred`.\n",
      "type": "number"
    }
  },
  "required": [
    "id",
    "slug",
    "title",
    "company",
    "company_slug",
    "remote_inferred",
    "last_verified_at",
    "status",
    "source",
    "source_type",
    "provider_type",
    "has_description",
    "field_sources"
  ],
  "type": "object",
  "$defs": {
    "FieldSources": {
      "description": "Per-field provenance. `published` means the value was carried by the\nsource. `inferred` means WE produced it — it may be right, it is not a\nquotation. `absent` means no value, which is a different failure from a\nguess and is reported as such.\n\n`category` and `seniority` are ALWAYS `inferred`: both are read off the\njob title by a classifier, never from a field the employer filled in.\n\n`salary` is `published` when the source stated it in a field\n(`salary_source: structured`), and `inferred` when we read it out of the\nadvert text (`salary_source: parsed_description`). It is never an\nestimate: the projection refuses to emit AI salary predictions\nat all, so a modelled figure cannot reach this API by any route.\n\n`region`, `expires_at`, `description_length`, `source_requisition_id`,\n`employer_website`, `salary_max`, `salary_currency` and `salary_period`\nwere added with db/144, which moved the fields parsed out of the source's\nown page into a side table (`joa.listing_extract`). The payload coalesces\nthe listing column with the extracted one, preferring whatever the row\nalready carried, and these keys say whether a value is there: a value\nthat came from the source is `published`, a missing one is `absent`.\n\nEvery key on this object is ALWAYS present on every job — the vocabulary\nis the value, not the presence — so a client can bind the whole shape.\n",
      "properties": {
        "agency": {
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "category": {
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "contract_category": {
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "country": {
          "description": "How the job's `country` was obtained. `published`: the source's own location\ncarried it. `inferred`: a rule of ours (gazetteer, state code), or the gazetteer\nand a language model agreeing (`country_confidence: \"high\"`, measured precision\n99.5%). `llm_inferred`: LOW TRUST. A language model read the location text and\nnamed the country with no second signal; `country_confidence` is `\"low\"` and the\nmeasured precision of this class is about 95% (hand-checked sample of 120 rows;\n98% on an independent model check of 400 rows), against about 99% for the other\nclasses. Filter on `field_sources.country` in your own code to keep only the\nhigher-trust values. `absent`: the job has no country.\n",
          "enum": [
            "published",
            "inferred",
            "llm_inferred",
            "absent"
          ],
          "type": "string"
        },
        "description": {
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "description_length": {
          "description": "Computed from the same octet_length as description, so the two cannot\ndisagree: published when the row carries text, absent when it does\nnot.\n",
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "employer_website": {
          "description": "Provenance of employer_website. Like source_requisition_id it only\never comes from the page the sweep parsed, so it is published or\nabsent, never inferred.\n",
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "employment_type": {
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "engagement": {
          "description": "Provenance of the engagement array (db/232): `published` when the\nsource's own field or page stated the relationship, `inferred` when\nan audited deterministic rule of ours established it (the payload\nthen also carries engagement_confidence), `absent` when the posting\ndoes not establish it — the value key is then omitted and\n`?engagement=unknown` selects the row. Never inferred from silence.\n",
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "expires_at": {
          "description": "Provenance of expires_at: published when the row carries one, from\neither source — see the Job schema — and absent while the vacancy is\nlive.\n",
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "language": {
          "description": "\"detected\" when the row publishes a language - our detector's reading of the advert text, not the source's declaration - and \"absent\" while the row's language is null (the key was omitted then until 17 Oct 2026). It never reads published or inferred.\n",
          "type": "string"
        },
        "location": {
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "posted_at": {
          "description": "published when the source stated the date or timestamp. Workday publishes a date only (see posted_at_precision): its rows read inferred until the posting's own start date has been read from Workday (on the next re-verification, about daily), because the list API only says \"Posted N Days Ago\" and we derive the date from that.\n",
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "region": {
          "description": "published when the row carries a region, absent when it does not. It\ndoes NOT separate the region the source declared from the ones db/078\nderives from the location text or the city gazetteer, and it does not\nseparate the listing column from the value extracted off the page\n(db/144) — read it as \"we established a region\", not as a quotation.\n",
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "remote": {
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "salary": {
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "salary_currency": {
          "description": "Provenance of salary_currency; the same verdict as salary.",
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "salary_max": {
          "description": "Provenance of salary_max, and always the same verdict as salary:\npublished only when the source stated the figure in a field of its\nown, inferred when we read it out of the advert text, absent when\nthere is no figure.\n",
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "salary_period": {
          "description": "Provenance of salary_period; the same verdict as salary.",
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "seniority": {
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "source_requisition_id": {
          "description": "Provenance of source_requisition_id. The value only ever comes from\nthe page the verify sweep parsed (db/144), so this is published or\nabsent, never inferred.\n",
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "source_type": {
          "description": "`inferred` when the value is the provider's default class;\n`published` when the row itself was classified upstream.\n\nTODAY THIS IS ALWAYS `inferred`: the row-level column is null on\nevery live listing, so the provider's class is the only path taken.\n`published` is reserved for when per-row classification lands. Do not\nbuild a filter that expects it to appear.\n",
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        },
        "working_schedule": {
          "enum": [
            "published",
            "inferred",
            "absent"
          ],
          "type": "string"
        }
      },
      "required": [
        "remote",
        "employment_type",
        "engagement",
        "contract_category",
        "agency",
        "working_schedule",
        "category",
        "seniority",
        "salary",
        "salary_max",
        "salary_currency",
        "salary_period",
        "location",
        "country",
        "region",
        "posted_at",
        "description",
        "description_length",
        "source_type",
        "expires_at",
        "source_requisition_id",
        "employer_website"
      ],
      "type": "object"
    }
  }
}

Work with this as data

Every JSON Schema here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for schemas

4 MCP tools reach this
  • find_json_schemasBrowse and filter every JSON Schema in the catalog.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This JSON Schema
curl "https://apis.io/api/v1/json-schemas/jobopportunitiesapi-org-job"
All schemas
curl "https://apis.io/api/v1/json-schemas?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.