# Applydra Job Data API Applydra exposes an anonymous, read-only corpus of current canonical job listings. No API key or account is required. Requests are rate limited per client IP. Human documentation: https://applydra.io/developers/api OpenAPI 3.1: https://applydra.io/api/v1/openapi.json Service directory: https://applydra.io/api/v1 Terms: https://applydra.io/developers/api#terms Acceptable use: https://applydra.io/developers/api#acceptable-use Data license: https://applydra.io/developers/api#data-license Privacy: https://applydra.io/developers/api#privacy Retention: https://applydra.io/developers/api#retention Contact: https://applydra.io/developers/api#contact Security: https://applydra.io/developers/api#security Default shortlist: https://applydra.io/api/v1/jobs?limit=100 The default query applies relevance=tech, remote=remote, geo=us, actionable=1, and observedWithin=7. relevance=tech covers technical roles plus analytical desk roles such as business, financial, and supply-chain analysts. Full current canonical corpus: https://applydra.io/api/v1/jobs?source=all&relevance=all&remote=all&geo=all&actionable=all&observedWithin=all The source filter is optional. source=all is exactly equivalent to omitting source and is reported as null in stats meta.filters. Fields and clocks: - source is the normalized source/filter key; relevance and geo are listing classifications. - posted is the employer/source posting time; sources that state only a relative age (Workday) give the start of that UTC day, never later than firstSeenAt. - firstSeenAt and observedAt are the first and latest persisted Applydra listing observations. - changedAt is the last time a served job field changed (or first discovery) since change tracking began; null when unchanged since then. Company-level fields (company, sourceSnapshotAt) do not move it. - freshness is fresh when observedAt is within 48 hours of meta.asOf; stale is retained coverage, not proof that applications remain open. - sourceSnapshotAt is the latest complete persisted source snapshot and may be null for sources without that lifecycle. - remoteSource describes workplace provenance; remoteConflict marks a retained contradiction between authoritative structured workplace metadata and role text; senioritySource describes canonical seniority provenance; compSource is field-scoped provenance for the stated compensation quartet. - url is the source posting link. applyUrl is the best known application destination: the employer ATS link a source page points to when known, else url; it is not verified live. - applyPortal is the platform recognized from applyUrl's host (for example greenhouse, workday, builtin), or null when employer-hosted or unrecognized; it is not proof that applications can be submitted. - companyNameSource is the provenance of company: posting, benchmark, listing, host (derived from a careers-site hostname), or slug (a readable source organization key). - requisitionId is the employer-published requisition ID when the source states one, else null. Applydra IDs are opaque; source-native posting identifiers are never exposed. Semantics: - experienceYearsMin is the largest required minimum years of experience the description states, read deterministically (preferred qualifications excluded); null means not stated. maxExperienceYears= excludes roles whose stated minimum exceeds n and keeps roles that state none. - unknown is an explicit unresolved classification, not JSON null. - remoteConflict=true records contradictory authoritative structured workplace metadata and role text. Explicit restrictive role text may resolve the canonical value to hybrid or onsite; a direct unresolved clash is served as unknown. Heuristic-only uncertainty does not set remoteConflict. Strict remote filters exclude non-remote verdicts, while broadened current-canonical queries retain them. - JSON null means not stated or not established; false remains a distinct explicit boolean value. - seniority values are intern, junior, mid, senior, staff+, leadership, or null; mid is explicit, while null is unestablished or unresolved. - leadership means organizational or people-management rank, not every occupational Manager title. - url can be null only after disabling actionability with actionable=all. - sourceSnapshotAt: null is not evidence that the listing was never observed. - freshness: stale is not the same as recorded closed; use observedAt and sourceSnapshotAt to judge the evidence clocks. - GET /api/v1/jobs/{id} for a stored ID: 308 redirects a role folded into another canonical listing to that job; 410 with error.code gone and closedAt means the role is recorded closed; 404 means the ID is malformed or was never issued. - observedWithin= keeps roles Applydra observed at their source within that many days of meta.asOf (default 7); observedWithin=all disables it. Listings unobserved for longer stay open and addressable by ID but are likelier to be gone. - Cursor values and meta.asOf are stable traversal/filter boundaries, not freshness fields. - firstSeenAfter= returns only roles whose served firstSeenAt is strictly after it, on jobs and stats; jobs pages are then ordered oldest-first by firstSeenAt instead of by posted date. Roles first seen within 5 minutes of meta.asOf are held back so that writes committing out of order are never skipped. firstSeenAt is first discovery: a role that later starts matching other filters keeps its original firstSeenAt and is not returned again; use changedAfter for that. - changedAfter= is the change feed: only roles whose served changedAt is strictly after it, on jobs and stats, ordered oldest-first by changedAt. Store the greatest changedAt you received. It returns new roles and roles that start matching your filters after a served field changes (for example reclassification, or a closed role reopening), with the same 5-minute hold-back. A role that changes again mid-traversal can be delivered twice; re-entry purely through time-window filters (observedWithin, postedWithin) is not a change. changedAfter cannot be combined with firstSeenAfter (400 invalid_request). - OpenAPI is the current wire authority. Version 1 may change in place without a deprecation period, changelog, compatibility shim, migration window, or new major path; clients must adapt. Pagination uses opaque cursors. Follow response.links.next until it is null. Do not construct or modify cursor values. Operational endpoints such as /api/health, /api/metrics, /healthz, and /readyz are not corpus API operations and are not part of the v1 compatibility contract. Use bounded concurrency, cache responses and revalidate with ETags, keep cursors opaque, and honor 429 and Retry-After. Do not rotate identities or bypass service controls. robots.txt and noindex directives control crawler indexing; they do not grant authorization or data rights. curl: curl --fail 'https://applydra.io/api/v1/jobs?limit=100' JavaScript: let next = 'https://applydra.io/api/v1/jobs?limit=100'; while (next) { const page = await fetch(next).then((response) => response.json()); for (const job of page.data) console.log(job.id, job.title); next = page.links.next; } Incremental sync (run on a schedule; store the greatest firstSeenAt you received and pass it next time): let since = loadWatermark(); // for example '2026-10-02T15:04:05.123Z' let next = `https://applydra.io/api/v1/jobs?limit=500&firstSeenAfter=${encodeURIComponent(since)}`; let newest = since; while (next) { const page = await fetch(next).then((response) => response.json()); for (const job of page.data) { handleNewRole(job); if (job.firstSeenAt > newest) newest = job.firstSeenAt; } next = page.links.next; } saveWatermark(newest); Python (standard library): import json from urllib.request import urlopen next_url = 'https://applydra.io/api/v1/jobs?limit=100' while next_url: with urlopen(next_url) as response: page = json.load(response) for job in page['data']: print(job['id'], job['title']) next_url = page['links']['next']