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
Two distinct causes share this status. Tell them apart by theerror string.
Wallet balance too low — also sets X-Credits-Required and X-Credits-Balance:
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.
There is no
403 for “your plan does not include this endpoint”, and API keys have no scopes or per-endpoint permissions. Plan-gated access returns 402.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 a contract preview and is not accepting traffic.POST /api/auto-apply/applications is deployment-gated and returns:
api_version.
At launch, domain errors will use problem+json with a stable code. Planned conflict codes include webhook_secret_not_configured, idempotency_key_reuse, idempotency_key_in_flight, ambiguous_ats, and not_cancelable; 422 adds job_apply_url_missing and unsupported_ats.
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.
JoboAuthenticationError, JoboRateLimitError, JoboValidationError, and JoboServerError, all deriving from JoboError. 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.

