Check the status code, then the envelope
The API returns three different error shapes depending on which layer rejected the request. Write your error handling against the status code first, and only then read the body.Status codes
Credits are never charged for a failed request. Per-delivered-unit endpoints (search, feed, company jobs) run a balance precheck and only debit after a successful response is built. Flat-rate endpoints such as geocode debit up front and are automatically refunded on any
4xx or 5xx. See Billing.Gateway errors
These come from middleware, before your request reaches an endpoint. They are plain JSON, notproblem+json.
401 Unauthorized
Also setsWWW-Authenticate: ApiKey realm="api.jobo.world".
402 Payment Required
One condition produces this status on the API: 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 your included jobs remaining; see
Billing.
The same condition surfaces in two other places with the same status but a
different envelope. Dashboard Export and Outbound (portal APIs, not this host)
return 402 with the error value "insufficient_credits" (lowercase) and a
sentence-form message. The MCP server relays a
problem+json document whose title is "Insufficient credits" and whose
detail includes a top-up link. In every case, match on the status and the
error / title string — those values are stable — and treat the prose
detail / message as rewordable at any time.
403 Forbidden
Only two situations produce a403:
POST /api/jobs/feed/managedcalled with anything other than a customer API key — master, marketplace, and sandbox keys are rejected.- A sandbox token used against a product it was not minted for.
API keys have no per-endpoint product scopes. Search and Feed can run from
included jobs or at the pay-as-you-go rate; the Unlimited plan is a billing option
, not a requirement to call Feed.
429 Too Many Requests
Also setsRetry-After and the X-RateLimit-* headers. group names the rate-limit group you exhausted — Default, JobSearch, JobFeed, Geocode, AutoApply, or AutoApplyManagement.
Endpoint errors
Validation and conflict errors follow RFC 7807 withContent-Type: application/problem+json.
Stable error codes
code is a stable, lowercase, machine-readable string. Branch on it rather than on title or detail, which are prose and may be reworded at any time.
Responses carrying a
code also carry docs_url, pointing at the relevant page here.
Unhandled errors
An unexpected server-side failure returns a minimal envelope. Adetail field is only populated outside production, so never depend on it.
Auto Apply errors
Auto Apply is an invite-only beta. Its errors useproblem+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:
The answers endpoint adds
400 validation_failed (per-field errors, nothing consumed), 409 invalid_state, and 409 stale_correction_round — see The application loop. Applications that fail after being queued report a failure.code instead, including answers_timeout when a step’s answer window expired.
Silent failures
Three cases return a successful status with no data and no error. They account for most “the API returned nothing” reports.An unrecognised filter value returns zero results, not a 400
An unrecognised filter value returns zero results, not a 400
Enum filters are matched literally against the index. There is no validation and no Send the canonical values listed under Enums. Jobs where the field is null are excluded from any filter on that field.On
400 — an unknown value matches nothing, and you get 200 with total: 0.POST /api/jobs/search, array values are not comma-split: ["full-time,contract"] is one literal value and matches nothing. Use ["full-time", "contract"].An unknown query parameter is ignored, not rejected
An unknown query parameter is ignored, not rejected
Unrecognised query parameters are silently dropped, so a typo returns unfiltered results that look plausible.
?is_remote=true is not a parameter — the filter is work_model=remote — so that request succeeds while filtering nothing.The same applies to include_facets: unknown names are dropped, so ?include_facets=work_models (plural typo) yields no facets rather than 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 the Only a missing or whitespace-only
succeeded field, not the status code.location parameter is a real 400. See Resolve locations.Retry strategy
Retry only429, 500, and 503. Everything else is a request you must change.
1
Honour Retry-After
On
429 and 503, wait exactly the number of seconds in the Retry-After header. Never hardcode a delay.2
Back off exponentially with jitter
For
500, start at 1s and double, capping around 30s. Add ±25% jitter so concurrent clients do not retry in lockstep.3
Cap attempts
Stop after 3–5 tries. Persistent
429 means your sustained rate is too high — slow the pipeline down rather than retrying harder.4
Treat a feed 409 as a restart, not a retry
On
feed_cursor_restart_required, discard the cursor and begin pagination again with {"stable_scan": true, "batch_size": 1000}. Retrying the same cursor fails forever.5
Send an idempotency key on writes
So a retry cannot double-charge or duplicate work. See Idempotency.
429 and 503 are retried with bounded backoff honouring Retry-After, and everything else raises a typed exception — JoboAuthenticationError, JoboPermissionError, JoboNotFoundError, JoboRateLimitError, JoboValidationError, JoboCursorRestartRequiredError, and JoboServerError, all deriving from JoboError and carrying the problem code. See Client libraries.
Getting support
Every response carries anx-correlation-id header. Quote it when contacting support@jobo.world — it identifies the exact request in our logs.

