API
JSON over HTTPS. Job postings from public company job boards, refreshed daily, with the dates we first and last saw each one. Included in Pro (50,000 rows a month, 60 requests a minute) and Business (500,000 rows, 300/min, plus the signals feed). The machine-readable contract is at /api/docs (OpenAPI 3.1).
Authentication
Create a key in your dashboard. It is shown once; we store only a hash. Send it as a bearer token. Keys in the query string are refused.
curl -H "Authorization: Bearer yj_live_…" \ "https://jobs.yugenox.com/api/v1/jobs?title=data%20engineer&country=CA&posted_within_days=7&limit=20"
Quotas & errors
Quotas count rows returned, not calls, per calendar month (UTC). If you ask for 100 rows with 30 left, you get 30, then 429. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-Quota-Remaining.
| Status | Meaning |
|---|---|
400 | Unknown query parameter or malformed cursor (we reject typos instead of ignoring them) |
401 | Missing or invalid key |
403 | Your plan doesn't include this endpoint |
429 | Rate limit (with Retry-After) or monthly quota used up |
Errors are JSON: { "error": "quota_exceeded", "message": "…" }
GET /api/v1/jobs
Search jobs. Newest first (coalesce(posted_at, first_seen) desc). Page with the opaque `cursor` from the previous response.
| Parameter | Type | Description |
|---|---|---|
q | string | Full-text keywords (web-search syntax: "exact phrase", -exclude, or) |
title | string | Keywords that must appear in the job TITLE (same syntax as q; combine with q for title + anywhere) |
location | string | Country, state/province or free text ("Canada", "Ontario", "Toronto", "Remote", "IL", "Chicago, IL"). A bare two-letter code is a US state first (CA = California, IL = Illinois), then a Canadian province; use country= for an ISO code |
country | string | ISO2 or name |
region | string | "US-CA", "CA-ON" or a state/province name |
city | string | City name or slug |
remote | boolean | Remote only (true) or exclude remote (false) |
workplace | string one of remote, hybrid, onsite | |
employment | string one of full-time, part-time, contract, internship, temporary | |
min_salary | integer | Annualized salary floor in ONE currency (see currency); jobs without a posted salary, or whose pay period is unknown and the amount too small to be yearly, are excluded |
has_salary | boolean | Only jobs whose employer posted a salary (any amount) |
currency | string | ISO 4217. With min_salary it defaults to the country (or region) currency, else USD; on its own it filters to salaries posted in this currency |
posted_within_days | integer 1–365 | |
first_seen_after | string | Only jobs our crawler first saw after this instant (incremental sync) |
company | string | Company slug or exact name |
department | string | Substring of department or team |
role | string | Role family slug (software-engineering, data, sales, ...) |
ats | string one of greenhouse, lever, ashby | |
include_removed | boolean | Include postings that have been taken down (with removed_at) |
include_description | boolean | Add description_text. Job descriptions are not in the feed yet, so it is null for now; every record links to the full posting (url) |
limit | integer default 20 · 1–100 | |
cursor | string | From next_cursor of the previous page |
Incremental sync
To mirror new postings daily, keep the time of your last run and pass it as first_seen_after, then follow next_cursor until it is null. Add include_removed=true to also receive postings that were taken down (they carry removed_at).
GET /api/v1/jobs?first_seen_after=2026-10-18T06:00:00Z&limit=100
→ { "data": [ …100 jobs… ], "next_cursor": "WyIyMDI2LTEw…" }
GET /api/v1/jobs?first_seen_after=2026-10-18T06:00:00Z&limit=100&cursor=WyIyMDI2LTEw…
→ { "data": [ …37 jobs… ], "next_cursor": null }GET /api/v1/signals
Hiring signals (Business plan). Companies ranked by growth in open jobs over the window, optionally for one role family and/or place.
| Parameter | Type | Description |
|---|---|---|
role | string | Role family slug |
country | string | |
region | string | |
window_days | integer one of 7, 30 · default 7 | |
min_open | integer default 5 | Ignore companies smaller than this |
limit | integer default 50 · –200 |
Job object
| Field | Type | Notes |
|---|---|---|
id | string | e.g. greenhouse:stripe:7103123 |
title | string | |
company | string | |
company_slug | string | |
company_has_page | boolean | false for employers we do not profile (no /companies/<slug> page); their postings are still returned |
ats | string | |
department | string | |
location | string | |
locations | array | |
countries | array | |
workplace_type | string | |
is_remote | boolean | true only for remote work: workplace_type remote, or no workplace type and a remote location. Hybrid and onsite are never remote. |
employment_type | string | |
role_family | string | |
salary | object | Only when the employer posted pay; never estimated. min/max = the headline tier, in the currency of the posting's primary location. |
posted_at | string | The date field as the board reports it. Greenhouse postings without a publish date carry their last edit here; use published_at for the posting date |
published_at | string | The employer's own publish date; null when the board gives none (then first_seen is the closest date we have) |
first_seen | string | When our crawler first saw the posting |
last_seen | string | |
removed_at | string | When it disappeared from the board (only with include_removed=true) |
url | string | |
apply_url | string | |
description_text | string | Only with include_description=true. Null for now: job descriptions are not in the feed yet (the full posting is at url) |
Salaries appear only when the employer posted one; we never estimate. countries are ISO 3166-1 alpha-2 codes resolved from the posted locations.