Content-Type: application/problem+json and a non-2xx HTTP status. The body always
includes a machine-readable code so you can branch on the failure without parsing prose.
Error envelope
A URI identifying the error type. Points at the docs section for the code.
A short, human-readable summary of the error type (e.g.
Out of credits).The HTTP status code, repeated in the body.
The stable, machine-readable error code. Branch on this. See the table below.
A human-readable explanation specific to this occurrence. For
invalid_request it carries
the validation message(s).Present on
out_of_credits — your current balance, so you can tell the user how short they
are.Example: out_of_credits
Error codes
| Code | HTTP | Title | Retry? | Meaning |
|---|---|---|---|---|
invalid_request | 400 | Invalid request | No — fix the request | Missing/unknown field, or a value over its limit. |
invalid_key | 401 | Invalid API key | No — fix the key | Missing, malformed, or revoked API key. |
out_of_credits | 402 | Out of credits | No — top up | Balance below the link’s cost. Includes balance. |
forbidden_scope | 403 | Forbidden scope | No — grant the scope | The key lacks the resolve scope. |
not_found | 404 | Not found | No | Unknown job id, or a job that belongs to another key. |
link_removed | 410 | Link removed | No | The destination was removed by the shortener. |
unsupported_link | 422 | Unsupported link | No — use a supported provider | The URL isn’t a supported shortener (or had no URL). |
rate_limited | 429 | Rate limited | Yes — after Retry-After | Per-minute limit hit. Includes Retry-After. |
quota_exceeded | 429 | Daily quota exceeded | Yes — after Retry-After | Daily quota reached. Resets at UTC 00:00. Includes Retry-After. |
resolve_failed | 502 | Resolve failed | Maybe — transient | The resolver couldn’t resolve the link. |
provider_down | 503 | Provider unavailable | Yes — transient | The provider is temporarily unavailable. |
rate_limited and quota_exceeded both use HTTP 429 and both send a Retry-After
header. Distinguish them by the code field, not the status. See Rate limits.Handling guidance
4xx — your request needs a change
4xx — your request needs a change
invalid_request, invalid_key, forbidden_scope, not_found, link_removed, and
unsupported_link won’t succeed on retry. Surface them to the caller / logs and fix the
input, key, scope, or URL. For unsupported_link, validate against
/v1/providers before resolving.402 — out of credits
402 — out of credits
Stop resolving and prompt a top-up. The
balance field tells you how much is left. Top
up in the dashboard. Nothing was charged.429 — slow down
429 — slow down
Honor the
Retry-After header (seconds) before retrying. For quota_exceeded, the
window resets at UTC midnight — back off until then rather than hammering. See
Rate limits.5xx — transient, retry with backoff
5xx — transient, retry with backoff
provider_down (503) and many resolve_failed (502) cases are temporary. Retry with
exponential backoff and a cap on attempts. If a queued job ends failed, the resolver
genuinely couldn’t get through — don’t retry indefinitely.Charging on errors
A request is only charged when it produces a resolved destination. Errors — includingout_of_credits, unsupported_link, link_removed, provider_down, and resolve_failed
— do not spend credits. Cached resolves also cost 0. See How it works.
Mapping in the SDKs
Both SDKs raise a typed exception per code, all subclasses of the base API error, so you cancatch precisely or broadly.
