Error Handling
Learn how to handle API errors gracefully in your application.
Error Response Format
All API errors follow a consistent JSON format:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{
"path": "url",
"message": "URL must start with http:// or https://"
}
]
},
"requestId": "req_xxxxxxxxxxxxx"
}The requestId is useful when contacting support about specific errors.
HTTP Status Codes
| Status | Meaning | What to Do |
|---|---|---|
| 200 | Success | Request succeeded |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Check request body format |
| 401 | Unauthorized | Check your API key |
| 403 | Forbidden | Check API key scopes |
| 404 | Not Found | Verify the resource ID exists |
| 409 | Conflict | Resource already exists (e.g., slug taken) |
| 422 | Validation Error | Check error details for specific field |
| 429 | Rate Limited | Wait and retry with backoff |
| 500 | Server Error | Retry later, contact support if persists |
Error Codes
The error.code field contains a machine-readable error code:
| Code | Status | Description |
|---|---|---|
BAD_REQUEST | 400 | Malformed request syntax or invalid parameters |
VALIDATION_ERROR | 400/422 | Request body validation failed |
AUTHENTICATION_REQUIRED | 401 | No authentication credentials provided |
INVALID_API_KEY | 401 | API key is invalid, expired, or revoked |
UNAUTHORIZED | 401 | Invalid, missing, or expired API key |
FORBIDDEN | 403 | Insufficient permissions or missing scopes |
INSUFFICIENT_PERMISSIONS | 403 | API key lacks required scopes for this operation |
FEATURE_NOT_AVAILABLE | 403 | Feature requires a higher tier plan |
DISABLED_LINK | 403 | Link has been disabled and cannot be accessed |
NOT_FOUND | 404 | Resource not found |
CONFLICT | 409 | Resource already exists (e.g., slug taken) |
EXPIRED_LINK | 410 | Link has passed its expiration date |
RATE_LIMIT_EXCEEDED | 429 | Too many requests |
PLAN_LIMIT_EXCEEDED | 402 | Plan quota exceeded (links, API calls, etc.) |
DATABASE_ERROR | 500 | Database operation failed |
INTERNAL_SERVER_ERROR | 500 | Unexpected server error |
WEBHOOK_DELIVERY_FAILED | 502 | Webhook delivery to endpoint failed |
SERVICE_UNAVAILABLE | 503 | Service temporarily unavailable, try again later |
Retry Strategy
For transient errors (5xx, 429), implement exponential backoff:
async function fetchWithRetry(url, options, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
// Rate limited - get retry-after header
const retryAfter = response.headers.get('Retry-After');
const waitTime = retryAfter
? parseInt(retryAfter) * 1000
: Math.pow(2, attempt) * 1000;
await new Promise(r => setTimeout(r, waitTime));
continue;
}
if (response.status >= 500) {
// Server error - retry with backoff
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
continue;
}
return response;
} catch (error) {
lastError = error;
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
}
}
throw lastError;
}Example Error Handling
async function createLink(url, slug) {
const response = await fetch(
'https://api.sniplink.co/v1/workspaces/{workspaceId}/links',
{
method: 'POST',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({ url, slug }),
}
);
const data = await response.json();
// Check for error response
if (data.error) {
switch (data.error.code) {
case 'VALIDATION_ERROR':
console.error('Invalid input:', data.error.details);
break;
case 'CONFLICT':
console.error('Slug already taken, try a different one');
break;
case 'RATE_LIMIT_EXCEEDED':
console.error('Too many requests, slow down');
break;
case 'UNAUTHORIZED':
console.error('Check your API key');
break;
default:
console.error('Error:', data.error.message);
}
// Include requestId for support
if (data.requestId) {
console.error('Request ID:', data.requestId);
}
return null;
}
return data.link;
}Was this page helpful?