Error Reference
Every error response includes a machine-readable code field. This page documents each code with common causes and recommended fixes.
Authentication Errors
INVALID_API_KEY
| HTTP Status | 401 Unauthorized |
| Meaning | The API key is malformed, not found, or was never created |
Common causes:
- Typo in the key — check for extra spaces or truncated characters
- Using a key from a different environment
- Key was deleted from the dashboard
Fix:
- Verify the key format:
xqr_pk_{8-char-prefix}.{64-char-secret} - Regenerate the key from Settings → Developers
- Ensure you’re not wrapping the key in quotes in your header
// Correctheaders: { Authorization: "Bearer xqr_pk_a1b2c3d4.xxxx..." }
// Wrong — double Bearer prefixheaders: { Authorization: "Bearer Bearer xqr_pk_..." }
// Wrong — missing Bearer prefixheaders: { Authorization: "xqr_pk_..." }REVOKED_API_KEY
| HTTP Status | 401 Unauthorized |
| Meaning | The API key was explicitly revoked by a workspace admin |
Common causes:
- A team member rotated or revoked the key
- Key was revoked as part of a security incident response
Fix:
- Go to Settings → Developers and create a new key
- If you rotated the key, use the new key that was generated during rotation
- Update all services using the old key
EXPIRED_API_KEY
| HTTP Status | 401 Unauthorized |
| Meaning | The key passed its expires_at date |
Fix:
- Create a new key from the dashboard
- Consider using non-expiring keys for production services
- Set up key rotation reminders before expiry
Authorization Errors
INSUFFICIENT_SCOPE
| HTTP Status | 403 Forbidden |
| Meaning | The API key is valid but lacks the scope required by this endpoint |
Example: Calling POST /v1/links with a key that only has links:read.
Fix:
- Check which scope the endpoint requires (listed at the top of each endpoint’s docs)
- Create a new key with the required scope, or use a key with
*(full access)
Scope reference:
| Endpoint pattern | Required scope |
|---|---|
GET /v1/links* | links:read |
POST/PATCH/DELETE /v1/links* | links:write |
*/qr (image render) | qr:render |
GET /v1/qr/templates* | qr:read |
POST/PATCH/DELETE /v1/qr/templates* | qr:write |
GET /v1/analytics* | analytics:read |
GET /v1/assets* | assets:read |
POST/DELETE /v1/assets* | assets:write |
GET /v1/bio* | bio:read |
POST/PATCH/DELETE /v1/bio* | bio:write |
GET /v1/workspace* | workspace:read |
GET /v1/webhooks* | webhooks:read |
POST/PATCH/DELETE /v1/webhooks* | webhooks:write |
FEATURE_NOT_AVAILABLE
| HTTP Status | 403 Forbidden |
| Meaning | Your plan does not include this feature |
Common causes:
- Free plan users trying to access the API (API requires Pro+)
- Pro plan users trying to use Enterprise-only features (e.g., advanced routing rules)
Fix:
- Upgrade your plan at Settings → Billing
- Check the plan comparison to see which features are available
PLAN_LIMIT_EXCEEDED
| HTTP Status | 403 Forbidden |
| Meaning | You’ve reached your plan’s resource limit |
Examples:
- Pro plan: max 500 links, trying to create link #501
- Business plan: max 10 API keys, trying to create key #11
Fix:
- Delete unused resources to free up capacity
- Check your usage via
GET /v1/workspace/usage - Upgrade your plan for higher limits
Validation Errors
VALIDATION_ERROR
| HTTP Status | 400 Bad Request |
| Meaning | The request body or parameters are invalid |
Common causes:
- Missing required fields
- Wrong field types (e.g., string instead of array)
- Values outside allowed range (e.g.,
size: 100when max is 50) - Invalid URL format
Response includes details:
{ "error": { "code": "VALIDATION_ERROR", "message": "url: Invalid URL format. Must start with http:// or https://", "status": 400 }}Fix: Read the message field — it indicates exactly which field failed and why.
SLUG_TAKEN
| HTTP Status | 409 Conflict |
| Meaning | The custom_slug is already used by another link in your workspace |
Fix:
- Choose a different slug
- Check existing slugs with
GET /v1/links?search=your-slug - Delete the existing link if it’s no longer needed
Rate Limiting Errors
RATE_LIMIT_EXCEEDED
| HTTP Status | 429 Too Many Requests |
| Meaning | Your monthly API quota has been exhausted |
Fix:
- Wait for your billing cycle to reset
- Check remaining quota:
GET /v1/workspace/usage - Upgrade your plan for higher monthly limits
- Optimize your integration to make fewer calls (use bulk endpoints, cache responses)
BURST_LIMIT_EXCEEDED
| HTTP Status | 429 Too Many Requests |
| Meaning | Too many requests per second (token bucket exhausted) |
Fix:
- Read the
Retry-Afterheader and wait that many seconds - Implement exponential backoff:
async function callWithRetry(fn: () => Promise<Response>, maxRetries = 3) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const res = await fn();
if (res.status !== 429) return res;
const retryAfter = parseInt(res.headers.get("Retry-After") || "1"); const backoff = retryAfter * 1000 * Math.pow(2, attempt); await new Promise((r) => setTimeout(r, backoff)); } throw new Error("Max retries exceeded");}import timeimport httpx
def call_with_retry(fn, max_retries=3): for attempt in range(max_retries + 1): response = fn()
if response.status_code != 429: return response
retry_after = int(response.headers.get("Retry-After", "1")) backoff = retry_after * (2 ** attempt) time.sleep(backoff)
raise Exception("Max retries exceeded")Resource Errors
RESOURCE_NOT_FOUND
| HTTP Status | 404 Not Found |
| Meaning | The resource does not exist or belongs to another workspace |
Common causes:
- Typo in the resource UUID
- Resource was deleted
- Trying to access another workspace’s resource
Fix:
- Verify the resource ID
- List resources to confirm it exists (e.g.,
GET /v1/links) - Ensure you’re using the correct API key (keys are workspace-scoped)
Server Errors
INTERNAL_ERROR
| HTTP Status | 500 Internal Server Error |
| Meaning | An unexpected server error occurred |
Fix:
- Retry with exponential backoff (transient errors usually resolve quickly)
- If persistent, check status.xqr.co for outages
- Contact support with the
request_idfrommeta.request_id
Was this page helpful?
Thanks for your feedback!