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