Errors
Every FLORA API error code, what it means, and how to recover.
FLORA returns conventional HTTP status codes and a structured error body on every non-2xx response.
Error shape
Section titled “Error shape”{ "error": { "code": "input_validation_error", "message": "Input 'prompt' is required but was not provided." }}Every response also includes a request-id header. Always capture it — support needs it to investigate.
curl -i https://app.flora.ai/api/v1/techniques/thumbnail-v3/runs \ -H "Authorization: Bearer $FLORA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"inputs": [], "mode": "async"}'
# < HTTP/2 400# < request-id: req_abc123...# < content-type: application/json## {"error":{"code":"input_validation_error","message":"..."}}All error codes
Section titled “All error codes”400 invalid_json
Section titled “400 invalid_json”The request body wasn’t valid JSON.
Fix: Validate JSON before sending. If using a SDK, this almost always indicates a serialization bug in your code — not a server issue.
400 input_validation_error
Section titled “400 input_validation_error”The request body parsed, but a field is missing, has the wrong type, or doesn’t match the Technique’s input schema.
Fix: Call GET /api/v1/techniques/{slug} first and match each input id and type exactly. The message field tells you which input failed.
try { await client.techniques.runs.create('thumbnail-v3', { inputs: [], mode: 'async' });} catch (err) { if (err instanceof APIError && err.code === 'input_validation_error') { // Re-fetch the schema and rebuild inputs }}401 unauthorized
Section titled “401 unauthorized”No Authorization header was sent.
Fix: Include Authorization: Bearer sk_live_XXXX on every request.
401 invalid_api_key
Section titled “401 invalid_api_key”The API key is malformed, revoked, or doesn’t exist.
Fix: Check the key value (no whitespace, no Bearer prefix duplicated, correct env var). If the key was revoked, create a new one in Settings → API Keys.
401 invalid_token (MCP only)
Section titled “401 invalid_token (MCP only)”The OAuth token expired and couldn’t refresh, or was revoked from FLORA → Connected apps.
Fix: Reconnect the MCP client. See MCP troubleshooting.
402 insufficient_credits
Section titled “402 insufficient_credits”The workspace has run out of credits and can’t start the run.
Fix: Top up in Settings → Billing, then retry. The chargedCost from previous completed runs and the runCost on GET /techniques/{slug} show your burn rate.
403 forbidden
Section titled “403 forbidden”The key is valid but lacks permission for this operation. Common cases:
- Creating a Project with a read-only key.
- Accessing a workspace your account was removed from.
- Calling a Technique that was archived.
Fix: Check the FLORA app to confirm the user/key role. If the role looks right, contact support with the request-id.
404 not_found
Section titled “404 not_found”The resource ID, Technique slug, or run ID doesn’t exist (or doesn’t belong to your workspace).
Fix:
- For Techniques: verify the slug. The URL
https://app.flora.ai/techniques/{slug}is authoritative. - For runs: the run ID was created in a different workspace, or you typo’d it.
- For assets/projects: confirm the ID by listing the parent collection first.
409 conflict
Section titled “409 conflict”The resource is in a state that doesn’t allow this operation. Examples: completing an asset that’s already complete, retrying an asset that didn’t fail.
Fix: Inspect the resource first (GET /assets/{id}) and only call the action if the current state allows it.
422 idempotency_conflict
Section titled “422 idempotency_conflict”You sent the same idempotency_key with a different request body. Idempotency keys must be paired with identical bodies.
Fix: Either reuse the original body (and get the cached response) or choose a new idempotency key. See Idempotency.
429 rate_limited
Section titled “429 rate_limited”You’ve exceeded the per-workspace request rate.
Fix:
- Back off with exponential delay before retrying.
- Reduce parallelism in batch jobs.
- The
retry-afterresponse header (when present) is the recommended wait in seconds.
const retryAfter = parseInt(err.headers['retry-after'] ?? '5', 10);await new Promise(r => setTimeout(r, retryAfter * 1000));// retry500 internal_error
Section titled “500 internal_error”Something broke on our side. The request didn’t complete.
Fix: Retry with backoff. If it persists, send the request-id to support.
503 service_unavailable
Section titled “503 service_unavailable”The API is overloaded or down for maintenance.
Fix: Retry with exponential backoff. Check our status page if reachable.
Run-level errors
Section titled “Run-level errors”A run that starts successfully can still fail during execution. The HTTP request returns 200, but the run status becomes failed:
{ "runId": "run_abc...", "status": "failed", "errorCode": "model_timeout", "errorMessage": "Underlying model exceeded the 60s budget"}Common errorCode values:
| Code | Meaning |
|---|---|
model_timeout | The model didn’t return in time. Usually transient — retry. |
safety_blocked | The model refused the request on safety grounds. Adjust prompts. |
input_unreachable | A URL input (image/video) couldn’t be fetched. Check the URL. |
internal_error | Generic execution failure. Retry; if persistent, contact support. |
Always handle status === 'failed' when polling — the run won’t auto-retry.
Recovery patterns
Section titled “Recovery patterns”Retry on transient errors
Section titled “Retry on transient errors”async function withRetry<T>(fn: () => Promise<T>, max = 3): Promise<T> { let lastErr: unknown; for (let attempt = 0; attempt < max; attempt++) { try { return await fn(); } catch (err) { if (err instanceof APIError) { if ([429, 500, 503].includes(err.status)) { lastErr = err; const delay = Math.min(1000 * 2 ** attempt, 30_000); await new Promise(r => setTimeout(r, delay)); continue; } } throw err; } } throw lastErr;}Don’t retry user errors
Section titled “Don’t retry user errors”400, 401, 403, 404, 409, 422 are caller mistakes. Retrying them produces the same error — surface them to the user/log instead.
Contacting support
Section titled “Contacting support”When you open a ticket, include:
- The
request-idfrom response headers (or the failing run’srunId). - The endpoint and HTTP method.
- The error
codeandmessage. - Approximate UTC timestamp.
- Your workspace ID.
The more of these we have, the faster we can diagnose. Without request-id we usually can’t find the request at all.
Related
Section titled “Related”- Authentication — 401 errors and key management.
- Idempotency — safe retry semantics.