Fibric. Docs fibric.io →
v0.9 · preview
Reference

Errors

Every error the Fibric API returns uses standard HTTP status codes and one consistent JSON envelope. The code is stable and machine-readable; branch on it, never on message, which is human-readable and may change. This page is the complete code table, grouped by status, with the conflict semantics that matter most: idempotency replay and the single-flight lock.

The error envelope

error.typestring

The broad class of failure: invalid_request_error, authentication_error, permission_error, not_found_error, conflict_error, validation_error, rate_limit_error, or api_error. One type per status group; use it for coarse handling.

error.codestring

The specific, stable code, for example entity_locked. New codes may be added within a major version; existing codes never change meaning.

error.messagestring

Human-readable detail, naming the field or resource involved. Safe to log; not safe to parse.

error.doc_urlstring

A link to the section of this page that documents the code.

error.request_idstring

Identifies the request in our logs and in any receipt the request produced. Include it when contacting support.

json · the error envelope
{
  "error": {
    "type": "conflict_error",
    "code": "state_conflict",
    "message": "Plan pl_7c1a is already executed and cannot be approved.",
    "doc_url": "https://fibric.io/docs/errors#state_conflict",
    "request_id": "req_9b3e21f0"
  }
}

The envelope is the whole contract: no error returns a bare string, an HTML page, or a different JSON shape, including 429 and 5xx responses. A client that handles the envelope handles every failure the API can produce.

Retryability at a glance

Before the full table, the decision that matters most in a client: given a status, is an unchanged retry ever correct?

StatusRetry unchanged?Why
400 401 403 404 422NoThe request itself is the problem. Sending it again produces the same answer; fix the request, the key, or the policy first.
409 entity_lockedYes, after Retry-AfterThe state that refused you is transient: the lock releases when the in-flight work disposes.
409 all other codesNoThe state that refused you will not change on its own. Read the resource and act on its actual state.
429Yes, after Retry-AfterThe request was never processed; the budget refills.
500 502 503Yes, with backoffReads are always safe. Writes are safe when they carried an Idempotency-Key, which is why every write should.

Codes by HTTP status

400

Bad request

Type invalid_request_error. The request never reached business logic. Do not retry unchanged.

CodeWhen it happensHow to fix
invalid_jsonThe body is not parseable JSON.Fix the serialization; check for truncation and encoding.
invalid_requestThe body parsed but the request is malformed as a whole, for example a payload over the 256 KB limit or a missing Content-Type.Follow the detail in message; it names what was wrong.
missing_parameterA required field is absent. message names it.Send the field. Required fields are marked on each endpoint page.
invalid_parameterA field is present but fails validation: a non-slug operator name, an event_type that is not dotted noun.verb, a timestamp that is not RFC 3339, a limit outside 1–100.Correct the named field to the documented format.
invalid_cursorA pagination cursor is malformed, expired, or was issued for a different query.Restart the listing from the first page and re-walk next_cursor.
401

Unauthenticated

Type authentication_error. The server does not know who you are. Do not retry with the same credentials.

CodeWhen it happensHow to fix
unauthenticatedThe Authorization header is missing or is not a Bearer token.Send Authorization: Bearer <key> on every request.
key_invalidThe token is not a key the server recognizes.Check for whitespace and truncation; confirm you are using the right environment's key.
key_revokedThe key existed but has been revoked.Mint a new key in the console and rotate your deployment.
403

Forbidden

Type permission_error. The server knows who you are and the answer is no.

CodeWhen it happensHow to fix
insufficient_scopeThe key is valid but lacks the scope the route requires, for example calling plans:approve with a read-only key.Issue a key with the needed scope. Scopes are listed on each endpoint's route bar.
tenant_mismatchThe body carried a tenant_id or reseller_id that does not match the key.Remove the tenancy fields from the body; the server stamps them from the key. There is no cross-tenant key.
404

Not found

Type not_found_error.

CodeWhen it happensHow to fix
not_foundNo such resource for the authenticated tenant. An id that exists in another tenant also reads as not_found: existence is never disclosed across the tenancy wall.Verify the id and its prefix (ev_, op_, cn_, pl_, act_, rc_, exp_), and that the key belongs to the tenant that owns the resource.
409

Conflict

Type conflict_error. The request was well formed but collided with the current state. The two conflict codes with dedicated semantics, idempotency_conflict and entity_locked, are detailed below the table.

CodeWhen it happensHow to fix
state_conflictA lifecycle precondition failed: approving an executed plan, resuming a draft operator, testing a pending_auth connector, or creating a resource whose unique name is taken.Fetch the resource, check its status, and act on the state it is actually in.
idempotency_conflictAn Idempotency-Key was replayed with a different request body.Use a new key for a new request, or replay the original body unchanged. See below.
entity_lockedThe single-flight lock on an entity_key the request needs is held by other in-flight work.Wait for Retry-After, then retry the same request unchanged. See below.
connector_in_useUninstalling a connector that is the only fulfiller of a capability an active operator depends on.Pause the dependent operators or install another connector fulfilling the capability, then retry.
action_not_undoableUndoing an action whose tool declares no inverse, that never applied, or that was already undone.Check the action's undoable and undone fields before calling undo.
422

Unprocessable

Type validation_error. The request was understood and refused on its merits, usually by governance.

CodeWhen it happensHow to fix
policy_blockedEvery action in the request was refused by the fail-closed trust policy. The body carries the verdicts.This is the guardrail working. To allow the action, change the operator's guardrails in the Operators API; approval never overrides a BLOCK.
capability_unboundAn operator requests a capability no installed connector fulfills.Install a connector that fulfills the capability through the Connectors API.
listing_early_accessInstalling a marketplace listing that is early access rather than live.Request access; early-access listings are provisioned with your team during onboarding.
auth_failedConnector credentials were rejected by the source system at install or test.Re-check the credentials against the source system, then retry the install or test.
429

Too many requests

Type rate_limit_error. Budgets, windows, and quota mechanics are documented in Rate limits & quotas.

CodeWhen it happensHow to fix
rate_limitedThe request rate for the route class exceeded its window.Back off for Retry-After seconds, then resume. Watch X-RateLimit-Remaining to stay under.
quota_exceededA standing quota is exhausted: the monthly action allowance, or the concurrent export-job cap.For actions, overage runs at $0.01 per action per your plan settings; raise or lift the cap in the console. For exports, wait for a running job to finish.
5xx

Server errors

Type api_error. Something failed on our side or upstream of us. Retries are safe on any request that carried an Idempotency-Key and on all reads.

StatusCodeWhen it happensHow to fix
500internal_errorAn unexpected failure inside the platform.Retry with exponential backoff. If it persists, contact support with the request_id.
502connector_upstream_errorA connector's source system errored or was unreachable while handling your request.Check the connector's status and the source system's health; run a connector test.
503service_unavailableThe platform is briefly unable to serve the route, for example during a deploy.Retry after Retry-After. Ingest retries dedupe on the idempotency key, so nothing double-counts.

Idempotency conflict semantics

An Idempotency-Key binds a key to the exact request body it was first seen with. Three outcomes are possible on replay, and only one is an error:

ReplayResultMeaning
Same key, same body200 with the original responseThe safe retry. Nothing executed twice; you receive the stored result of the first attempt.
Same key, different body409 idempotency_conflictThe key is telling you your client has a bug: two different requests claimed to be the same one. Neither the first result nor the new body is acted on.
New keyNormal processingA genuinely new request.

Keys are scoped per tenant and route, and retained for 24 hours. Derive keys from the operation's natural identity, for example magento:SO-10884:v7 for an event or approve-pl_7c1a for an approval, rather than from random values, so a crashed-and-restarted worker replays instead of duplicating. Side-effecting plan actions additionally carry their own idempotency_key in the body, enforced by the executor itself; a replayed action disposes as DEDUP and is receipted, not errored. See Single-flight & idempotency.

Single-flight 409 semantics

The executor serializes side effects per entity_key: one thing in flight per order, per room, per asset. Inside a single plan this is invisible, actions on the same entity simply run in order. Across requests, it surfaces as a lock:

json · 409 entity_locked
HTTP/1.1 409 Conflict
Retry-After: 4

{
  "error": {
    "type": "conflict_error",
    "code": "entity_locked",
    "message": "order:SO-10884 is locked by plan pl_7c1a; retry after the in-flight work disposes.",
    "doc_url": "https://fibric.io/docs/errors#entity_locked",
    "request_id": "req_4d17ab02"
  }
}

Handling rules:

Working with request ids

Every response, success or failure, carries the request id, in the body for errors and in the X-Request-Id response header for everything. The same id appears in any receipt the request produced, so one identifier joins your client logs, our server logs, and the audit ledger.

bash · trace a failing request end to end
# capture the request id from the error body
curl -s -X POST https://api.fibric.io/v1/plans/pl_7c1a/approve \
  -H "Authorization: Bearer sk_live_3f9c2a7b8e1d4f60a2c9" \
  -H "Content-Type: application/json" \
  -d '{"approver": "l.ops@example.com"}' | jq -r '.error.request_id'
# req_9b3e21f0

# then find any receipt the request wrote
curl -s "https://api.fibric.io/v1/receipts?request_id=req_9b3e21f0" \
  -H "Authorization: Bearer sk_live_3f9c2a7b8e1d4f60a2c9"

Log the request id on every non-2xx response as a matter of course. When you contact support, it is the one field that lets us find the exact request without guessing from timestamps.

i
Refusals are receipted

When governance refuses work, a policy_blocked response or a BLOCK verdict inside a plan, the refusal is written to the receipt ledger like any success. An auditor can see not only what ran but what was refused, when, and by which rule.