- API reference
- Endpoints
Get modified jobs
Jobs whose tracked fields changed within a window — the upsert side of a sync.
https://api.hyperjobs.io/v1/jobs/modifiedJobs that changed — not new, not dead, changed. When a posting's salary
band moves, its apply URL is swapped, or it flips to remote, it won't reappear
in /v1/jobs/feed (that's windowed on first
index). This endpoint catches those edits: full Job objects,
oldest change first, each carrying modified_at and a modified_fields array
telling you exactly what moved. See syncing the feed
for where it fits.
curl -G https://api.hyperjobs.io/v1/jobs/modified \
-H "Authorization: Bearer $HYPERJOBS_KEY" \
--data-urlencode "since=24h"Query parameters#
sinceRelative window: jobs whose tracked fields changed within it. 15m,
2h, 24h, 7d — m is minutes. A bad value is a 400.
modified_gteAbsolute alternative to since: changes on/after this instant. Use it to
resume from the exact modified_at of the last row you processed.
limitResults per page, 1 to 1,000.
cursorOpaque keyset cursor from the previous page's next_cursor. Page until it
comes back null.
description_formatInclude the description. Worth considering here even if you skip it on the
feed — description is a tracked field, and without this flag a
description-only change hands you a Job with no visible difference.
No filters beyond the window — anything else is a 400. Like /v1/jobs and
the feed, the population excludes duplicates and expired postings; a job whose
change was its expiry shows up in
/v1/jobs/expired instead.
Tracked fields#
Only changes to these fields register — modified_fields draws from exactly
this vocabulary:
title · organization_name · locations · countries · employment_type ·
work_arrangement · seniority · salary_min · salary_max ·
salary_currency · salary_unit · apply_url · url · remote ·
department · description
A re-scrape that changes nothing tracked — or only touches enrichment like
skills or taxonomies — does not bump modified_at and won't surface here.
Response#
countRows in data — this page, not a total.
dataFull Job objects, oldest change first — ascending by modified_at.
Every row carries modified_at and modified_fields.
next_cursorPass it back as ?cursor=; null means the window is drained.
{
"count": 37,
"data": [
{
"id": "ashby:7c1f2a94-3e17-4d8b-9c05-1f6ab2e40d33",
"title": "Staff Backend Engineer",
"company": { "slug": "linear", "name": "Linear", "domain": "linear.app" },
"location": { "remote": true, "countries": ["United States"], "continent": "North America" },
"employment_type": ["FULL_TIME"],
"salary": {
"min": 190000,
"max": 240000,
"currency": "USD",
"unit": "YEAR",
"annual_min": 190000,
"annual_max": 240000,
"summary": "$190,000 – $240,000 a year"
},
"apply_url": "https://jobs.ashbyhq.com/linear/7c1f2a94…",
"source": "ashby",
"posted_at": "2026-07-08T14:02:11.000Z",
"modified_at": "2026-07-16T08:30:05.000Z",
"modified_fields": ["salary_max", "apply_url"],
"created_at": "2026-07-08T14:10:22.000Z",
"updated_at": "2026-07-16T08:30:05.000Z"
}
// …36 more rows — full Job objects, other fields elided here
],
"next_cursor": null
}The sync loop role#
The feed adds, /v1/jobs/expired deletes —
this endpoint is the update. On each poll cycle, upsert every returned Job
into your mirror keyed on id, replacing the stored row wholesale: the payload
is the complete current state, so there's no patch to compute. Ignoring this
endpoint doesn't break a mirror, it stales it — jobs keep their day-one
salary and apply URL for as long as they live, which for a job board means
sending applicants to links that have since been swapped out.
modified_fields is there so you can react selectively — re-run salary
normalization only when a salary_* field appears, re-verify links only on
apply_url — rather than reprocessing every row. Overlapping windows are
harmless: the upsert is idempotent. The full pattern is in
syncing the feed.
Errors#
| Status | Cause |
|---|---|
400 | Invalid parameter or value — the body names the param and carries a hint. |
401 | Missing, unknown, or revoked key. |
429 | Rate limit or monthly quota exceeded. |
500 | Server error. Retry with backoff. |
Next#
Snapshot, poll, expire — the whole mirroring pattern.
Get the feedThe add side: newly indexed jobs, ascending.
Expired jobsThe delete side: ids to purge from your mirror.
The Job objectEvery field on the payloads this endpoint returns.