Puesto Labs
IntroductionAuthenticationPagination & sortingSyncing with the changes feedErrorsRate limitsBuilding with AI

Here you can add a description about your company or product

© Copyright 2026 Puesto Labs. All Rights Reserved.

About
  • Blog
  • Contact
Product
  • Documentation
  • llms.txt
Legal
  • Terms of Service
  • Privacy Policy
  • Cookie Policy
Puesto Docs

List job changes

GET
/api/v1/changes

Poll the append-only feed of job change events for incremental sync. Pages forward by a monotonic integer cursor; omit the cursor to replay the full history from the beginning (initial sync). Always pass the returned next_cursor back on the next poll. When has_more is true, keep paging immediately; when false, poll again on your own interval. Set include=job to embed the current job record (descriptions omitted) with each change. Use since (an ISO-8601 timestamp with offset) as a convenience alternative to cursor for starting the feed at a point in time; it is mutually exclusive with a non-zero cursor. Snapshot filters (level, workplace, employment_type, country, company_id, has_salary) use before-OR-after matching: an event is delivered if the job matched the filter before the change or after it — so you also see jobs leaving your scope (e.g. filtering level=senior still delivers the event where a job moves out of the senior level). Different filter dimensions are ANDed together. The changed_fields attribution filter delivers only events that changed at least one of the named content fields; created events carry no field-level attribution and are always delivered. Its vocabulary names STORED columns rather than response fields — location_raw and locations_raw are what a job renders as its location string and locations array, so filter on those names to catch a relocation even though neither is returned on its own.

Authorization

bearerAuth
AuthorizationBearer <token>

Provide your secret API key (prefixed sk_) as a bearer token: Authorization: Bearer sk_....

In: header

Query Parameters

cursor?string

Resume the feed after this change id. Omit to replay the full history from the beginning. Always pass back the next_cursor from the previous response. Mutually exclusive with since (a non-zero cursor alongside since is rejected).

Match^\d+$
Default"0"
types?array<>

Filter to specific change kinds. Repeat the parameter or comma-separate values (e.g. types=created,closed). A change matches if it is any of the given kinds.

Itemsitems <= 4
level?array<>

Filter by seniority level. Repeat the parameter or comma-separate values. Matches before-OR-after: an event is delivered if the job was at any of these levels before the change or is at one after it (jobs leaving your scope are still delivered).

Itemsitems <= 7
workplace?array<>

Filter by workplace arrangement. Repeat the parameter or comma-separate values. Matches before-OR-after: delivered if the job matched any of these before the change or matches one after it.

Itemsitems <= 3
employment_type?array<>

Filter by employment type. Repeat the parameter or comma-separate values. Matches before-OR-after: delivered if the job matched any of these before the change or matches one after it.

Itemsitems <= 5
country?array<>

Filter by ISO 3166-1 alpha-2 country code(s). Repeat the parameter or comma-separate values (e.g. country=us,de). Up to 20 values; case-insensitive. Matches before-OR-after: delivered if the job was tagged with any of these countries before the change or matches its current location.

Itemsitems <= 20
company_id?integer

Filter to changes for jobs belonging to this numeric company id.

has_salary?boolean

When true, deliver events where the job had a salary before the change OR has one after it; when false, the inverse. Matches before-OR-after state.

changed_fields?array<>

Attribution filter: deliver only events that changed at least one of these content fields. Repeat the parameter or comma-separate values (e.g. changed_fields=salary_min,salary_max). created events are always delivered (they have no field-level attribution). The vocabulary names STORED fields rather than response fields: location_raw and locations_raw are the location internals behind a job's location string and locations array, so filtering on them is how you catch a location change. Every name accepted here is a name that can come back in an event's changed_fields, and an unrecognized one is rejected rather than silently matching nothing.

Itemsitems <= 20
since?string

Start the feed at this ISO-8601 timestamp, including a UTC offset (e.g. 2024-01-01T00:00:00+00:00). A convenience alternative to cursor for starting from a point in time; resolved to a cursor server-side. Mutually exclusive with a non-zero cursor.

Formatdate-time
limit?integer

Maximum number of changes to return. Defaults to 100; values above 500 are clamped to 500.

Range0 < value
Default100
include?"job"

When set to job, attach the current job record (descriptions omitted) to each change.

Value in

  • "job"

Response Body

application/json

application/problem+json

application/problem+json

application/problem+json

curl -X GET "https://example.com/api/v1/changes"
{  "data": [    {      "id": 0,      "job_id": 0,      "change_type": "created",      "changed_at": "string",      "changed_fields": [        "string"      ],      "job": {        "id": 0,        "source_id": 0,        "company_id": 0,        "external_job_id": "string",        "url": "string",        "apply_url": "string",        "title": "string",        "location": "string",        "locations": [          {            "kind": "country",            "code": "string",            "name": "string",            "admin1_name": "string",            "country_code": "string"          }        ],        "workplace": "remote",        "employment_type": "full_time",        "level": "intern",        "experience_min_years": 0,        "experience_max_years": 0,        "sponsors_visa": true,        "skills": [          {            "slug": "string",            "name": "string",            "kind": "string",            "requirement": "required"          }        ],        "language": "string",        "salary_raw": "string",        "salary_min": 0,        "salary_max": 0,        "salary_currency": "string",        "salary_period": "year",        "posted_at": "string",        "expires_at": "string",        "content_hash": "string",        "first_seen_at": "string",        "closed_at": "string",        "created_at": "string",        "updated_at": "string",        "source": {          "type": "string",          "external_id": "string",          "alias_ids": [            "string"          ]        },        "description_md": "string",        "description_html": "string",        "company": {          "id": 0,          "name": "string",          "website_url": "string",          "logo_url": "string"        }      }    }  ],  "next_cursor": "string",  "has_more": true,  "limit": 0}
{  "type": "string",  "title": "string",  "status": 0,  "detail": "string",  "errors": [    {      "field": "string",      "message": "string"    }  ]}
{  "type": "string",  "title": "string",  "status": 0,  "detail": "string",  "errors": [    {      "field": "string",      "message": "string"    }  ]}
{  "type": "string",  "title": "string",  "status": 0,  "detail": "string",  "errors": [    {      "field": "string",      "message": "string"    }  ]}