Errors
Handler errors return a consistent envelope with a nested error object:
{ "success": false, "error": { "code": "ERROR_CODE", "message": "Human-readable description", "details": { } }, "meta": { "request_id": "0190a8b2-...", "timestamp": "2026-04-08T12:00:00Z", "status": 400 }}details is optional and carries extra context (field errors, usage counters, etc.). Include the meta.request_id when contacting support.
Note: errors raised by the authentication and rate-limit middleware use a flat shape instead: {"success": false, "error": "CODE", "message": "..."}. This applies to MISSING_API_KEY, INVALID_API_KEY, IP_NOT_ALLOWED, INSUFFICIENT_PERMISSIONS, DOMAIN_SCOPE_DENIED, and the hourly RATE_LIMIT_EXCEEDED.
Error codes
Section titled “Error codes”| Code | HTTP Status | Meaning |
|---|---|---|
BAD_REQUEST | 400 | Malformed request |
VALIDATION_ERROR | 400 / 422 | Invalid request body or query params |
INVALID_URL | 400 | Destination URL is not a valid URL |
INVALID_ALIAS | 400 | Custom alias has invalid format |
MISSING_API_KEY | 401 | No X-API-Key header sent |
INVALID_API_KEY | 401 | Key is malformed, revoked, or expired |
INSUFFICIENT_PERMISSIONS | 403 | Key lacks the required permission scope |
IP_NOT_ALLOWED | 403 | Request IP not in the key’s allowlist |
DOMAIN_SCOPE_DENIED | 403 | Key is scoped to other domains |
FORBIDDEN | 403 | Resource belongs to another organization |
SECURITY_BLOCKED | 403 | Destination URL blocked by security checks |
NOT_FOUND | 404 | Resource doesn’t exist or isn’t yours |
REQUEST_TIMEOUT | 408 | Bulk operation took too long (60s cap) |
ALIAS_EXISTS | 409 | Custom alias already taken |
RESERVED_ALIAS | 409 | Custom alias is reserved |
RATE_LIMIT_EXCEEDED | 429 | Hourly rate limit exceeded. Check Retry-After header |
SUBSCRIPTION_LIMIT_EXCEEDED | 429 | Monthly link creation limit reached |
JOURNEY_LIMIT_EXCEEDED | 429 | Monthly journey event cap exceeded |
TIER_LIMIT_EXCEEDED | 429 | Tier resource cap reached (e.g. webhook endpoints) |
INTERNAL_ERROR | 500 | Server error. Retry or contact support |
DATABASE_ERROR | 500 | Database error. Retry or contact support |
SERVICE_UNAVAILABLE | 503 | Analytics service temporarily down |
Common fixes
Section titled “Common fixes”401: Check your key starts with qck_, hasn’t been revoked, and is in the X-API-Key header (not Authorization: Bearer).
403: Check the key has the required permission scope. If using IP allowlists, verify your request IP is listed.
429: Wait for Retry-After seconds. See Rate Limits for tier details.
503: Analytics endpoints depend on ClickHouse. Link management (create, update, delete) is unaffected. Retry after a few seconds.