These pages describe Fibric’s target architecture. Public CLI, API, SDK, and sandbox access are not available. Examples do not establish deployed capabilities.
Errors
This page specifies proposed HTTP error shapes and codes for a future Fibric API. No public stable error contract exists today; the examples below are target semantics.
#The error envelope
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.
The specific, stable code, for example entity_locked. New codes may be added within a major version; existing codes never change meaning.
Human-readable detail, naming the field or resource involved. Safe to log; not safe to parse.
A link to the section of this page that documents the code.
Identifies the request in our logs and in any receipt the request produced. Include it when contacting support.
{
"error": {
"type": "conflict_error",
"code": "state_conflict",
"message": "Plan pl_7c1a is already executed and cannot be approved.",
"doc_url": "https://www.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?
| Status | Retry unchanged? | Why |
|---|---|---|
400 401 403 404 422 | No | The request itself is the problem. Sending it again produces the same answer; fix the request, the key, or the policy first. |
409 entity_locked | Yes, after Retry-After | The state that refused you is transient: the lock releases when the in-flight work disposes. |
409 all other codes | No | The state that refused you will not change on its own. Read the resource and act on its actual state. |
429 | Yes, after Retry-After | The request was never processed; the budget refills. |
500 502 503 | Yes, with backoff | Reads are always safe. Writes are safe when they carried an Idempotency-Key, which is why every write should. |
#Codes by HTTP status
Bad request#
Type invalid_request_error. The request never reached business logic. Do not retry unchanged.
| Code | When it happens | How to fix |
|---|---|---|
invalid_json | The body is not parseable JSON. | Fix the serialization; check for truncation and encoding. |
invalid_request | The 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_parameter | A required field is absent. message names it. | Send the field. Required fields are marked on each endpoint page. |
invalid_parameter | A 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_cursor | A pagination cursor is malformed, expired, or was issued for a different query. | Restart the listing from the first page and re-walk next_cursor. |
Unauthenticated#
Type authentication_error. The server does not know who you are. Do not retry with the same credentials.
| Code | When it happens | How to fix |
|---|---|---|
unauthenticated | The Authorization header is missing or is not a Bearer token. | Send Authorization: Bearer <key> on every request. |
key_invalid | The token is not a key the server recognizes. | Check for whitespace and truncation; confirm you are using the right environment's key. |
key_revoked | The key existed but has been revoked. | Mint a new key in the console and rotate your deployment. |
Forbidden#
Type permission_error. The server knows who you are and the answer is no.
| Code | When it happens | How to fix |
|---|---|---|
insufficient_scope | The 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_mismatch | The 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. |
Not found#
Type not_found_error.
| Code | When it happens | How to fix |
|---|---|---|
not_found | No such resource for the authenticated tenant. An id that exists in another tenant also reads as not_found: existence is never disclosed across the tenant boundary. | 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. |
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.
| Code | When it happens | How to fix |
|---|---|---|
state_conflict | A 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_conflict | An 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_locked | The 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_use | Uninstalling 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_undoable | Undoing 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. |
Unprocessable#
Type validation_error. The request was understood and refused on its merits, usually by governance.
| Code | When it happens | How to fix |
|---|---|---|
policy_blocked | Every 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_unbound | An operator requests a capability no installed connector fulfills. | Install a connector that fulfills the capability through the Connectors API. |
listing_early_access | The reference listing is managed early access rather than live in BearScope. | Request a managed review; no self-serve install path is public. |
auth_failed | Connector 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. |
Too many requests#
Type rate_limit_error. Budgets, windows, and quota mechanics are documented in Rate limits & quotas.
| Code | When it happens | How to fix |
|---|---|---|
rate_limited | The 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_exceeded | A proposed deployment quota is exhausted. | Reference behavior only; managed limits, caps, and commercial terms are agreed in writing. |
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.
| Status | Code | When it happens | How to fix |
|---|---|---|---|
500 | internal_error | An unexpected failure inside the platform. | Retry with exponential backoff. If it persists, contact support with the request_id. |
502 | connector_upstream_error | A 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. |
503 | service_unavailable | The 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:
| Replay | Result | Meaning |
|---|---|---|
| Same key, same body | 200 with the original response | The safe retry. Nothing executed twice; you receive the stored result of the first attempt. |
| Same key, different body | 409 idempotency_conflict | The 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 key | Normal processing | A genuinely new request. |
The target contract scopes keys per tenant and route; retention is configured per managed deployment. Stable natural keys can help the executor recognize retries, but they do not prove the downstream effect. Reconciliation remains required; 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:
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://www.fibric.io/docs/errors#entity_locked",
"request_id": "req_4d17ab02"
}
}
Handling rules:
- The lock is per entity, not per tenant. Work on other entities proceeds normally while one entity is locked.
- Retry the same request unchanged, with the same
Idempotency-Key, afterRetry-Afterseconds. If the in-flight work already did what your request would do, the idempotency layer dedupes and you get the stored result rather than a second side effect. - Do not treat
entity_lockedas failure in monitoring. It is the kernel refusing to interleave two writes on one entity, which is the behavior that prevents a double hold or a double refund.
#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.
# 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.
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.