Skip to main content

API Error Codes

AxonFlow does not have one universal error enum shared perfectly across every handler. Different subsystems use slightly different response helpers. What is consistent enough for engineers to build against is:

  • standard HTTP status codes
  • common JSON error envelopes on most api/v1 handlers
  • recurring error codes such as VALIDATION_ERROR, NOT_FOUND, INTERNAL_ERROR, UNAUTHORIZED, CONFLICT, TIER_RESTRICTED and, in v11, LEGACY_POLICY_WRITE_FROZEN

That means the safest production integration strategy is:

  1. branch on HTTP status first
  2. inspect error.code when present
  3. log the full body for operator debugging

Common HTTP Statuses​

Status CodeMeaningDescription
200OKRequest successful
201CreatedResource created
204No ContentDelete or success without a body
400Bad RequestInvalid request parameters
401UnauthorizedMissing auth or missing tenant scope on some handlers
403ForbiddenTier restriction, proxy restriction, or governance denial
404Not FoundEndpoint or resource not found
409ConflictDuplicate resource or incompatible state; in v11 also a write to the legacy policy families (LEGACY_POLICY_WRITE_FROZEN)
415Unsupported Media Typecheck-input called with a content_type that has no registered redaction detector (fail-closed governance refusal)
500Internal Server ErrorUnexpected server error
503Service UnavailableA dependency is unavailable; in v11 also a request whose caller identity could not be established (subject_unverifiable)

Common Error Envelope​

Many orchestrator handlers return:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed"
}
}

Some older handlers instead return plain string bodies such as:

Connector not found

or:

{
"error": "Invalid request body"
}

So production clients should not assume every route returns the exact same envelope.

Frequently Seen Error Codes​

Error codeWhere you will see it
VALIDATION_ERRORPolicy APIs, provider APIs, and other typed control-plane handlers
INVALID_JSONTemplate and policy handlers when the request body is malformed
INVALID_REQUESTMedia governance and some gateway or legacy handlers
UNAUTHORIZEDMissing tenant scope or auth on structured handlers
TENANT_REQUIREDTenant-scoped handlers called without X-Tenant-ID. Added in platform v7.2.0.
NOT_FOUNDMissing providers, policies, templates, executions, or steps
CONFLICTDuplicate provider names or resource conflicts
LEGACY_POLICY_WRITE_FROZENv11: any write to the legacy system-policy, tenant-policy or /api/v1/policies families, and applying a policy template. Returns 409; the message names /api/v1/typed-policies. See Legacy policy write refused.
IDEMPOTENCY_KEY_MISMATCHWCP /gate or /complete supplied an idempotency_key that does not match the key recorded on the step's first gate call. Returns 409. See Retry Semantics & Idempotency.
LICENSE_ERRORLicense-gated provider creation failures
TIER_RESTRICTEDPaid-tier-only config such as media governance writes
FEATURE_REQUIRES_EVALUATION_LICENSEEvaluation-tier-or-higher feature attempted on a Community license. Examples: evidence export, policy simulation, retry-aware policy conditions (step.* fields on WCP policies). Returns 403. Upgrade path: getaxonflow.com/evaluation-license.
INTERNAL_ERRORBackend or storage failures

Practical Guidance​

  • Treat 400 and 401 as client-fixable issues.
  • Treat 403 as either a governance restriction, a tier restriction, or a deployment routing issue.
  • Treat 404 as a missing resource or a wrong endpoint.
  • Treat 500 as retriable operational failure after logging enough context.

For example:

  • POST /api/v1/tenant-policies returns 409 LEGACY_POLICY_WRITE_FROZEN in v11
  • POST /api/v1/llm-providers can return CONFLICT or LICENSE_ERROR
  • POST /api/v1/workflows/{id}/steps/{step_id}/gate and /complete can return 409 IDEMPOTENCY_KEY_MISMATCH when the supplied idempotency_key does not match the key recorded on the step's first gate call
  • PUT /api/v1/media-governance/config can return TIER_RESTRICTED
  • replay routes can return NOT_FOUND for missing executions or steps

Missing Tenant Scope (401)​

Platform v7.2.0 adds a dedicated TENANT_REQUIRED response for tenant-scoped handlers that previously fell back to an empty-string tenant when neither X-Tenant-ID nor a session-stored tenant was set. The fallback silently scoped SQL to WHERE tenant_id = '', so legitimate calls without the header returned zero rows while sharing a global empty-tenant quota bucket with every other unauthenticated caller.

{
"error": {
"code": "TENANT_REQUIRED",
"message": "Missing tenant context: caller must provide X-Tenant-ID header"
}
}

Handlers that now fail closed with the structured 401 TENANT_REQUIRED response above:

  • POST /api/v1/evidence/export
  • GET /api/v1/evidence/summary
  • POST /api/v1/policies/simulate
  • POST /api/v1/policies/impact-report
  • POST /api/v1/plans/estimate
  • GET /api/v1/plans/{id}/cost

A few other tenant-scoped handlers also require X-Tenant-ID and reject the call with 401 when it is missing, but return a plain-text error message rather than the structured TENANT_REQUIRED envelope:

  • GET /api/v1/decisions/{id}/explain — 401 with body {"error": "X-Tenant-ID header is required"}
  • POST /api/v1/audit/search — 401 with body {"error": "tenant scoping required"}
  • GET /api/v1/audit/tenant/{tenant_id} — 401 with body {"error": "X-Tenant-ID header is required"} plus a 403 tenant scope mismatch when the URL tenant differs from the header

In practice the agent proxy injects X-Tenant-ID from the authenticated Basic-auth client, so well-formed SDK and Portal traffic never sees either error shape. They surface most often when a caller hits the orchestrator directly without the proxy-auth layer.

Legacy policy write refused (409)​

In v11 the legacy system-policy and tenant-policy rows are read-only. A write to either family, through the Agent, the Orchestrator or the Customer Portal, and an apply of a policy template, answers:

{
"error": {
"code": "LEGACY_POLICY_WRITE_FROZEN",
"message": "The legacy policy tables are read-only in v11: migrations/core/172 revoked write access from the application role, and this endpoint writes them. Author policies through the typed authoring route at /api/v1/typed-policies instead. Reads on this endpoint are unaffected."
}
}

Do not retry it: no licence, edition or upgrade changes the answer. Author the policy through typed policy authoring instead.

Identity refusals (401, 403, 503)​

In v11 every governed request's caller is admitted through the identity plane after it authenticates:

  • A bad credential is refused 401 before any policy runs, and so is a user token whose signature fails on a deployment that verifies user identity.
  • A user token the identity plane does not admit is refused with the identity reason: a deny verdict on POST /api/v1/decide, 403 on Gateway Mode's pre-check and on Proxy Mode's POST /api/request, and a withheld 403 on the MCP response pass. The reasons a caller will meet include UNKNOWN_REALM (the token's issuer is declared by no trust realm, or it has no issuer), ORG_BINDING_MISMATCH, SUBJECT_MISSING, CREDENTIAL_EXPIRED, CREDENTIAL_NOT_YET_VALID and CREDENTIAL_REVOKED.
  • When admission cannot reach an answer, for example because a revocation source is unavailable, the request fails closed with 503 and subject_unverifiable.

A request with no user token is decided for its client credential and is not refused for the missing token. On Community and Community SaaS, which verify no per-user identity, a user token is ignored and every request is decided for its client credential, so none of these identity refusals applies there. Identity Compatibility Mode lists what changed in v11.

Policy Denial (403)​

Returned when a governance policy blocks the request. The body differs by plane, and the safe integration assumption is that policy denials are not normalized to one shared POLICY_DENIED envelope everywhere. On POST /api/v1/decide a refusal is a 200 response whose verdict is deny and whose reasons carry the engine's reason, for example explicit_constraint (a policy whose action is block matched) or unsupported_obligation (a mandatory safeguard the calling enforcement point cannot carry out).

Rate Limiting (429)​

Returned when the client exceeds configured rate limits:

{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded",
"details": {
"limit": 100,
"remaining": 0,
"reset_at": "2026-03-29T12:00:00Z"
}
}
}

MCP Policy Block (403)​

Returned by check-input or check-output when MCP policy evaluation blocks:

{
"allowed": false,
"block_reason": "Blocked by policy corpus:static_policies:sql__injection__union",
"policies_evaluated": 3
}

Note: MCP check endpoints return a flat response (not the nested error envelope).

Unsupported Content Type (415)​

POST /api/v1/mcp/check-input rejects a request whose content_type names no registered redaction detector, so a caller cannot forward content the engine cannot govern:

{
"success": false,
"error": "no redaction detector registered for content_type: image/png",
"blocked": false
}

An empty or omitted content_type defaults to the built-in text detector, so existing callers never see this status.

SDK Error Handling​

Python​

from axonflow.exceptions import (
AxonFlowError,
PolicyViolationError,
RateLimitError,
AuthenticationError,
)

try:
response = client.proxy_llm_call(...)
except PolicyViolationError as e:
print(f"Blocked by policy: {e.block_reason}")
except RateLimitError as e:
print(f"Rate limited, retry after: {e.reset_at}")
except AuthenticationError:
print("Invalid credentials")
except AxonFlowError as e:
print(f"API error {e.status_code}: {e.message}")

TypeScript​

import { AxonFlowError } from '@axonflow/sdk';

try {
const response = await axonflow.proxyLLMCall({...});
} catch (error) {
if (error instanceof AxonFlowError) {
if (error.statusCode === 403) {
console.error('Policy blocked:', error.message);
} else if (error.statusCode === 429) {
console.error('Rate limited');
}
}
}

Go​

response, err := client.ProxyLLMCall("user-123", "query", "chat", nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
if response.Blocked {
fmt.Printf("Blocked by policy: %s\n", response.BlockReason)
}

Operational Readiness Checklist​

Before relying on this page in a production rollout, pair it with the core operations docs: