Error Handling
Understanding and handling Flameup API errors
Error Response Format
Most errors are flat. Write your handler against this shape first — it is what the great majority of endpoints return, including everything on the ingestion, people and device surfaces:
{
"error": "Person not found"
}
Some responses add a message alongside it with more detail, and the
ingestion routes (/track, /identify) add "success": false and a
details string to their flat errors.
A minority of endpoints — mainly workspace and campaign management — return a typed envelope instead:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable error message",
"details": "field: what was wrong with it"
}
}
error defensively: it is a string on most responses and an object on some. typeof err.error === 'string' ? err.error : err.error.message covers both. (The typed envelope is itself not perfectly uniform — some variants omit success — which is one more reason to key on error rather than on success.)HTTP Status Codes
Flameup uses standard HTTP status codes:
| Code | Meaning | Description |
|---|---|---|
200 | OK | Request succeeded |
201 | Created | Resource created successfully |
204 | No Content | Request succeeded (no response body) |
400 | Bad Request | Invalid request parameters |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | Insufficient permissions |
404 | Not Found | Resource doesn't exist |
409 | Conflict | Resource already exists, or a delete is blocked by references (e.g. deleting a segment still used by campaigns returns 409 naming them) |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server-side error |
501 | Not Implemented | The route exists but is not built yet (e.g. push delivery-history reads). Returns {"error": "not implemented"} — not worth retrying |
503 | Service Unavailable | Temporary unavailability |
Common Error Codes
Authentication Errors
{"error": ..., "message": ...}), not the typed error.code envelope.HTTP Status: 401
API key is missing or invalid. Any authentication failure returns this generic 401 response.
{
"error": "Unauthorized",
"message": "Invalid or expired API key",
"details": "Please provide a valid API key in the Authorization header: Bearer <your_api_key>"
}
Solutions:
- Verify the API key is correct
- Check the
Authorizationheader is set withBearerprefix - Ensure the key hasn't been revoked
Resource Errors
HTTP Status: 404
The requested resource doesn't exist. On the people, events, device and
push surfaces this is flat, with a surface-specific string — the push
surface says "Person not found", the people surface says:
{ "error": "person with id 'user_12345' not found" }
Match on the status code, not the string.
Workspace and campaign management endpoints return the typed form instead:
{ "success": false, "error": { "code": "NOT_FOUND", "message": "..." } }
Solutions:
- Verify the resource ID is correct
- Check you're using the right workspace
Validation Errors
HTTP Status: 400
Request body or parameters failed validation. details is a string in the form field: message.
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": "email: Invalid email format"
}
}
Solutions:
- Check the request body matches the API spec
- Validate data before sending
Rate Limiting Errors
HTTP Status: 429
Too many requests in a time window. This response uses the flat shape and includes X-RateLimit-* headers.
{
"error": "Rate limit exceeded",
"message": "Too many requests. Please try again later."
}
Solutions:
- Check the
X-RateLimit-Resetheader for when to retry - Implement exponential backoff
- Contact support if you need a higher limit
Error Handling Best Practices
Retry Strategy
Implement exponential backoff for transient errors:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.ok) {
return response.json();
}
const error = await response.json();
// Don't retry client errors (except rate limits)
if (response.status >= 400 && response.status < 500) {
if (response.status === 429) {
// Rate limited - wait and retry using header
const resetTime = parseInt(response.headers.get('X-RateLimit-Reset') || '0') * 1000;
const waitTime = resetTime > Date.now() ? resetTime - Date.now() : 60000;
await sleep(waitTime);
continue;
}
throw new FlameupError(error);
}
// Server error - retry with backoff
if (attempt < maxRetries - 1) {
await sleep(Math.pow(2, attempt) * 1000);
continue;
}
throw new FlameupError(error);
} catch (networkError) {
if (attempt < maxRetries - 1) {
await sleep(Math.pow(2, attempt) * 1000);
continue;
}
throw networkError;
}
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
import time
import requests
def fetch_with_retry(url, options, max_retries=3):
for attempt in range(max_retries):
try:
response = requests.request(**options, url=url)
if response.ok:
return response.json()
error = response.json()
# Don't retry client errors (except rate limits)
if 400 <= response.status_code < 500:
if response.status_code == 429:
# Use X-RateLimit-Reset header
reset_time = int(response.headers.get('X-RateLimit-Reset', 0))
wait_time = max(reset_time - time.time(), 60) if reset_time else 60
time.sleep(wait_time)
continue
raise FlameupError(error)
# Server error - retry with backoff
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
continue
raise FlameupError(error)
except requests.RequestException:
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
continue
raise
Error Logging
Log errors with context for debugging:
// `error` is the response body's `error` field, which is a string on most
// surfaces and an object on the workspace and campaign endpoints.
function logError(error, context) {
const e = typeof error === 'string' ? { message: error } : (error || {});
console.error('Flameup API Error', {
code: e.code,
message: e.message,
details: e.details,
context: {
endpoint: context.endpoint,
method: context.method,
userId: context.userId,
timestamp: new Date().toISOString()
}
});
}
User-Friendly Messages
Map error codes to user-friendly messages:
const ERROR_MESSAGES = {
UNAUTHORIZED: 'Please check your API credentials',
FORBIDDEN: 'You don\'t have permission for this action',
NOT_FOUND: 'The requested item was not found',
VALIDATION_ERROR: 'Please check your input and try again',
RATE_LIMIT: 'Too many requests. Please wait a moment.',
default: 'Something went wrong. Please try again.'
};
function getUserMessage(errorCode) {
return ERROR_MESSAGES[errorCode] || ERROR_MESSAGES.default;
}
Debugging Tips
- Check the response body - Error details are always in the response
- Verify API key permissions - Most 403 errors are permission issues
- Validate request format - Use the API reference for correct schemas
- Check rate limit headers - Monitor
X-RateLimit-*headers (returned on429responses) - Include the full response - Paste the status code and body when contacting support