Endpoints / Modified jobs

Get modified jobs

Jobs whose tracked fields changed within a window — the upsert side of a sync.

GET/v1/jobs/modified
GEThttps://api.hyperjobs.io/v1/jobs/modified

Jobs 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#

since
<number><m|h|d>default: 24h

Relative window: jobs whose tracked fields changed within it. 15m, 2h, 24h, 7dm is minutes. A bad value is a 400.

modified_gte
ISO 8601 date

Absolute alternative to since: changes on/after this instant. Use it to resume from the exact modified_at of the last row you processed.

limit
integerdefault: 200

Results per page, 1 to 1,000.

cursor
string

Opaque keyset cursor from the previous page's next_cursor. Page until it comes back null.

description_format
"text" | "html"default: omitted

Include 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#

count
integer

Rows in data — this page, not a total.

data
Job[]

Full Job objects, oldest change first — ascending by modified_at. Every row carries modified_at and modified_fields.

next_cursor
string | null

Pass it back as ?cursor=; null means the window is drained.

Response
{
  "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#

StatusCause
400Invalid parameter or value — the body names the param and carries a hint.
401Missing, unknown, or revoked key.
429Rate limit or monthly quota exceeded.
500Server error. Retry with backoff.

Next#

Was this page helpful?