Skip to content
FLORA DocsGo to app
Platform

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": {
"code": "input_validation_error",
"message": "prompt: prompt is required.",
"fields": [{ "field": "prompt", "message": "prompt is required." }]
}
}

For validation errors, fields lists each rejected field when field-level details are available.

Every response also includes a request-id header. Always capture it — support needs it to investigate.

Terminal window
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":"..."}}

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.

The request body parsed, but a field is missing, has the wrong type, uses the wrong public ID prefix, or doesn’t match the Technique’s input schema.

Fix: Use the fields array and message to correct the payload. Common fixes:

  • Use public API IDs with the expected prefixes: ws_ for workspaces, prj_ for projects, tech_ for techniques, run_ for runs.
  • For generations, use type: "image" | "video" | "audio" | "text"; don’t use t2i, i2v, or text-to-image as type.
  • Pass model as a model endpoint ID string from GET /api/v1/models or MCP list_models.
  • For asset uploads, pass source as a string: "signed-url" or an allowlisted HTTPS URL.
  • For technique runs by slug, call GET /api/v1/techniques/{slug} first and match each input id and type exactly.
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
}
}

No Authorization header was sent.

Fix: Include Authorization: Bearer sk_live_XXXX on every request.

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.

The OAuth token expired and couldn’t refresh, or was revoked from FLORA → Connected apps.

Fix: Reconnect the MCP client. See MCP troubleshooting.

The workspace balance (USD) is too low to start the run.

Fix: Top up in Settings → Billing, then retry. The charged_cost from previous completed runs and the run_cost on GET /techniques/{slug} show your burn rate.

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.

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.

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.

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.

You’ve exceeded the per-workspace request rate.

Fix:

  • Back off with exponential delay before retrying.
  • Reduce parallelism in batch jobs.
  • The retry-after response 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));
// retry

Something broke on our side. The request didn’t complete.

Fix: Retry with backoff. If it persists, send the request-id to support.

The API is overloaded or down for maintenance.

Fix: Retry with exponential backoff. Check our status page if reachable.

A run that starts successfully can still fail during execution. The HTTP request returns 200, but the run status becomes failed:

{
"run_id": "run_abc...",
"status": "failed",
"error_code": "model_timeout",
"error_message": "Underlying model exceeded the 60s budget"
}

Common error_code 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.

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;
}

400, 401, 403, 404, 409, 422 are caller mistakes. Retrying them produces the same error — surface them to the user/log instead.

When you open a ticket, include:

  • The request-id from response headers (or the failing run’s run_id).
  • The endpoint and HTTP method.
  • The error code and message.
  • 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.