Skip to content
Get started

Errors & rate limits

#Error shape

Every error response — regardless of status code — is:

json
{ "error": { "code": "not_found", "message": "Run not found", "details": null } }

code is a stable, machine-readable string (safe to switch on); message is a human-readable English sentence (interfaces translate by code, not by parsing message — see Data & providers for how the console/web apps localize); details is present only on a handful of error codes that carry structured extra data.

#Common codes

HTTP code When
400 invalid_request Malformed JSON, or a missing/wrong-typed required field
400 schema_required Created/updated a document/custom-form agent without config.outputSchema
401 unauthorized Missing/invalid API key, or missing/expired session
403 forbidden Valid credential, but missing scope (key) or role permission (session)
403 auth.email_verification_required Unverified session user hit the unverified-run limit
404 not_found No such resource in this workspace
409 conflict (or a specific code below) An operation doesn't apply to the resource's current state
409 run_not_awaiting_input / _approval / _review Called /input, /approve, or /review on a run not in that status
409 run_already_terminal / run_currently_running Called /cancel on a run that's already finished, or currently executing
409 agent_no_version Ran (or eval'd) an agent that has no published version
409 idempotency_in_progress A request with the same Idempotency-Key is already being processed
409 idempotency_key_conflict The same Idempotency-Key was reused with a different request body
409 output_not_available Fetched /output before the run produced one
409 csv_not_supported Fetched /output?format=csv for a shape that can't flatten to rows
402 billing.insufficient_credits Workspace balance can't cover the estimate — details: { required, available }
413 payload_too_large Upload exceeds the 25 MB limit
415 unsupported_media_type Upload's content type isn't on the allowlist
429 rate_limited Over 120 req/min for this key — Retry-After header set
429 too_many_streams Workspace already has 20 concurrent GET .../events connections open
500 internal_error Unexpected server error — no further detail is included

#Idempotency

POST /v1/runs, POST /v1/runs/:id/input, and POST /v1/runs/:id/approve accept an Idempotency-Key header (POST /v1/runs/:id/review does not, currently — see Human-in-the-loop). Send a fresh UUID per logical operation:

  • Same key + same workspace + same request body, within 24 hours → the original response is replayed verbatim, side effects and all — safe to retry after a timeout or a dropped connection.
  • Same key + a different request body409 idempotency_key_conflict. Treat this as a bug in the caller (reusing a key across two different operations), not something to retry.
  • Same key, concurrently in flight409 idempotency_in_progress. Retry after a short delay once the first attempt has completed.
typescript
async function createRunOnce(body: unknown, idempotencyKey: string) {
  const res = await fetch("https://api.agentflowbind.com/v1/runs", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.AFB_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey, // generate once per logical run, reuse it across retries
    },
    body: JSON.stringify(body),
  });
  return res.json();
}

#Rate limits

Route(s) Limit
Any key-authenticated /v1/* request Per the calling workspace's plan (entitlements.apiRateLimitPerMin) — see Authentication & scopes for the per-plan numbers
POST /v1/console/auth/sign-in, /sign-up 10/min per client IP and 5/min per email — whichever is hit first blocks the request
GET /v1/runs/:id/events 20 concurrent open streams per workspace

A 429 always includes Retry-After (seconds) except the SSE stream cap, which is a hard concurrency limit rather than a time window — close an existing stream (or wait for one to reach a terminal run status) before opening another.