code in the body when there is one. Retry only the statuses under Retry strategy.
Check the status code, then the envelope
Three layers can reject a request, and each returns a different body shape:Status codes
Your wallet is never charged for a failed request. Per-job endpoints (search, feed, company jobs) charge only for delivered jobs; if that charge can’t be covered, the jobs are withheld and you get
402. Flat-rate endpoints such as geocode refund their up-front debit on any 4xx or 5xx. See Billing.
Gateway errors
These are returned before the request reaches an endpoint, as plain JSON, notproblem+json.
401 Unauthorized
The response also setsWWW-Authenticate: ApiKey realm="api.jobo.world". On /api/auto-apply/* paths the body also carries api_version.
402 Payment Required
Your remaining included jobs plus your wallet balance cannot cover the request. The response also setsX-Credits-Required and X-Credits-Balance:
quota_remaining is the number of included jobs you have left. See Billing.
Three other surfaces return the same 402 with a different envelope:
Match on the status and the
error or title string, which are stable. The prose in detail and message can change at any time.
403 Forbidden
This key or account may not use the endpoint. A403 is never a plan check. Keys have no per-endpoint scopes, and Feed works on any plan: without Unlimited, it uses included jobs, then the pay-as-you-go rate.
Four situations produce a 403:
Playground tokens are short-lived tokens from the public jobo.world playground. Their refusal:
404, not 403: a saved feed id owned by another account, and a sandbox key calling an endpoint the sandbox does not serve (code: not_available_in_sandbox).
429 Too Many Requests
The response also setsRetry-After and the X-RateLimit-* headers. group names the rate-limit group you exhausted, and detail names the window, the group, and the limit.
api_version. The 429 for repeated authentication failures has no code.
Endpoint errors
Validation and conflict errors follow RFC 7807 withContent-Type: application/problem+json.
Stable error codes
code is a stable, lowercase string. Branch on it, not on title or detail, which are prose and may be reworded.
Not every error carries a
code. For requests made with a customer API key:
400,409,413,415,422, and429carry one, except the authentication-failure429.403and404carry one only for access gates (company_enrichment_not_enabled,auto_apply_not_enabled), the sandbox (not_available_in_sandbox), and other Auto Apply domain errors.401and402never do.
code, match on the status and the error or title string.
For customer API keys, endpoint errors with status 400, 409, 413, 415, or 422 also carry docs_url, a link to the relevant docs page. The gateway 429 has code but no docs_url.
Unhandled errors
An unexpected server failure returns a minimal envelope. Itsdetail field is filled only outside production, so never depend on it.
503 with the same { "message" } envelope and Retry-After: 2. Retry it.
Auto Apply errors
Auto Apply is coming soon; you can build against it today with a sandbox key, no approval needed. Its errors areproblem+json with a stable code, and every Auto Apply response, errors included, carries api_version.
POST /api/auto-apply/applications refuses before queueing anything when:
With a live key, create runs the three
403 checks in the order under Access: agreement, enablement, business review. A sandbox key skips all three.
The answers endpoint adds 400 validation_failed (per-field errors, nothing consumed), 409 invalid_state, and 409 stale_correction_round. See The application loop. An application that fails after being queued reports a failure.code instead, such as answers_timeout when a step’s answer window expired.
Silent failures
These three cases return a success status, so they look like empty or unfiltered data rather than errors.An unrecognized filter value returns zero results, not a 400
An unrecognized filter value returns zero results, not a 400
Enum filters are matched literally against the index. An unknown value matches nothing and returns Send the canonical values listed under Enums. Jobs where the field is null never match a filter on that field.On
200 with total: 0, never 400.POST /api/jobs/search, array values are not comma-split: ["full-time,contract"] is one literal value and matches nothing. Send ["full-time", "contract"].Occupation filters behave the same way. Take their values from GET /api/occupations:job_categoriesandjob_subcategoriesmatch the taxonomy name, ignoring case (Healthcareorhealthcare, notHealth care).occupation_idsmatches the occupation id. An occupation name such asRegistered Nursereturns200withtotal: 0.- A subcategory whose name contains a comma (
Film, TV & Stage Production) is split by the comma-separatedGETparameter and matches nothing. Send it onPOST /api/jobs/search.
An unknown parameter is ignored, not rejected
An unknown parameter is ignored, not rejected
Unrecognized parameters are ignored, so a typo returns plausible but unfiltered results. Feed requests ignore unknown fields without a warning. Unknown
?is_remote=true filters nothing, because the remote filter is work_model=remote.Search reports unknown query parameters and body fields as an unknown_request_fields warning, in the body and the X-Jobo-Warnings header:include_facets names are also dropped silently: ?include_facets=work_models (plural typo) returns no facets, not the default set.Geocode returns 200 for input it cannot resolve
Geocode returns 200 for input it cannot resolve
Placeholder and unresolvable input is not an error. Check Only a missing or whitespace-only
succeeded, not the status code.location parameter returns 400. See Resolve locations.Retry strategy
Retry only429, 500, and 503, plus Auto Apply’s 409 idempotency_key_in_flight. Every other status means the request must change.
1
Honor Retry-After
On
429 and 503, wait at least the number of seconds in the Retry-After header. Never hardcode a delay.2
Back off exponentially with jitter
On
500, or a 503 without Retry-After, wait 1 second and double the wait each time, up to 30 seconds. Add ±25% jitter so concurrent clients don’t retry in lockstep.3
Cap attempts
Stop after 3–5 attempts. A persistent
429 means your sustained rate is too high: slow the pipeline down instead of retrying harder.4
Restart a cursor instead of retrying it
A cursor error fails on every retry. On
400 invalid_cursor, restart the scan. On 409 feed_cursor_restart_required, start again with {"stable_scan": true, "batch_size": 1000}. On 409 feed_changed, restart the saved-feed scan. See Pagination.5
Give feed requests enough time
Set a response timeout of at least 120 seconds on feed calls. The feed can work for up to 90 seconds before it returns a retryable
503.6
Send an idempotency key on writes
A retried write that reuses the key cannot double-charge or duplicate work. See Idempotency.
429 and 503 for you, but not 500: catch JoboServerError and apply the backoff above. Other errors raise a typed exception that carries the problem code; see Client libraries.
Getting support
Every response carries anx-correlation-id header. Quote it when you contact support@jobo.world: it identifies the exact request in our logs.

