Auth And Header Matrix
AxonFlow exposes several API surfaces, and they do not all authenticate the same way. This page is the reference for which credential or header model belongs to which surface.
That matters because many integration failures are not business-logic bugs. They are simply the wrong auth model applied to the wrong endpoint family.
Identity Primer (read this first)
AxonFlow uses three distinct identifiers. They appear in similar places and used to be conflated pre-v9. Keeping them straight removes most of the confusion around auth, headers, and audit rows.
| Identifier | What it is | Where you see it |
|---|---|---|
org_id | The customer organization that owns the data. This is the tenant-isolation boundary that Row-Level Security enforces under the v8.0.0 default of AXONFLOW_DB_USE_APP_ROLE=true. | DB rows, Basic-auth-derived request context, the X-Org-ID header when forwarded by the SDK |
client_id | The API credential / app identity that authenticated this request. One org can have many clients (prod, staging, per-service). | Basic Auth username, the X-Client-ID header (emitted since platform v8.0.0 as part of the v9 identity model), audit-log credential column |
| deployment license identity | The AxonFlow installation that booted this agent. Validated against the license at startup. Not written to customer rows. | License payload (V3 deployment_id field; V2 org_id field still accepted) + the ORG_ID env var on the agent |
A few practical examples:
- In-VPC Enterprise:
org_id = acme-corp(the customer),client_id = acme-prod-api(their production app credential), deployment identity = whatever the operator set at deployment time (commonly the same asorg_id). - Community SaaS:
org_id = cs_abc123andclient_id = cs_abc123— one credential per customer is the common case, so they share the same value. Deployment identity =axonflow-community-saas(AxonFlow's installation). - Self-hosted Community:
org_id = local-dev-org(the default; never changes unless you setORG_ID),client_id= your Basic Auth username, no license.
Why "tenant" terminology is being phased out: pre-v8 docs used tenant_id for both customer org and API credential because the codebase didn't separate them. v8.0.0 makes the distinction explicit. The on-wire tenant_id field in JSON responses is still emitted, and the X-Tenant-ID request header is still tolerated on current releases: the agent accepts the header but ignores it, logging a deprecation warning, and identity is always derived from the authenticated credentials (trusted internal-service calls from the customer portal are the one path that still consumes it). Removal of the deprecated field and header is not scheduled; when it happens it will land on a major version boundary with a migration note in the release notes. The full design lives in the v7 → v8 Migration Guide.
Quick Matrix
| Surface | Primary auth model | Important headers or fields | Notes |
|---|---|---|---|
Agent request path (/api/request) | SDK client credentials or direct request fields | client_id, user_token, optional Basic auth in SDK-driven flows | most application traffic starts here |
| Gateway and proxy SDK flows | Basic auth with clientId:clientSecret | Authorization: Basic ... | client secret is optional in community mode, required in enterprise-style deployments |
| Community SaaS plugin / SDK requests | Basic auth + optional X-License-Token | Authorization: Basic ..., X-License-Token: AXON-..., X-Axonflow-Client: <id>/<v> | per-credential tier resolution + scope check; see License Matrix Headers below |
| Orchestrator protected workflow APIs | Basic auth (org_id + client_id derived from credentials) | Authorization: Basic ..., X-Org-ID, X-Client-ID (forwarded) | identity derived server-side; X-Tenant-ID accepted as deprecated alias |
| MCP standalone policy checks | same application auth context as the calling runtime | request body plus normal client context | used when external orchestrators want policy-only checks |
| Per-user fleet identity (governed request planes) | per-user OIDC token, verified against the identity provider's JWKS | X-User-Token: <oidc-id-token> (or the user_token request field) | attributes a governed call to an individual developer on top of the app credential; roles resolve from SCIM, never from a token claim. See Per-Developer Identity |
| Customer portal APIs | session auth | axonflow_session cookie | established by password login or portal SSO (SAML or OIDC); used for most protected portal workflows |
| SCIM provisioning | bearer token | Authorization: Bearer ... | separate from portal session auth |
| Admin organization APIs | admin API key | X-Admin-API-Key | required in SaaS production, optional in some other deployments |
| Internal service hop (AxonFlow Agent to Orchestrator) | internal-service proxy token | X-Axonflow-Proxy-Auth, plus the trusted-hop headers below | not a client-facing surface; see Trusted-Hop Headers |
Public Runtime Patterns
SDK and application traffic
The public SDK guidance is centered on client credentials:
AXONFLOW_CLIENT_IDAXONFLOW_CLIENT_SECRET
The SDKs use Basic auth with:
Authorization: Basic base64(clientId:clientSecret)
At the request level, the runtime also uses fields such as:
client_iduser_token
This is why the public docs often talk about both SDK credentials and request identity fields. They are related, but not identical.
License Matrix Headers (X-License-Token + X-Axonflow-Client)
On Community SaaS endpoints (try.getaxonflow.com and self-hosted community-saas overlays), every governed plugin or SDK request can carry two additional headers that drive per-tenant tier resolution and scope validation per ADR-050:
X-Axonflow-Client: <client-id>/<version>
Identifies which plugin, integration or SDK is making the request. Set on every governed request automatically by the AxonFlow plugins (Claude Code, Cursor, Codex, OpenClaw, Claude Desktop), the integrations (Google ADK, n8n) and the SDKs (TypeScript, Python, Go, Java, Rust) — you don't set it manually.
Examples that the agent recognises:
| Header value | Maps to scope |
|---|---|
openclaw/2.1.0 | plugin |
claude-code-plugin/1.1.0 | plugin |
cursor-plugin/1.1.0 | plugin |
codex-plugin/1.1.0 | plugin |
sdk-typescript/7.8.0 | sdk |
sdk-python/7.8.0 | sdk |
sdk-go/7.8.0 | sdk |
sdk-java/7.8.0 | sdk |
claude-desktop-plugin/0.3.3 | full (the Claude Desktop governance proxy authenticates with its deployment's Basic credentials) |
mcp-proxy/0.3.2 | full — the id Claude Desktop sent through 0.3.2, still accepted so proxies already deployed keep being attributed while they upgrade |
google-adk-plugin/1.1.0 | plugin |
n8n-plugin/1.1.0 | plugin |
| (header absent) | full (default) |
The agent uses the derived scope to validate that any presented X-License-Token is authorised for that scope — a token issued for the SaaS Plugin path can't be used as SaaS SDK auth, and vice versa.
These integrations send this header and nothing else. Google ADK, n8n and the Claude Desktop proxy emit no heartbeat and no telemetry ping of any kind — the header rides a governed request the platform already receives, and identifies it. It is attribution only: the platform authenticates on Authorization, and a missing or mangled X-Axonflow-Client can never cause a request to fail.
The agent also captures the value into the Community SaaS telemetry table under the client column so operators can see per-plugin / per-SDK request distribution. On Enterprise deployments (platform v9.7.0+), the validated <client-id>/<version> pair is additionally recorded into the Prometheus client version-distribution metric on the decision and MCP check-output planes — telemetry-only, outside the auth path, with strict shape validation and a hard series cap.
X-License-Token: AXON-<base64url-payload>.<base64url-signature>
A Pro-tier license token issued by Stripe Checkout. Optional — absent means Free-tier baseline (3-day audit retention, 200 events/day). When present, the agent validates the signature, the aud claim against the SaaS Plugin path's accept list, the scope against X-Axonflow-Client via HasScope(), and the tenant binding against the Basic-auth-derived tenant.
Validation outcomes:
- Valid token + matching
plugin_user_licensesrow → Pro tier (30-day retention, 1000 events/day) for the duration of the token (90 days from purchase). - Token signature invalid → 401 with
invalid_license_tokenreason. - Token aud not in SaaS Plugin path accept list (e.g. a self-hosted token sent here) → 401 with
cross_quadrant_tokenreason. - Token aud accepted but scope mismatch (e.g. plugin-aud token + SDK client header) → 401 with
scope_mismatchreason. - Token
tenant_idmismatches Basic-auth-derivedclient_id→ 403 withtenant_mismatchreason. (Thetenant_idclaim on the token is the legacy field name; in v9 terminology it carries theclient_id. The wire field is unchanged; only the conceptual mapping changes.) - DB-level revocation (chargeback, dispute) → 401 within ~60 seconds (no caching).
For the full license matrix concept including all six canonical aud values, see License Matrix.
Orchestrator headers
Orchestrator surfaces use identity headers derived from Basic auth credentials. After v9, the canonical headers are:
X-Org-ID— the customer organization (RLS-enforced boundary).X-Client-ID— the authenticated API credential / app identity.X-User-ID— the human user, when applicable.
Compatibility through v9:
X-Tenant-IDis accepted as a deprecated alias forX-Client-ID. The agent and orchestrator both honour it on inbound requests; the agent's outbound proxy emits bothX-Client-IDandX-Tenant-IDuntil v10 removes the alias.- Inbound
X-Org-IDheaders are overwritten by auth-derived values to prevent client-side spoofing — only the values resolved by the auth middleware reach orchestrator handlers and audit writes.
See the Identity Primer above for the meaning of each identifier and how they differ from the license deployment identity.
Trusted-Hop Headers (never client-assertable)
A separate family of headers carries authority between AxonFlow's own services. They are not part of any client-facing contract, and a client may never assert them, in any spelling, on any route.
| Header | What it asserts | Availability |
|---|---|---|
X-Axonflow-User-Role | The caller's validated per-user authorization role. The agent sets it only from a validated per-user token, never from a request body or an identity header. | current releases |
X-Axonflow-Read-Scope | The calling service has already authorized this caller for tenant-wide reads under its own access model. | current releases |
X-Axonflow-Admin-Authority | The calling service has already authorized this caller as an administrator of the tenant. A separate axis from read scope: a caller may read tenant-wide without being an administrator. | platform v9.14.0+ |
X-Axonflow-Tenancy-Scope | The caller is bound to the organization, so the tenant header on the request is a display default rather than an authorization narrowing. | platform v10.0.0+ |
Two mechanisms keep them trustworthy, and both matter when you are reasoning about a deployment:
- The orchestrator honours them only on a request carrying a valid
X-Axonflow-Proxy-Authinternal-service token. Without one, the request resolves to least privilege regardless of what the headers claim. - The AxonFlow Agent deletes all four from every inbound client request before forwarding, then re-asserts the role only from a validated per-user token.
So sending one of these to a governed endpoint has no effect. It is not an error and not a rejection: the header is removed and the request proceeds at the authority the caller actually holds. If you run your own gateway or reverse proxy in front of AxonFlow, treat all four names as reserved and strip them on ingress as well, and take care that no rewrite rule maps client-controllable input onto them.
Note that X-Axonflow-Read-Scope is also used as a response header on the
orchestrator audit-read endpoints, where it reports the authority a read
resolved to (tenant, own-rows, or none) so that a 200 with zero rows can
be told apart from an empty audit trail. Request and response headers are
separate channels; the outbound value is diagnostic only and carries no
authority back inbound.
Protected Portal Patterns
Session-backed portal APIs
The customer portal uses login and session flows under /api/v1/auth/..., and successful login establishes the axonflow_session cookie. That cookie-backed session is then used for:
- usage and analytics
- API keys
- connectors and providers
- approvals
- exports
- SSO settings
SCIM
SCIM is its own auth model. The portal manages SCIM tokens, but actual provisioning requests use bearer-token auth against /scim/v2/....
Admin APIs
The admin organization surface uses:
- header:
X-Admin-API-Key - env var on the service side:
ADMIN_API_KEY
In the current middleware, SaaS production requires it. Other deployment modes are looser, but that should be treated as an operational choice, not as a reason to blur the auth model in client code.
Practical Advice
When an AxonFlow call fails, identify the endpoint family before changing credentials. A good debugging sequence is:
- Is this a public runtime call, a portal call, a SCIM call, or an admin call?
- Does this surface expect Basic auth, bearer token, session cookie, or admin key?
- Does the request also need tenant or org routing headers?
That sequence is faster than guessing between Authorization, tenant derived from Basic auth, and portal cookies after the fact.
Related Docs
- SDK Authentication
- Agent Endpoints
- Orchestrator Endpoints
- v7 → v8 Migration Guide — terminology rebrand, header aliases, RLS-enforcement caveats
- Enterprise Authentication
Operational Readiness Checklist
Before relying on this page in a production rollout, pair it with the core operations docs:
- Deployment Mode Matrix for self-hosted, Evaluation, Enterprise, SaaS, and In-VPC fit
- Failure Modes And Recovery for degraded-provider, connector, approval, and runtime behavior
- Capacity Planning for sizing and growth signals
- Community vs Evaluation vs Enterprise for limits, support surfaces, and upgrade triggers
