Skip to content
API v1 preview — endpoints and fields may change before general availability.

Errors

What does each error code mean, and should I retry?

View .md

BetterJobs uses HTTP status codes for request-level failures and one JSON error shape for all of them. Provider failures are different: they never fail your request. You get 200 with partial results and pay only for what was returned.

Every error response has the same shape:

{
"error": {
"type": "billing_error",
"code": "insufficient_credits",
"message": "This request needs at least 25 credits; 3 remain.",
"credits_needed": 25,
"upgrade_url": "https://betterjobs.cc/pricing",
"doc_url": "https://docs.betterjobs.cc/platform/errors/#insufficient_credits",
"request_id": "req_6Ek5FuM9wX"
}
}
Field Always present What it is
type Yes Broad class: invalid_request_error, authentication_error, billing_error, permission_error, not_found_error, conflict_error, rate_limit_error or api_error.
code Yes The specific error. Branch on this.
message Yes Human-readable explanation. It can change. Never parse it.
doc_url Yes Link to this code’s row below.
request_id Yes Same as the X-Request-Id header. Quote it to support.
param On invalid_request, unknown_filter The offending field, for example filters.job_title_or.
credits_needed On insufficient_credits Credits the request needs.
required_plan On plan_required The plan that includes the feature or provider.
upgrade_url On insufficient_credits, plan_required Where to top up or upgrade.

Each row has an anchor. doc_url in an error body links straight to it.

CodeHTTPCauseFixRetryExtra fields
invalid_request400The body or a parameter is malformed or out of range.Read error.param and error.message, then fix the request.after fixparam
unknown_filter400A field in filters does not exist. Unknown fields are rejected, never ignored.Check the name against the filter list. error.param names the bad field.after fixparam
unauthorized401Missing, malformed or revoked API key.Send Authorization: Bearer bj_live_... (or bj_test_...).after fix—
insufficient_credits402Not enough credits left for this request.Top up or upgrade at error.upgrade_url, or lower waterfall.max_credits.after top-upcredits_needed, upgrade_url
plan_required403Your plan does not include this feature or provider.Upgrade to error.required_plan, or remove the provider from waterfall.providers.norequired_plan, upgrade_url
not_found404The id or domain does not exist.Check the id prefix (job_, srch_, wat_, evt_) and value.no—
idempotency_conflict409The same Idempotency-Key was reused with a different body.Use a new key for a new request.after fix—
rate_limited429Too many requests in the current window.Wait Retry-After seconds. Watch RateLimit-Remaining.after Retry-After—
provider_timeout200*A provider missed waterfall.timeout_ms.Not an HTTP error. You get 200 with metadata.status: partial and the provider in metadata.providers.failed. You pay only for returned jobs.yes—
provider_error200*A provider returned an error.Not an HTTP error. You get 200 with metadata.status: partial and the provider in metadata.providers.failed. You pay only for returned jobs.yes—
internal_error500Something failed on our side.Retry with the same Idempotency-Key. You will not be charged twice.yes—

* Not an HTTP error. Reported inside a 200 response as metadata.status: partial.

A slow or failing provider never turns your request into an error. BetterJobs drops it, returns what the other sources found, and says so in metadata:

{
"data": ["..."],
"next_cursor": null,
"metadata": {
"request_id": "req_9Qa4NvB2sE",
"status": "partial",
"credits_charged": 1,
"providers": {
"tried": ["betterjobs", "techmap", "theirstack", "coresignal"],
"hit": ["betterjobs", "techmap", "theirstack"],
"failed": [{ "provider": "coresignal", "code": "provider_timeout" }],
"contributions": { "betterjobs": 1, "techmap": 0, "theirstack": 0 }
}
}
}

Illustrative and shortened from the spec example.

  • metadata.status is complete or partial. The HTTP status is 200 either way.
  • metadata.providers.failed lists each dropped provider with a code: provider_timeout (it missed waterfall.timeout_ms) or provider_error (it returned an error).
  • You are billed only for jobs in the response. A failed provider costs nothing.

What to do with a partial result depends on the job:

  • Good enough. Use it. metadata.providers.hit shows which sources did answer.
  • Need full coverage. Retry the same request later. Jobs you already paid for come back free, so a retry costs only the new jobs the missing provider adds.
  • Timeouts again and again. Raise waterfall.timeout_ms (up to 30000), or check the provider’s live status with GET /v1/providers. See Provider status.

Async searches report the same thing as status: partial on the search. See Async searches.

Situation Retry? How
429 rate_limited Yes Wait Retry-After seconds, then retry. See Rate limits.
500 internal_error Yes Back off, then retry with the same Idempotency-Key. You are not charged twice.
Network error or timeout, no response Yes Retry with the same Idempotency-Key. Without one, you may create a second async search or watch.
metadata.status: partial Optional Retry later if you need the missing provider. Already-paid jobs are free.
402 insufficient_credits After top-up Top up at error.upgrade_url, or lower waterfall.max_credits.
400, 401, 409 After a fix The same request fails the same way. Fix it first.
403 plan_required, 404 not_found No Change the request or the plan. Retrying does not help.

GET and DELETE requests are safe to retry as they are. For POST, send an Idempotency-Key so a retry can never double up. See Idempotency.

The backoff helper on the rate limits page implements this table for 429 and 5xx.

Terminal window
# -i prints the status line and headers, including X-Request-Id
curl -i https://api.betterjobs.cc/v1/jobs/search \
-H "Authorization: Bearer $BETTERJOBS_API_KEY" \
-H "BetterJobs-Version: 2026-10-01" \
-H "Content-Type: application/json" \
-d '{ "filters": { "job_title_or": ["Head of RevOps"] } }'
# HTTP/2 400 ... "code": "unknown_filter", "param": "filters.job_title_or"