api
Parameters
Every query parameter the listing endpoints take, grouped by what it does, generated from the live specification so it cannot drift.
1How to read these tables
Name, type, default, allowed values, and whether it takes a comma-separated list. Everything applies to /v1/jobs unless a section says otherwise.
In depthWhy it exists, what it is not, what people get wrong
- comma-separated
- The parameter takes a list:
?country=DE,AT,CH. No spaces. Each list has a maximum length and a longer list is truncated rather than refused. - default
- What the API uses when you omit the parameter. An omitted filter means “do not filter on this”, not “filter on the default”.
- range
- Inclusive bounds.
limitis clamped to the plan maximum rather than refused. - paid endpoints only
- The parameter is honoured on
/v1/*and ignored on/public/*. It is not an error to send it keylessly; it simply does nothing. - Case
- Values are matched case-insensitively —
category=engineeringandcategory=Engineeringare the same query. Parameter names are case-sensitive. - Combining
- Every filter ANDs with every other. There is no OR across different parameters; a comma-separated list is the OR within one.
The same parameter set is accepted by /v1/jobs, /v1/jobs/closed and /v1/export, with the differences noted on each endpoint's own page. /v1/companies has its own smaller set.
Exact contractTypes, defaults, ranges, errors, edge cases
Unknown parameters are ignored rather than refused. That is deliberate: a newer client sending a parameter an older deployment has not heard of should degrade to a broader query, not to an error. The cost is that a typo in a parameter name is silent — ?catgeory=Engineering returns everything — whereas a typo in a parameter value for a controlled vocabulary is a 422. Check the shape of your result set the first time you add a filter.
2Paging
limit sets the page size; cursor continues from the previous page. Both are documented in full under Pagination.
In depthWhy it exists, what it is not, what people get wrong
limit costs records, not requests: a page of 200 charges 200 records against your monthly allowance and one request against your rate limit. Ask for what you will actually use — the most common avoidable expense on this API is a loop that requests 200 rows and reads the first ten.
The maximum page size is a plan property, not a constant. Every purchasable plan currently allows 200; the keyless surface caps at 50. A limit above your maximum is clamped quietly rather than refused.
Exact contractTypes, defaults, ranges, errors, edge cases
limitintegerdefault 251–200cursorstringThe next_cursor from the previous page.
2 parameters, generated from /openapi.json when this page was built. The spec is served from the running API and is the contract; if this table and the spec ever disagree, the spec is right and this is a bug — please say so with the thumbs-down below.
3Place — country, city, state
Three location filters of decreasing coverage. Each is absent wherever the source did not give us enough to fill it with confidence.
In depthWhy it exists, what it is not, what people get wrong
Location arrives as free text from most sources — “Clermont-Ferrand, Auvergne-Rhône-Alpes, France” — and is resolved into structured fields where it can be. location keeps the original string on every row that had one; country, city and state are the resolved parts.
Resolution under-reports on purpose. Where a city name is ambiguous the structured field is left empty rather than filled with a guess, because a job placed in the wrong country is worse than a job placed in no country. That is why filtering on city returns fewer rows than searching the free-text location would, and why the free-text is still there for you to search yourself.
Exact contractTypes, defaults, ranges, errors, edge cases
countrystringcomma-separatedComma-separated ISO-3166 alpha-2.
citystringstatestringcomma-separatedComma-separated two-letter US state codes, e.g. OH or OH,TX.
Absent where we could not establish the state from the source;
deliberately absent for ambiguous city names, so this filter
under-reports rather than placing a job in the wrong state.
exclude_countrystring4 parameters, generated from /openapi.json when this page was built. The spec is served from the running API and is the contract; if this table and the spec ever disagree, the spec is right and this is a bug — please say so with the thumbs-down below.
There is no “country is null” filter. To work with unlocated rows, fetch without a country filter and inspect field_sources.location — or use exclude_country with the codes you do not want, which keeps the unlocated rows in the result.
4Country codes
The country parameter lets you search for a job in a specific country, so you see only openings in that place, using ISO codes.
In depthWhy it exists, what it is not, what people get wrong
ISO codes and countries. The codes are ISO-3166 alpha-2: two letters, FR for France, DE for Germany, US for the United States. Uppercase by convention, though the filter is case-insensitive like every other value here. Several at once are comma-separated and mean OR: ?country=DE,AT,CH returns roles in any of the three.
Below is every code the ledger currently carries, with how many live rows sit behind each. It is not a list of the world's countries — it is what this data holds today, including dependencies and territories with their own code, and it moves as employers post and roles close.
Every country in the ledger — 171 rows, sorted by live listings
| ISO | Live | With a description | Described | With a published salary | Employers |
|---|---|---|---|---|---|
US | 1,583,921 | 1,199,967 | 75.8% | 186,103 | 105,037 |
DE | 169,230 | 158,668 | 93.8% | 7,850 | 29,175 |
GB | 110,642 | 95,672 | 86.5% | 5,719 | 11,710 |
IN | 72,340 | 57,947 | 80.1% | 987 | 5,268 |
FR | 63,761 | 59,789 | 93.8% | 2,778 | 4,142 |
CA | 60,032 | 54,531 | 90.8% | 4,744 | 6,110 |
NL | 50,434 | 46,502 | 92.2% | 4,994 | 7,123 |
SE | 31,576 | 30,782 | 97.5% | 51 | 5,507 |
AU | 23,023 | 20,548 | 89.2% | 285 | 3,200 |
ES | 21,741 | 18,512 | 85.1% | 504 | 2,967 |
BE | 16,502 | 14,522 | 88% | 145 | 2,337 |
HU | 14,424 | 2,928 | 20.3% | 20 | 482 |
IT | 14,362 | 13,041 | 90.8% | 321 | 1,823 |
CH | 14,182 | 13,053 | 92% | 108 | 2,259 |
MX | 14,100 | 11,439 | 81.1% | 198 | 1,749 |
BR | 12,923 | 9,866 | 76.3% | 298 | 1,555 |
PL | 12,900 | 10,894 | 84.4% | 279 | 1,907 |
SG | 12,630 | 9,924 | 78.6% | 97 | 1,636 |
PH | 11,422 | 9,796 | 85.8% | 276 | 1,291 |
MY | 10,203 | 8,804 | 86.3% | 725 | 1,491 |
AE | 10,082 | 8,985 | 89.1% | 765 | 1,173 |
AT | 10,054 | 9,429 | 93.8% | 3,115 | 2,091 |
JP | 9,953 | 8,514 | 85.5% | 99 | 1,837 |
CN | 9,822 | 7,383 | 75.2% | 63 | 1,186 |
HR | 8,723 | 8,486 | 97.3% | 20 | 1,163 |
IE | 8,588 | 7,420 | 86.4% | 311 | 1,440 |
NO | 8,372 | 7,724 | 92.3% | 139 | 1,296 |
ZA | 7,070 | 6,489 | 91.8% | 664 | 986 |
PT | 7,012 | 6,233 | 88.9% | 114 | 904 |
DK | 5,571 | 5,126 | 92% | 41 | 829 |
GR | 5,515 | 5,173 | 93.8% | 54 | 654 |
FI | 5,155 | 4,432 | 86% | 73 | 940 |
ID | 4,994 | 4,324 | 86.6% | 12 | 906 |
TH | 4,763 | 3,902 | 81.9% | 61 | 636 |
CO | 4,723 | 4,022 | 85.2% | 82 | 894 |
RO | 4,476 | 3,941 | 88% | 35 | 762 |
SA | 4,457 | 3,966 | 89% | 19 | 617 |
NZ | 4,101 | 3,761 | 91.7% | 29 | 576 |
HK | 3,727 | 2,913 | 78.2% | 160 | 735 |
EG | 3,708 | 3,237 | 87.3% | 44 | 513 |
TW | 3,602 | 2,750 | 76.3% | 29 | 521 |
VN | 3,547 | 2,911 | 82.1% | 20 | 547 |
CZ | 3,331 | 2,705 | 81.2% | 40 | 608 |
LT | 3,060 | 2,816 | 92% | 85 | 262 |
AR | 3,031 | 2,561 | 84.5% | 91 | 683 |
GE | 2,995 | 2,043 | 68.2% | 22 | 612 |
KR | 2,859 | 2,349 | 82.2% | 17 | 600 |
UA | 2,764 | 2,549 | 92.2% | 31 | 363 |
BG | 2,711 | 2,407 | 88.8% | 75 | 412 |
PE | 2,459 | 1,844 | 75% | 15 | 322 |
IL | 2,335 | 1,981 | 84.8% | 22 | 396 |
CL | 2,137 | 1,781 | 83.3% | 9 | 340 |
TR | 1,849 | 1,493 | 80.7% | 8 | 431 |
MA | 1,777 | 1,596 | 89.8% | 4 | 241 |
UN | 1,636 | 1,616 | 98.8% | 478 | 349 |
LU | 1,617 | 1,405 | 86.9% | 22 | 361 |
NG | 1,527 | 1,490 | 97.6% | 4 | 262 |
PK | 1,451 | 1,392 | 95.9% | 46 | 252 |
QA | 1,368 | 1,143 | 83.6% | 7 | 168 |
CR | 1,213 | 882 | 72.7% | 8 | 235 |
SK | 1,150 | 946 | 82.3% | 26 | 217 |
RS | 1,145 | 1,057 | 92.3% | 11 | 338 |
MT | 1,131 | 1,063 | 94% | 40 | 230 |
CY | 1,094 | 1,047 | 95.7% | 28 | 237 |
LK | 915 | 847 | 92.6% | 4 | 107 |
EE | 899 | 854 | 95% | 17 | 201 |
GT | 817 | 718 | 87.9% | 16 | 109 |
LV | 775 | 714 | 92.1% | 33 | 165 |
KE | 761 | 703 | 92.4% | 7 | 218 |
EC | 672 | 597 | 88.8% | 5 | 87 |
JO | 631 | 495 | 78.4% | 0 | 127 |
TN | 626 | 526 | 84% | 5 | 116 |
PA | 573 | 446 | 77.8% | 2 | 146 |
KZ | 522 | 476 | 91.2% | 9 | 115 |
LB | 454 | 336 | 74% | 5 | 145 |
DO | 440 | 384 | 87.3% | 0 | 103 |
SI | 424 | 333 | 78.5% | 5 | 115 |
PR | 412 | 275 | 66.7% | 8 | 75 |
AZ | 383 | 367 | 95.8% | 167 | 226 |
KW | 380 | 346 | 91.1% | 0 | 86 |
MV | 360 | 341 | 94.7% | 0 | 17 |
UY | 350 | 289 | 82.6% | 0 | 107 |
BD | 338 | 307 | 90.8% | 5 | 125 |
MU | 325 | 322 | 99.1% | 0 | 35 |
BH | 323 | 266 | 82.4% | 1 | 86 |
LI | 319 | 317 | 99.4% | 0 | 36 |
RU | 305 | 296 | 97% | 3 | 91 |
OM | 287 | 212 | 73.9% | 1 | 81 |
SV | 282 | 264 | 93.6% | 72 | 67 |
TZ | 269 | 266 | 98.9% | 3 | 46 |
AM | 268 | 244 | 91% | 1 | 76 |
AL | 252 | 234 | 92.9% | 36 | 103 |
HN | 230 | 209 | 90.9% | 17 | 71 |
SR | 224 | 182 | 81.3% | 1 | 25 |
GH | 223 | 207 | 92.8% | 8 | 91 |
VE | 211 | 199 | 94.3% | 4 | 63 |
BM | 200 | 186 | 93% | 0 | 12 |
SW | 197 | 197 | 100% | 0 | 99 |
MD | 187 | 166 | 88.8% | 0 | 50 |
IQ | 186 | 175 | 94.1% | 0 | 42 |
NI | 184 | 178 | 96.7% | 0 | 47 |
NP | 155 | 116 | 74.8% | 1 | 34 |
AD | 146 | 140 | 95.9% | 21 | 131 |
MK | 144 | 130 | 90.3% | 2 | 67 |
FJ | 140 | 108 | 77.1% | 0 | 21 |
DZ | 140 | 114 | 81.4% | 0 | 38 |
ET | 136 | 109 | 80.1% | 0 | 46 |
JM | 127 | 111 | 87.4% | 7 | 48 |
SO | 125 | 125 | 100% | 1 | 20 |
BA | 120 | 115 | 95.8% | 0 | 54 |
AO | 116 | 83 | 71.6% | 0 | 20 |
XK | 113 | 112 | 99.1% | 0 | 27 |
KH | 111 | 87 | 78.4% | 4 | 46 |
BO | 110 | 102 | 92.7% | 0 | 35 |
MZ | 108 | 103 | 95.4% | 0 | 25 |
BY | 106 | 106 | 100% | 0 | 23 |
GI | 105 | 104 | 99% | 0 | 27 |
UG | 101 | 101 | 100% | 0 | 35 |
CI | 101 | 101 | 100% | 0 | 36 |
MM | 98 | 68 | 69.4% | 1 | 31 |
CD | 98 | 97 | 99% | 1 | 30 |
JE | 91 | 90 | 98.9% | 1 | 19 |
MO | 90 | 63 | 70% | 0 | 18 |
TT | 89 | 85 | 95.5% | 0 | 19 |
IM | 86 | 86 | 100% | 2 | 19 |
BS | 86 | 75 | 87.2% | 2 | 25 |
ME | 86 | 65 | 75.6% | 0 | 18 |
SN | 84 | 79 | 94% | 0 | 31 |
SP | 81 | 81 | 100% | 0 | 14 |
CM | 80 | 80 | 100% | 0 | 26 |
UZ | 79 | 75 | 94.9% | 0 | 31 |
GG | 76 | 76 | 100% | 0 | 15 |
AW | 76 | 62 | 81.6% | 0 | 6 |
MC | 74 | 61 | 82.4% | 1 | 32 |
IS | 72 | 59 | 81.9% | 0 | 33 |
KY | 70 | 59 | 84.3% | 1 | 17 |
PG | 69 | 58 | 84.1% | 0 | 20 |
LA | 68 | 50 | 73.5% | 2 | 34 |
ZM | 66 | 62 | 93.9% | 0 | 27 |
PF | 66 | 60 | 90.9% | 2 | 9 |
MQ | 64 | 63 | 98.4% | 0 | 18 |
NA | 63 | 59 | 93.7% | 0 | 18 |
IR | 62 | 55 | 88.7% | 1 | 16 |
HO | 61 | 61 | 100% | 0 | 3 |
PY | 61 | 53 | 86.9% | 0 | 31 |
RW | 58 | 48 | 82.8% | 0 | 24 |
GY | 57 | 52 | 91.2% | 0 | 16 |
SC | 52 | 52 | 100% | 0 | 7 |
BB | 50 | 45 | 90% | 0 | 13 |
MG | 50 | 48 | 96% | 0 | 16 |
RE | 49 | 48 | 98% | 1 | 25 |
SZ | 46 | 46 | 100% | 0 | 6 |
SL | 44 | 41 | 93.2% | 0 | 12 |
GA | 41 | 41 | 100% | 0 | 12 |
JA | 40 | 40 | 100% | 0 | 16 |
BW | 39 | 39 | 100% | 0 | 18 |
NE | 37 | 34 | 91.9% | 4 | 24 |
LY | 36 | 35 | 97.2% | 0 | 13 |
TD | 34 | 34 | 100% | 0 | 11 |
BU | 33 | 29 | 87.9% | 0 | 22 |
SD | 32 | 28 | 87.5% | 1 | 13 |
WS | 32 | 28 | 87.5% | 0 | 6 |
AF | 30 | 24 | 80% | 0 | 14 |
PO | 30 | 30 | 100% | 0 | 7 |
GP | 28 | 28 | 100% | 0 | 13 |
GU | 27 | 26 | 96.3% | 1 | 15 |
HT | 26 | 25 | 96.2% | 1 | 12 |
ZW | 26 | 26 | 100% | 0 | 13 |
LR | 26 | 26 | 100% | 0 | 7 |
SM | 25 | 15 | 60% | 0 | 6 |
SY | 25 | 24 | 96% | 0 | 16 |
171 countries, rendered from /public/coverage/countries, which needs no key. The count is of ISO codes we have live rows for — it is not a list of the world’s countries and it moves as employers post and roles close. Measured 11 September 2026, 22:52 UTC.
The same vocabulary, with live counts, is enumerable in code from `/public/facets` (keyless) or /v1/meta/facets. That is the form to use in a program, because it is the list the API will accept today rather than the list it accepted when this page was built.
| Value to send | Label | Live rows |
|---|---|---|
US | US | 1,583,921 |
DE | DE | 169,230 |
GB | GB | 110,642 |
IN | IN | 72,340 |
FR | FR | 63,761 |
CA | CA | 60,032 |
NL | NL | 50,434 |
SE | SE | 31,576 |
AU | AU | 23,023 |
ES | ES | 21,741 |
BE | BE | 16,502 |
HU | HU | 14,424 |
IT | IT | 14,362 |
CH | CH | 14,182 |
MX | MX | 14,100 |
BR | BR | 12,923 |
PL | PL | 12,900 |
SG | SG | 12,630 |
PH | PH | 11,422 |
MY | MY | 10,203 |
AE | AE | 10,082 |
AT | AT | 10,054 |
JP | JP | 9,953 |
CN | CN | 9,822 |
HR | HR | 8,723 |
IE | IE | 8,588 |
NO | NO | 8,372 |
ZA | ZA | 7,070 |
PT | PT | 7,012 |
DK | DK | 5,571 |
GR | GR | 5,515 |
FI | FI | 5,155 |
ID | ID | 4,994 |
TH | TH | 4,763 |
CO | CO | 4,723 |
RO | RO | 4,476 |
SA | SA | 4,457 |
NZ | NZ | 4,101 |
HK | HK | 3,727 |
EG | EG | 3,708 |
TW | TW | 3,602 |
VN | VN | 3,547 |
CZ | CZ | 3,331 |
LT | LT | 3,060 |
AR | AR | 3,031 |
GE | GE | 2,995 |
KR | KR | 2,859 |
UA | UA | 2,764 |
BG | BG | 2,711 |
PE | PE | 2,459 |
IL | IL | 2,335 |
CL | CL | 2,137 |
TR | TR | 1,849 |
MA | MA | 1,777 |
UN | UN | 1,636 |
LU | LU | 1,617 |
NG | NG | 1,527 |
PK | PK | 1,451 |
QA | QA | 1,368 |
CR | CR | 1,213 |
SK | SK | 1,150 |
RS | RS | 1,145 |
MT | MT | 1,131 |
CY | CY | 1,094 |
LK | LK | 915 |
EE | EE | 899 |
GT | GT | 817 |
LV | LV | 775 |
KE | KE | 761 |
EC | EC | 672 |
JO | JO | 631 |
TN | TN | 626 |
PA | PA | 573 |
KZ | KZ | 522 |
LB | LB | 454 |
DO | DO | 440 |
SI | SI | 424 |
PR | PR | 412 |
AZ | AZ | 383 |
KW | KW | 380 |
MV | MV | 360 |
UY | UY | 350 |
BD | BD | 338 |
MU | MU | 325 |
BH | BH | 323 |
LI | LI | 319 |
RU | RU | 305 |
OM | OM | 287 |
SV | SV | 282 |
TZ | TZ | 269 |
AM | AM | 268 |
AL | AL | 252 |
HN | HN | 230 |
SR | SR | 224 |
GH | GH | 223 |
VE | VE | 211 |
BM | BM | 200 |
SW | SW | 197 |
MD | MD | 187 |
IQ | IQ | 186 |
NI | NI | 184 |
NP | NP | 155 |
AD | AD | 146 |
MK | MK | 144 |
FJ | FJ | 140 |
DZ | DZ | 140 |
ET | ET | 136 |
JM | JM | 127 |
SO | SO | 125 |
BA | BA | 120 |
AO | AO | 116 |
XK | XK | 113 |
KH | KH | 111 |
BO | BO | 110 |
MZ | MZ | 108 |
BY | BY | 106 |
GI | GI | 105 |
CI | CI | 101 |
UG | UG | 101 |
CD | CD | 98 |
MM | MM | 98 |
JE | JE | 91 |
MO | MO | 90 |
TT | TT | 89 |
BS | BS | 86 |
IM | IM | 86 |
ME | ME | 86 |
SN | SN | 84 |
SP | SP | 81 |
CM | CM | 80 |
UZ | UZ | 79 |
AW | AW | 76 |
GG | GG | 76 |
MC | MC | 74 |
IS | IS | 72 |
KY | KY | 70 |
PG | PG | 69 |
LA | LA | 68 |
ZM | ZM | 66 |
PF | PF | 66 |
MQ | MQ | 64 |
NA | NA | 63 |
IR | IR | 62 |
PY | PY | 61 |
HO | HO | 61 |
RW | RW | 58 |
GY | GY | 57 |
SC | SC | 52 |
MG | MG | 50 |
BB | BB | 50 |
RE | RE | 49 |
SZ | SZ | 46 |
SL | SL | 44 |
XX | XX | 44 |
GA | GA | 41 |
JA | JA | 40 |
BW | BW | 39 |
NE | NE | 37 |
LY | LY | 36 |
TD | TD | 34 |
BU | BU | 33 |
WS | WS | 32 |
SD | SD | 32 |
AF | AF | 30 |
PO | PO | 30 |
GP | GP | 28 |
GU | GU | 27 |
LR | LR | 26 |
HT | HT | 26 |
ZW | ZW | 26 |
SM | SM | 25 |
SY | SY | 25 |
KG | KG | 24 |
CF | CF | 24 |
VA | VA | 24 |
YE | YE | 24 |
GF | GF | 23 |
FM | FM | 23 |
GN | GN | 23 |
MW | MW | 22 |
AQ | AQ | 22 |
BJ | BJ | 22 |
PS | PS | 22 |
CU | CU | 21 |
BZ | BZ | 21 |
GM | GM | 20 |
SS | SS | 20 |
AX | AX | 18 |
TU | TU | 17 |
ML | ML | 17 |
LS | LS | 16 |
TC | TC | 15 |
TO | TO | 15 |
MR | MR | 14 |
EU | EU | 13 |
MH | MH | 13 |
MN | MN | 13 |
XI | XI | 12 |
SX | SX | 12 |
GQ | GQ | 12 |
CV | CV | 12 |
NC | NC | 12 |
GD | GD | 11 |
YT | YT | 11 |
VI | VI | 11 |
KN | KN | 10 |
VU | VU | 10 |
CW | CW | 10 |
TA | TA | 9 |
BI | BI | 9 |
HQ | HQ | 9 |
SB | SB | 9 |
BN | BN | 8 |
TJ | TJ | 8 |
BT | BT | 8 |
BQ | BQ | 7 |
ST | ST | 7 |
VG | VG | 7 |
BF | BF | 6 |
DJ | DJ | 6 |
SU | SU | 6 |
TG | TG | 5 |
CG | CG | 5 |
TM | TM | 5 |
SH | SH | 5 |
FO | FO | 4 |
KI | KI | 4 |
GW | GW | 4 |
AG | AG | 3 |
TL | TL | 3 |
AS | AS | 3 |
DC | DC | 3 |
LC | LC | 2 |
SF | SF | 2 |
GL | GL | 2 |
NY | NY | 2 |
ER | ER | 2 |
PW | PW | 2 |
DM | DM | 1 |
ZZ | ZZ | 1 |
FL | FL | 1 |
LE | LE | 1 |
VC | VC | 1 |
AN | AN | 1 |
OR | OR | 1 |
BL | BL | 1 |
MF | MF | 1 |
MP | MP | 1 |
KP | KP | 1 |
UM | UM | 1 |
250 values, rendered from /public/facets (keyless) — the same data as /v1/meta/facets. Enumerate it at run time rather than hard-coding this list.
Exact contractTypes, defaults, ranges, errors, edge cases
Technical detail — how the filter actually works.
countrystringcomma-separatedComma-separated ISO-3166 alpha-2.
exclude_countrystring2 parameters, generated from /openapi.json when this page was built. The spec is served from the running API and is the contract; if this table and the spec ever disagree, the spec is right and this is a bug — please say so with the thumbs-down below.
The country column on a listing is a resolved value, not a copy of anything the source sent. Sources supply a free-text location; a resolver maps that to an ISO code and, where it can, a city and a US state. When the text is ambiguous — a city name that exists in several countries, with nothing else to disambiguate it — the column is left null. It is present on roughly three quarters of live rows.
A row with a null country is unreachable by ?country=. It is not excluded by ?exclude_country=, because it does not match the code you are excluding — so the two parameters are not complements of each other. ?country=US and ?exclude_country=US do not partition the ledger; the unlocated rows are in neither of the first and in the second.
| What you want | Send | Note |
|---|---|---|
| One country | ?country=DE | Case-insensitive. |
| Several countries | ?country=DE,AT,CH | Comma-separated, no spaces. OR within the list. |
| Everywhere except one | ?exclude_country=US | Keeps rows with no country at all. |
| A country and a category | ?country=DE&category=Engineering | Different parameters AND together. |
| A US state | ?state=OH or ?state=OH,TX | US only. No equivalent for other countries' subdivisions. |
| A city | ?city=Berlin | Resolved city, thinner coverage than country. |
| Rows with no country | not directly addressable | Fetch unfiltered and read field_sources.location. |
Interactions worth knowing. country composes with everything, including require_fields — ?require_fields=salary&country=DE is a reasonable and common query, and the per-country table above is where you check whether it will return enough rows to be useful. Country-locked keys (the single-country SKU) are restricted to one code and are exempt from the record meter for job rows; on such a key a request for another country returns nothing and every response carries X-JOA-Country-Lock. See country-locked keys.
Performance: country is one of the cheapest filters here and narrowing by it is the first thing to try when a query is slow enough to risk the 503 timeout — particularly if you are also using q, title or description_contains, which are the most expensive things this API can be asked.
# Keyless — no account needed. Swap FR for any code in the list above. $ curl -s 'https://api.jobopportunitiesapi.org/public/jobs?country=FR&limit=2' \ | jq '.data[] | {title, company, city, country}' { "data": [ { "id": "b8813094-5483-4d42-8749-c15e4c05c230", "slug": "fireman-b8813094", "title": "Fireman", "company": "disney", "company_slug": "disney", "company_logo": "https://supabase-erioun.tzekos.eu/storage/v1/object/public/company-logos/logos/disney.png", "category": "Safety & Environment", "category_confidence": 0.95, "country": "FR", "location": "Marne la Vallee Cedex 4, France", "remote": "on_site", "remote_inferred": true, "posted_at": "2026-09-11T22:06:08Z", "first_seen_at": "2026-09-11T22:09:29Z", "last_verified_at": "2026-09-11T23:09:47Z", "status": "live", "closed_at": null, "closed_reason": null, "apply_url": "https://disney.wd5.myworkdayjobs.com/disneycareerdc/job/Marne-la-Vallee-Cedex-4-France/Fireman_DLP-0000454599", "source": "workday", "source_type": "ats", "provider_type": "employer_ats", "has_description": false, "field_sources": { "remote": "inferred", "employment_type": "absent", "category": "inferred", … 50 more lines
$ curl -s 'https://api.jobopportunitiesapi.org/public/jobs?country=DE,AT,CH&limit=3' \ | jq -r '.data[] | "\(.country) \(.company) \(.title)"' { "data": [ { "id": "8239d881-d520-4405-85c5-53c6d5c8e1ec", "slug": "agentforce-operations-account-executive-8239d881", "title": "Agentforce Operations Account Executive", "company": "salesforce", "company_slug": "salesforce", "company_logo": "https://supabase-erioun.tzekos.eu/storage/v1/object/public/company-logos/logos/salesforce.png", "category": "Sales", "category_confidence": 0.65, "country": "DE", "city": "Munich", "location": "Germany, Munich (5 Locations)", "remote": "on_site", "remote_inferred": true, "posted_at": "2026-09-11T22:06:08Z", "first_seen_at": "2026-09-11T22:09:29Z", "last_verified_at": "2026-09-11T22:56:13Z", "status": "live", "closed_at": null, "closed_reason": null, "apply_url": "https://salesforce.wd12.myworkdayjobs.com/external_career_site/job/Germany---Munich/Agentforce-Operations-Account-Executive_JR359678", "source": "workday", … 95 more lines
curl -s https://api.jobopportunitiesapi.org/public/coverage/countries \
| jq -r '.countries[] | "\(.country)\t\(.live)"' \
| sort -k2 -rn | head -20curl -s -H "Authorization: Bearer $JOA_KEY" \
'https://api.jobopportunitiesapi.org/v1/jobs?exclude_country=US&limit=5' \
| jq -r '.data[] | "\(.country // "—") \(.title)"'5Classification — category, seniority, employment type, remote
Four filters over what kind of job it is. Two of them are always our reading rather than the employer's statement, and the rows say so.
In depthWhy it exists, what it is not, what people get wrong
category and seniority are inferred from the job title by a classifier. They are useful and they are not quotations, which is why field_sources reports both as inferred on every row and why require_fields=category is a deliberate 422.
remote is the interesting one. A small minority of sources state a remote status in a field; for the rest we read it from the location text, the title, or the presence of a named workplace city. Both kinds are returned, and remote_inferred on every row says which you have. remote_confirmed=true restricts the result to the stated ones — a much smaller and much more defensible set.
employment_type and seniority both accept the literal value not_stated, which selects rows carrying none. That is a different question from omitting the filter, and it is the only way to ask it.
Exact contractTypes, defaults, ranges, errors, edge cases
categorystringcomma-separatedComma-separated. uncategorised selects rows with no confident classification.
remotestringremote, hybrid, on_site, or not_stated.
employment_typestringcomma-separatedComma-separated; not_stated selects rows with none.
senioritystringcomma-separatedComma-separated; not_stated selects rows with none.
exclude_categorystringremote_confirmedbooleantrue returns only listings whose remote status the SOURCE stated —
377,019 of 3,647,719 live rows (10.3%).
Without it you also receive the 3,266,412
(89.5%) where we inferred it from the location text, the
title, or the presence of a named workplace city.
6 parameters, generated from /openapi.json when this page was built. The spec is served from the running API and is the contract; if this table and the spec ever disagree, the spec is right and this is a bug — please say so with the thumbs-down below.
| Value to send | Label | Live rows |
|---|---|---|
Engineering | Engineering | 433,807 |
Healthcare | Healthcare | 417,340 |
Operations & Admin | Operations & Admin | 369,705 |
Sales | Sales | 285,380 |
Skilled Technician | Skilled Technician | 220,023 |
Retail | Retail | 205,551 |
uncategorised | Uncategorised | 193,737 |
Hospitality | Hospitality | 186,856 |
Finance | Finance | 178,734 |
Logistics & Transport | Logistics & Transport | 108,666 |
Marketing | Marketing | 96,891 |
Customer Support | Customer Support | 95,571 |
Education | Education | 84,290 |
Manufacturing | Manufacturing | 83,761 |
HR & Recruiting | HR & Recruiting | 71,011 |
Data & Analytics | Data & Analytics | 64,958 |
Consulting & Strategy | Consulting & Strategy | 62,233 |
Construction & Trades | Construction & Trades | 51,876 |
Legal & Compliance | Legal & Compliance | 47,103 |
Product | Product | 37,126 |
Design | Design | 36,332 |
Security | Security | 30,337 |
Science & Research | Science & Research | 28,734 |
Procurement | Procurement | 19,972 |
Safety & Environment | Safety & Environment | 14,326 |
25 values, rendered from /public/facets (keyless) — the same data as /v1/meta/facets. Enumerate it at run time rather than hard-coding this list.
The category vocabulary, with live counts.
| Value to send | Label | Live rows |
|---|---|---|
not_stated | Not stated | 1,883,742 |
Manager | Manager | 446,536 |
Entry | Entry | 323,849 |
Senior | Senior | 252,916 |
Mid | Mid | 161,894 |
Lead | Lead | 137,513 |
Director | Director | 104,002 |
Intern | Intern | 87,678 |
Executive | Executive | 26,190 |
9 values, rendered from /public/facets (keyless) — the same data as /v1/meta/facets. Enumerate it at run time rather than hard-coding this list.
| Value to send | Label | Live rows |
|---|---|---|
not_stated | Not stated | 3,175,770 |
Full-time | Full-time | 186,909 |
Contract | Contract | 30,637 |
Part-time | Part-time | 13,845 |
Temporary | Temporary | 11,118 |
Internship | Internship | 6,041 |
6 values, rendered from /public/facets (keyless) — the same data as /v1/meta/facets. Enumerate it at run time rather than hard-coding this list.
| Value to send | Label | Live rows |
|---|---|---|
on_site | On Site | 2,944,738 |
remote | Remote | 261,821 |
hybrid | Hybrid | 212,791 |
not_stated | Not stated | 4,970 |
4 values, rendered from /public/facets (keyless) — the same data as /v1/meta/facets. Enumerate it at run time rather than hard-coding this list.
category=uncategorised selects rows with no confident classification — the ones where the classifier scored below 0.6 and no category was published. Every row that does carry a category also carries category_confidence, which is therefore either null or at least 0.6.
6Provenance and serving — source, provider, quality, poster type, status
Filters over where a row came from and whether it is served by default. This is where the editorial policy is exposed as parameters.
In depthWhy it exists, what it is not, what people get wrong
source_type- The provenance bucket —
ats,career_site,public_agency. The legacy spellingsemployer_ats,governmentanddirectare accepted and mapped.aggregatorandagencyare reserved and match nothing. provider- The exact system:
greenhouse,workday,company_site. Finer thansource_type. Up to twelve values; an unknown one is a 422. statuslive(default),closed, orany. Paid endpoints only.qualityallre-admits rows held back by reversible doubt. It never re-admits rows removed for breaching the employer-direct guarantee. Paid endpoints only.include_poster_type- Re-admits staffing-agency and job-board postings, which are excluded by default. Paid endpoints only.
require_fields- Only rows where every named field was published by the source. Adds a
completenessblock.
The distinction between quality and include_poster_type is worth holding onto. quality=all is about our confidence in a row — a future posted_at, a missing apply URL. include_poster_type is about who posted it. Different questions, different parameters, and neither of them can re-admit a row that fails the employer-direct promise.
Exact contractTypes, defaults, ranges, errors, edge cases
statusstringdefault liveliveclosedanylive (default) returns open vacancies. closed returns roles that
have left their source. any returns both; every row carries
status. Paid endpoints only.
include_poster_typestringcomma-separatedstaffingjobboardallRe-admits vacancies whose poster is a staffing agency or a job board. Excluded by DEFAULT: the posting is real and the apply link is the poster's own, but the poster is not the employer, and one agency with 18,000 listings can flood a category until search stops being useful. Comma-separated. Paid endpoints only. An unrecognised value is a 422.
qualitystringallall re-admits rows we have GATED -- reversible doubt, such as a
future posted_at or a missing apply URL. It never re-admits rows we
have REMOVED: those breach the employer-direct guarantee and no query
parameter may opt back into it. Each returned row carries its verdict,
its rule id and the matched host. Paid endpoints only.
providerstringcomma-separatedComma-separated list of the exact source systems to include, e.g.
greenhouse,lever,workday. This is the ATS or board a vacancy came
from, finer than source_type which buckets them. A name we do not
publish is a 422, never a silently empty page. Up to 12 values.
exclude_providerstringSame vocabulary as provider, removed instead of kept.
source_typestringcomma-separatedComma-separated provenance filter. The legacy spellings
employer_ats, government and direct are still accepted and map
onto ats, public_agency and career_site.
Filter values are matched case-insensitively: category=engineering
and category=Engineering are the same query. Enumerate the legal
values with /v1/meta/facets.
exclude_source_typestringrequire_fieldsstringcomma-separatedComma-separated. Returns only rows where EVERY named field is
published in field_sources — a value the source carried, never one
we derived. The response then also contains a completeness object
saying how many live rows carry each of them.
This is the answer to "only 6.3% of your rows have a
salary". They do — and require_fields=salary returns
229,376 rows of which 100% carry a figure an employer
actually wrote, with no estimate anywhere in the response. Check the
per-country split at /public/coverage/countries before you spend a
record.
category and seniority are refused with 422: both are read off the
job title by our classifier, so they are inferred by construction and
no row can ever satisfy them. An empty page would look like a coverage
problem; the error says what it is.
source_type is accepted and currently matches nothing — the per-row
classification exists in the schema and no live row carries one yet. The
completeness block reports that as a count rather than leaving you to
infer it from an empty page.
8 parameters, generated from /openapi.json when this page was built. The spec is served from the running API and is the contract; if this table and the spec ever disagree, the spec is right and this is a bug — please say so with the thumbs-down below.
| Value to send | Label | Live rows |
|---|---|---|
career_site | Company career site | 1,742,168 |
ats | Employer ATS | 1,671,874 |
public_agency | Public employment agency | 10,278 |
3 values, rendered from /public/facets (keyless) — the same data as /v1/meta/facets. Enumerate it at run time rather than hard-coding this list.
With quality=all, each re-admitted row carries its verdict, the id of the rule that matched and the host that triggered it — so you can judge the gate's decision rather than take it on trust. That is the intended use: not to get more rows, but to audit which rows were held and why.
7Company — slug and domain
Filter by company slug, or by bare domain. The domain is the join key you already have in a CRM, and it is the stabler of the two.
In depthWhy it exists, what it is not, what people get wrong
company takes slugs — the same values that appear as company_slug on a job row and in /company/<slug> URLs on the website. company_domain takes bare domains: stripe.com, not https://stripe.com/careers, though a value with a scheme and a path is tolerated and reduced to the bare domain.
Prefer the domain where you have one, for two reasons. It is the identifier your own systems already hold, so no mapping table is needed; and company slugs are not currently guaranteed stable across refreshes. About 37% of companies carry a domain, so “where you have one” is doing real work in that sentence.
Exact contractTypes, defaults, ranges, errors, edge cases
companystringcomma-separatedComma-separated company slugs.
company_domainstringcomma-separatedComma-separated bare domains, e.g. stripe.com,figma.com — no scheme
and no path. The join key you already have in a CRM. 37.2% of
companies carry a domain; the rest can never match this filter.
exclude_company_domainstringSame vocabulary as company_domain, removed instead of kept.
3 parameters, generated from /openapi.json when this page was built. The spec is served from the running API and is the contract; if this table and the spec ever disagree, the spec is right and this is a bug — please say so with the thumbs-down below.
limitintegerdefault 251–200offsetinteger0–100000cursorstringThe next_cursor from the previous page. Overrides offset.
countrystringorg_typestringsource_typestringhas_websitebooleanOnly companies whose domain we fetched and found their own name on. An absent website means we could not prove one, not that they have none.
include_discoveredbooleanAlso return the companies we watched hiring on their own careers page
under a source we do not redistribute. They arrive with
open_roles: 0 and a non-zero own_site_roles, and careers_url
points at the page we saw them on. Useful for enrichment; not
inventory.
qstring9 parameters, generated from /openapi.json when this page was built. The spec is served from the running API and is the contract; if this table and the spec ever disagree, the spec is right and this is a bug — please say so with the thumbs-down below.
curl -s -H "Authorization: Bearer $JOA_KEY" --get \
https://api.jobopportunitiesapi.org/v1/jobs \
--data-urlencode 'company_domain=stripe.com,figma.com,linear.app' \
--data-urlencode 'limit=10' \
| jq -r '.data[] | "\(.company)\t\(.title)"'8Text — q, title, description_contains
Three full-text filters over different parts of a row. q deliberately does not search the advert body; description_contains is the one that does.
In depthWhy it exists, what it is not, what people get wrong
q- Full text over title, company name and location. Not the description.
title- Full text over the job title only.
title_exclude- Drops rows whose title matches. Same matching as
title. description_contains- Full text over the advert body. Implies
has_description=true, because it can only match rows that have one.
That q does not search the description is a design decision, not a limitation. The description is two and a half kilobytes of prose per row, and searching it makes a query dramatically more expensive; folding it into the default search would make every simple query pay that cost. So the cheap search is the default and the expensive one is opt-in and named.
Exact contractTypes, defaults, ranges, errors, edge cases
has_descriptionbooleantrue returns only the 2,937,340 live rows (80.5%) that carry a description.
include_descriptionbooleanReturn the full advert text in a description field. Off by default:
descriptions average 2,581 bytes, so a 200-row page would be a 516 KB
response nobody asked for. With this on, limit may not exceed 50 —
a larger request is refused with 422 rather than quietly clamped.
qstringFull-text over title, company name and location — NOT the description.
That is deliberate, not a limitation: use description_contains for
the advert body. Stemming is off (simple dictionary), so q=engineer
does not match engineering.
titlestringFull-text over the job title only, e.g. ?title=engineer. It ANDs
with every other filter, including ?description_contains=kubernetes
over the advert body — but the two full-text filters together are the
most expensive query this API can be asked, because both indexes are
GIN and the matching rows still have to be fetched to be ordered by
posted_at. Send them together on a narrow country or category, not on
the whole ledger.
title_excludestringDrop rows whose title matches these words. Same matching as title.
description_containsstringFull-text over the advert body. Only ever matches rows that have one,
so it implies has_description=true.
6 parameters, generated from /openapi.json when this page was built. The spec is served from the running API and is the contract; if this table and the spec ever disagree, the spec is right and this is a bug — please say so with the thumbs-down below.
9Salary
has_salary selects rows that have one; min_salary and max_salary bound a normalised annual EUR figure that only a small share of rows carry.
In depthWhy it exists, what it is not, what people get wrong
has_salary=true (and its alias structured) returns only rows whose salary the source published. has_salary=any also includes figures we read out of the advert text. AI estimates are never included under any value — they are refused by the query that builds the data, not by a filter.
min_salary and max_salary bound salary_min_annual_eur, which is null wherever the period or currency could not be recognised. So they select only rows with a normalisable figure — a narrow filter by nature, not a broken one.
Exact contractTypes, defaults, ranges, errors, edge cases
has_salarystringtruestructuredanytrue (and structured) returns only rows whose salary the SOURCE published — the meaning this parameter has always had, kept so that adding derived salaries does not change the results of a query you already ship. any also includes figures we read out of the advert text (salary_source: parsed_description, reported as inferred). AI estimates are never published under any value.
min_salarynumberLower bound on salary_min_annual_eur. Selects ONLY rows with
structured salary we could normalise — 2.0% of the ledger — so this
is a narrow filter by nature, not a broken one.
max_salarynumberUpper bound on salary_min_annual_eur. Same 2.0% caveat as min_salary.
3 parameters, generated from /openapi.json when this page was built. The spec is served from the running API and is the contract; if this table and the spec ever disagree, the spec is right and this is a bug — please say so with the thumbs-down below.
For a clean set with a real employer-published figure on every row, use require_fields=salary rather than has_salary=true — it makes the guarantee explicit in the response and adds the completeness block. Detail.
10Time — posted_after and verified_after
Two different questions: what was posted since a date, and what was re-confirmed at its source since a date.
In depthWhy it exists, what it is not, what people get wrong
posted_after filters on the source's own posting date, which many sources do not state — so it silently excludes every row with no posted_at. verified_after filters on last_verified_at, which is on every row, and answers “what have you re-confirmed since yesterday”.
Exact contractTypes, defaults, ranges, errors, edge cases
posted_afterstringDate or RFC3339 timestamp.
verified_afterstringOnly rows re-confirmed at their source since this instant.
2 parameters, generated from /openapi.json when this page was built. The spec is served from the running API and is the contract; if this table and the spec ever disagree, the spec is right and this is a bug — please say so with the thumbs-down below.
Both accept RFC3339 (2026-08-01T00:00:00Z) and posted_after also accepts a bare date (2026-08-01). An unparseable value is a 422, not an ignored parameter.
If you need every row to carry a date — for a backfill, or a chart — use first_seen_at, which is populated on 100% of rows, rather than posted_at. Note that /v1/jobs orders by posted_at DESC NULLS LAST, id DESC, so undated rows sort last rather than being excluded.
11Controlled vocabularies — never hard-code these
Providers, categories, source types and country codes are enumerable at run time. A value that has left the list is a 422, not an empty page.
In depthWhy it exists, what it is not, what people get wrong
This is the single cheapest mistake to avoid on this API, and it has already cost somebody real time. On 2026-08-15 three provider names left /public/providers. The next morning the first external evaluator this product ever had copied ?exclude_provider=eures out of the documentation and got a 422. The API was right, the vocabulary check was right, and the documentation was the only thing that was wrong.
| Vocabulary | Enumerate from | Key? |
|---|---|---|
provider | /public/providers or /v1/meta/providers | No / yes |
category | /public/facets → family | No |
country | /public/facets → country, or /public/coverage/countries | No |
city | /public/facets → city | No |
employment_type | /public/facets → employment | No |
seniority | /public/facets → seniority | No |
remote | /public/facets → remote | No |
source_type | /public/facets → source_type | No |
plan | /public/plans | No |
Every one of these is keyless, so there is no reason not to read them at start-up and cache them for an hour.
Exact contractTypes, defaults, ranges, errors, edge cases
import json, urllib.request
A = "https://api.jobopportunitiesapi.org"
def vocabulary(group):
"""Legal values for one facet, read from the API rather than remembered."""
with urllib.request.urlopen(f"{A}/public/facets", timeout=30) as r:
facets = json.load(r)["data"]
return {o["value"] for o in facets.get(group, [])}
categories = vocabulary("family")
wanted = "Engineering"
if wanted not in categories:
raise SystemExit(f"{wanted!r} is not a category today; have: {sorted(categories)[:8]} …")12The complete table
Every parameter /v1/jobs accepts, in the order the specification lists them, with nothing omitted.
In depthWhy it exists, what it is not, what people get wrong
The sections above group these by what they do, which is how you find one. This is the flat list, which is how you check you have not missed one. Both are generated from the same specification.
Exact contractTypes, defaults, ranges, errors, edge cases
limitintegerdefault 251–200cursorstringThe next_cursor from the previous page.
statusstringdefault liveliveclosedanylive (default) returns open vacancies. closed returns roles that
have left their source. any returns both; every row carries
status. Paid endpoints only.
include_poster_typestringcomma-separatedstaffingjobboardallRe-admits vacancies whose poster is a staffing agency or a job board. Excluded by DEFAULT: the posting is real and the apply link is the poster's own, but the poster is not the employer, and one agency with 18,000 listings can flood a category until search stops being useful. Comma-separated. Paid endpoints only. An unrecognised value is a 422.
qualitystringallall re-admits rows we have GATED -- reversible doubt, such as a
future posted_at or a missing apply URL. It never re-admits rows we
have REMOVED: those breach the employer-direct guarantee and no query
parameter may opt back into it. Each returned row carries its verdict,
its rule id and the matched host. Paid endpoints only.
categorystringcomma-separatedComma-separated. uncategorised selects rows with no confident classification.
countrystringcomma-separatedComma-separated ISO-3166 alpha-2.
citystringstatestringcomma-separatedComma-separated two-letter US state codes, e.g. OH or OH,TX.
Absent where we could not establish the state from the source;
deliberately absent for ambiguous city names, so this filter
under-reports rather than placing a job in the wrong state.
remotestringremote, hybrid, on_site, or not_stated.
employment_typestringcomma-separatedComma-separated; not_stated selects rows with none.
senioritystringcomma-separatedComma-separated; not_stated selects rows with none.
providerstringcomma-separatedComma-separated list of the exact source systems to include, e.g.
greenhouse,lever,workday. This is the ATS or board a vacancy came
from, finer than source_type which buckets them. A name we do not
publish is a 422, never a silently empty page. Up to 12 values.
exclude_providerstringSame vocabulary as provider, removed instead of kept.
source_typestringcomma-separatedComma-separated provenance filter. The legacy spellings
employer_ats, government and direct are still accepted and map
onto ats, public_agency and career_site.
Filter values are matched case-insensitively: category=engineering
and category=Engineering are the same query. Enumerate the legal
values with /v1/meta/facets.
exclude_source_typestringcompanystringcomma-separatedComma-separated company slugs.
exclude_categorystringexclude_countrystringremote_confirmedbooleantrue returns only listings whose remote status the SOURCE stated —
377,019 of 3,647,719 live rows (10.3%).
Without it you also receive the 3,266,412
(89.5%) where we inferred it from the location text, the
title, or the presence of a named workplace city.
has_salarystringtruestructuredanytrue (and structured) returns only rows whose salary the SOURCE published — the meaning this parameter has always had, kept so that adding derived salaries does not change the results of a query you already ship. any also includes figures we read out of the advert text (salary_source: parsed_description, reported as inferred). AI estimates are never published under any value.
has_descriptionbooleantrue returns only the 2,937,340 live rows (80.5%) that carry a description.
require_fieldsstringcomma-separatedComma-separated. Returns only rows where EVERY named field is
published in field_sources — a value the source carried, never one
we derived. The response then also contains a completeness object
saying how many live rows carry each of them.
This is the answer to "only 6.3% of your rows have a
salary". They do — and require_fields=salary returns
229,376 rows of which 100% carry a figure an employer
actually wrote, with no estimate anywhere in the response. Check the
per-country split at /public/coverage/countries before you spend a
record.
category and seniority are refused with 422: both are read off the
job title by our classifier, so they are inferred by construction and
no row can ever satisfy them. An empty page would look like a coverage
problem; the error says what it is.
source_type is accepted and currently matches nothing — the per-row
classification exists in the schema and no live row carries one yet. The
completeness block reports that as a count rather than leaving you to
infer it from an empty page.
include_descriptionbooleanReturn the full advert text in a description field. Off by default:
descriptions average 2,581 bytes, so a 200-row page would be a 516 KB
response nobody asked for. With this on, limit may not exceed 50 —
a larger request is refused with 422 rather than quietly clamped.
posted_afterstringDate or RFC3339 timestamp.
verified_afterstringOnly rows re-confirmed at their source since this instant.
qstringFull-text over title, company name and location — NOT the description.
That is deliberate, not a limitation: use description_contains for
the advert body. Stemming is off (simple dictionary), so q=engineer
does not match engineering.
titlestringFull-text over the job title only, e.g. ?title=engineer. It ANDs
with every other filter, including ?description_contains=kubernetes
over the advert body — but the two full-text filters together are the
most expensive query this API can be asked, because both indexes are
GIN and the matching rows still have to be fetched to be ordered by
posted_at. Send them together on a narrow country or category, not on
the whole ledger.
title_excludestringDrop rows whose title matches these words. Same matching as title.
description_containsstringFull-text over the advert body. Only ever matches rows that have one,
so it implies has_description=true.
company_domainstringcomma-separatedComma-separated bare domains, e.g. stripe.com,figma.com — no scheme
and no path. The join key you already have in a CRM. 37.2% of
companies carry a domain; the rest can never match this filter.
exclude_company_domainstringSame vocabulary as company_domain, removed instead of kept.
min_salarynumberLower bound on salary_min_annual_eur. Selects ONLY rows with
structured salary we could normalise — 2.0% of the ledger — so this
is a narrow filter by nature, not a broken one.
max_salarynumberUpper bound on salary_min_annual_eur. Same 2.0% caveat as min_salary.
34 parameters, generated from /openapi.json when this page was built. The spec is served from the running API and is the contract; if this table and the spec ever disagree, the spec is right and this is a bug — please say so with the thumbs-down below.
This page was rendered 12 September 2026, 00:26 UTC. Every figure on it comes from the endpoint named beside it, and every published request is re-sent against the live API before this site is allowed to build. If something here is wrong, the thumbs-down above reaches a person.