Error Handling
Learn how to handle errors from the Flow Myna API. All errors follow a consistent format and use standard HTTP status codes.
HTTP Status Codes
| Code | Meaning | When It Occurs |
|---|---|---|
| 200 | Success | Request completed successfully |
| 401 | Unauthorized | Missing, invalid, revoked, or expired API key |
| 403 | Forbidden | Valid key, but it doesn't carry the scope this endpoint needs |
| 422 | Validation Error | Invalid request body (missing fields, wrong types) |
| 429 | Rate Limited | Over 1,200 requests/minute on this key — see Retry-After |
| 500 | Server Error | Something went wrong on our end |
404 is rare — The API auto-creates objects, object types, and event types, so you won't get 404 errors for missing resources.
Error Response Format
All error responses follow this consistent format:
Error Response Format
{
"detail": "Human-readable error message"
}Authentication Errors (401)
Missing API Key
{
"detail": "Missing API key. Use 'Authorization: Bearer fm_live_...' or 'X-FlowMyna-Api-Key: fm_live_...'"
}Fix: Include the Authorization header in your request.
Invalid API Key Format
{
"detail": "Invalid API key format. Keys must start with 'fm_live_'"
}Fix: Ensure your key starts with fm_live_ and is 40 characters.
Invalid API Key
{
"detail": "Invalid API key"
}Fix: Check that you're using the correct key from your dashboard.
Revoked Key
{
"detail": "API key has been revoked"
}Fix: Create a new API key in your dashboard.
Expired Key
{
"detail": "API key has expired"
}Fix: Create a new API key or one without an expiration date.
Scope Errors (403)
A 403 means the key is genuine and active — it just wasn't granted this part of the API. Don't rotate the key; create one that carries the scope, or leave scopes off entirely for full workspace access.
{
"detail": "API key is missing the 'ingest:write' scope (this key holds: process:read)."
}The message names the scope you need. See Authentication → Scopes.
Rate Limit Errors (429)
Each API key gets 1,200 requests a minute. Past that you get a 429 with a Retry-After header giving the seconds until the key has budget again — wait that long rather than retrying tightly.
{
"detail": "Rate limit exceeded (1200 requests/minute per API key). Retry in 43s."
}A batch counts as one request, so batching is the cheapest way to stay under. See Rate Limits.
Validation Errors (422)
Validation errors occur when your request body doesn't match the expected schema:
| Field | Constraint |
|---|---|
event | Required, 1-200 characters |
objects | Required, at least 1 item |
objects[].type | Required, 1-100 characters |
objects[].id | Required, 1-500 characters |
timestamp | Optional, valid ISO 8601 format |
Validation Error Example
{
"detail": [
{
"loc": ["body", "event"],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": ["body", "objects"],
"msg": "ensure this value has at least 1 items",
"type": "value_error.list.min_items"
}
]
}Retry Logic
Implement exponential backoff for transient errors:
Retry Implementation
import time
import random
import requests
def record_with_retry(event_data, max_retries=3):
"""Record event with exponential backoff retry"""
for attempt in range(max_retries):
try:
response = requests.post(
f"{BASE_URL}/event",
json=event_data,
headers=headers,
timeout=30
)
# Success
if response.status_code == 200:
return response.json()
# Don't retry client errors (except rate limits)
if response.status_code in [401, 422]:
raise ValueError(f"Client error: {response.json()}")
# Retry server errors and rate limits
if response.status_code in [429, 500, 502, 503, 504]:
if attempt == max_retries - 1:
raise Exception(f"Max retries exceeded: {response.text}")
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
# Exponential backoff with jitter
wait_time = (2 ** attempt) + random.uniform(0, 1)
time.sleep(wait_time)
raise Exception("Unexpected retry exit")Our SDKs handle retries automatically. The Python and Node.js SDKs include built-in retry logic with exponential backoff.