Errors & rate limits
BASE https://api.rii.link/v1AUTH: BEARER → AUTHENTICATION
One envelope, stable codes, honest headers. Match on code, never on message.
The error envelope
Every non-2xx response is this shape. code is stable and machine-matchable; message names the exact field or conflict; doc_url deep-links back into this page.
EXAMPLE · 409
{
"error": {
"code": "slug_taken",
"message": "\"summer\" is already claimed on rii.link.",
"doc_url": "https://rii.link/docs/api/errors#slug_taken"
}
}
Error codes
The full set. Codes are append-only — none of these will ever change meaning.
validation_failed · 400A field is missing or malformed — message names the field.invalid_key · 401Key missing, malformed, or revoked.scope_denied · 403The key exists but lacks the scope for this call.not_found · 404No such id in this workspace.slug_taken · 409Also domain_in_use when deleting a host that still carries links.link_expired · 410The link passed its expires_at.rule_invalid · 422A routing rule references an unknown condition or malformed target.rate_limited · 429Budget exhausted — honor Retry-After.Rate limits
Per key, rolling window — 60 requests per minute on Free, 600 on Premium, custom on Enterprise. The budget rides on every response; 429s add Retry-After. Bulk import is why the CSV pipeline is on the roadmap — until it ships, pace writes.
HEADERS RIDE ON EVERY RESPONSE, NOT JUST 429S — BUDGET IS ALWAYS VISIBLE
RESPONSE HEADERS · 429
HTTP/2 429
x-ratelimit-limit: 600
x-ratelimit-remaining: 0
x-ratelimit-reset: 2026-08-12T09:17:00Z
retry-after: 14