Errors
HTTP status codes, error response format, and common error codes for the Warpflow Signals API.
Error response format
All error responses follow a consistent JSON structure:
{
"error": "ERROR_CODE",
"message": "Human-readable description of what went wrong",
"status": 400
}| Field | Type | Description |
|---|---|---|
error | string | Machine-readable error code (uppercase, underscored) |
message | string | Human-readable explanation |
status | number | HTTP status code |
HTTP status codes
2xx — Success
| Status | Meaning |
|---|---|
| 200 | Request processed successfully |
| 202 | Accepted — event queued for async processing |
4xx — Client errors
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Request body failed schema validation. The message field describes which field is missing or malformed | Check the payload against the endpoint's expected schema |
| 401 | UNAUTHORIZED | Missing or invalid Authorization header | Verify your API key is correct and active. Check that the header format is Bearer <key> |
| 403 | FORBIDDEN | Valid authentication but insufficient permissions for this operation | Check that your key has access to the requested tenant |
| 403 | HIPAA_RESTRICTED | This endpoint is disabled for HIPAA-enabled tenants | Use OAuth2 (Cognito) instead of API keys for HIPAA tenants. Email endpoints are unavailable for HIPAA tenants |
| 404 | NOT_FOUND | The requested resource doesn't exist | Check the tenant_id, lead_id, contact_id, or other identifiers in the URL |
| 409 | CONFLICT | Resource conflict (e.g., attempting to create a duplicate) | The resource already exists. Use PATCH to update instead |
| 429 | RATE_LIMITED | Too many requests | Wait for the duration specified in the Retry-After header before retrying |
5xx — Server errors
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 500 | INTERNAL_ERROR | Unexpected server error | Retry after a short delay. If persistent, contact support |
| 503 | SERVICE_UNAVAILABLE | Temporary service disruption (upstream dependency failure) | Retry with exponential backoff |
Common scenarios
Invalid API key
401 Unauthorized
{
"error": "UNAUTHORIZED",
"message": "Invalid or expired API key",
"status": 401
}Causes:
- Key was revoked or rotated past the 72-hour grace period
- Key prefix doesn't match any active key
- Missing
Bearerprefix in the Authorization header
Missing required field
400 Bad Request
{
"error": "VALIDATION_ERROR",
"message": "content.body is required",
"status": 400
}Fix: Include the content.body field in your webhook payload. See Webhooks for the full payload schema.
HIPAA tenant accessing email
403 Forbidden
{
"error": "HIPAA_RESTRICTED",
"message": "Email endpoints are not available for HIPAA-enabled tenants",
"status": 403
}Why: The Nylas standard plan does not include a BAA. HIPAA compliance requires all email processing to be covered by a Business Associate Agreement. Contact support if you need HIPAA-compliant email.
Rate limited
429 Too Many Requests
Retry-After: 30
{
"error": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 30 seconds.",
"status": 429
}Fix: Implement exponential backoff or throttle your request rate. For bulk operations, add a short delay between requests.
Retry strategy
For transient errors (429, 500, 503), implement exponential backoff:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options)
if (response.ok) return response.json()
if (response.status === 429 || response.status >= 500) {
const retryAfter = response.headers.get('Retry-After')
const delay = retryAfter
? parseInt(retryAfter) * 1000
: Math.min(1000 * Math.pow(2, attempt), 30000)
await new Promise((resolve) => setTimeout(resolve, delay))
continue
}
// Non-retryable error
throw new Error(`API error: ${response.status}`)
}
throw new Error('Max retries exceeded')
}Do not retry 400, 401, 403, or 404 errors — these indicate a problem with the request that won't be fixed by retrying.