Error Handling
When a request fails, the Flowstate API returns a structured JSON error response. This page describes the error format, common error codes, and best practices for handling errors in your integration.
Error Response Format
All errors follow a consistent structure:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The 'email' field must be a valid email address.",
"details": [
{
"field": "email",
"message": "Invalid email format"
}
],
"errorId": "err_x7k9m2p4q1"
}
}| Field | Type | Always Present | Description |
|---|---|---|---|
code | string | Yes | Machine-readable error code (see table below) |
message | string | Yes | Human-readable description of the error |
details | array | No | Additional context (e.g., field-level validation) |
errorId | string | Yes | Unique identifier for this error occurrence |
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR | 400 | The request body or query parameters are invalid |
UNAUTHORIZED | 401 | Missing, invalid, or expired API key |
FORBIDDEN | 403 | Valid API key but insufficient permissions for this operation |
NOT_FOUND | 404 | The requested resource does not exist |
CONFLICT | 409 | The operation conflicts with the current state of the resource |
INTERNAL_ERROR | 500 | An unexpected server error occurred |
HTTP Status Codes
| Status | Meaning | When It Happens |
|---|---|---|
200 | OK | Successful read or update |
201 | Created | Successful resource creation |
204 | No Content | Successful deletion |
400 | Bad Request | Invalid parameters, malformed JSON, validation errors |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | Key lacks required permission scope |
404 | Not Found | Resource or route does not exist |
409 | Conflict | Duplicate resource or state conflict |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Unexpected server failure |
Validation Errors
When a 400 VALIDATION_ERROR is returned, the details array contains one entry per invalid field:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed.",
"details": [
{
"field": "firstName",
"message": "Required field is missing"
},
{
"field": "email",
"message": "Must be a valid email address"
},
{
"field": "startDate",
"message": "Must be a valid ISO 8601 date (YYYY-MM-DD)"
}
],
"errorId": "err_v4l1d8t10n"
}
}Rate Limiting
The API enforces rate limits to ensure fair usage. When you exceed the limit, you will receive a 429 Too Many Requests response:
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Please retry after the specified time.",
"errorId": "err_r8t3l1m1t"
}
}The response includes a Retry-After header indicating how many seconds to wait before retrying:
HTTP/1.1 429 Too Many Requests
Retry-After: 30TIP
Rate limits are generous for typical integration use cases. If you consistently hit limits, consider reducing your request frequency or batching operations.
Error ID for Support
Every error response includes a unique errorId. If you need to contact Flowstate support about an issue, include this ID so the support team can quickly locate the relevant logs:
errorId: err_x7k9m2p4q1Best Practices
Retry Strategy
Implement exponential backoff for transient errors:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
// Success — return the response
if (response.ok) return response;
// Rate limited — wait and retry
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "30", 10);
await sleep(retryAfter * 1000);
continue;
}
// Server error — exponential backoff
if (response.status >= 500) {
const delay = Math.pow(2, attempt) * 1000;
await sleep(delay);
continue;
}
// Client error (4xx) — do not retry, return immediately
return response;
}
throw new Error("Max retries exceeded");
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}Handle Specific Error Codes
const response = await fetch(url, options);
const body = await response.json();
if (!response.ok) {
switch (body.error.code) {
case "VALIDATION_ERROR":
// Log field-level details and fix the request
console.error("Validation failed:", body.error.details);
break;
case "UNAUTHORIZED":
// API key is invalid or expired — rotate it
console.error("Authentication failed — check your API key");
break;
case "FORBIDDEN":
// Key lacks permissions — update key scopes
console.error("Permission denied:", body.error.message);
break;
case "NOT_FOUND":
// Resource does not exist — check the ID
console.error("Resource not found:", body.error.message);
break;
default:
// Log the errorId for support
console.error(`Error ${body.error.code}: ${body.error.message} (${body.error.errorId})`);
}
}Log the Error ID
Always log the errorId from error responses. If you need to escalate an issue to Flowstate support, this ID allows the team to trace the exact request through server logs.