v8 → v9 Migration Guide
There is no v9 platform release available yet. v9 is in flight as Epic #2230. Several phases have already landed in the v8.x train (the identity plumbing, the client_id column additions, and the V3 license payload as of 2026-05-19), but the v9 cut itself waits on Phase 8 — FORCE ROW LEVEL SECURITY rollout completing its staged soak.
This page is published early so customers can plan ahead. Until v9 is cut:
- Don't pin to
axonflow:v9— that tag doesn't exist. - The "verifying after upgrade" smoke tests at the bottom of this page work today on current v8.x agents that include the Phase 4 X-Client-ID code path (merged in #2233).
- The single breaking change (direct SQL under
FORCE ROW LEVEL SECURITY) is not yet enforced in any released platform. It activates only when Phase 8 ships.
Watch the v9.0.0 release notes for the cut.
AxonFlow v9 is a terminology and enforcement cleanup, not a breaking API redesign. For most customers using the SDKs, plugins, or HTTP API normally, no application changes are required when v9 ships. This guide explains what's changing conceptually, what stays the same on the wire, and the one situation that does break (direct SQL queries on a shared DB) once Phase 8 RLS enforcement lands.
One-line summary: keep using
clientIdandclientSecret; the platform separates your customer organization (org_id) and your API credential (client_id) as distinct concepts internally;tenant_idJSON fields and theX-Tenant-IDheader keep working through v9 and are planned for removal in a future v10.
What's changed conceptually
Pre-v9, AxonFlow used tenant_id for two different things:
- The customer organization that owns the data (the natural boundary for Row-Level Security).
- The API credential / app identity that authenticated a given request.
That overload caused real bugs: Community-SaaS customers all shared org_id="community-saas" while their actual customer identity lived in tenant_id, and several audit/policy write paths left org_id empty. v9 separates the two concepts and adds a third for completeness:
| Identifier | What it is | RLS-relevant? |
|---|---|---|
org_id | Customer organization. Tenant-isolation boundary. | Yes — Row-Level Security uses this in v9. |
client_id | API credential / app identity inside an org. (One org can have many credentials: prod, staging, per-service.) | No — credentials inside one org can read each other's data subject to policy. |
| deployment license identity | The AxonFlow installation that booted this agent. Validated at startup against the license. | No — never written to customer-data rows. |
A worked example:
- An enterprise customer Acme Corp runs AxonFlow in their own VPC. Their
org_idisacme-corp. They issue three Basic Auth credentials —acme-prod-api,acme-staging-api,acme-batch-jobs. Each one is a distinctclient_idinside the sameorg_id. Their license identifies the deployment — the value they used at deployment time (often the same as theirorg_id, but conceptually a separate field). - A Community-SaaS user cs_abc123 registered through
try.getaxonflow.com. Theirorg_idiscs_abc123and theirclient_idis alsocs_abc123(one customer, one credential — same value for both). The deployment identity isaxonflow-community-saas(AxonFlow's installation, not theirs). - A self-hosted Community user who never set
ORG_IDruns withorg_id = local-dev-org(the default — stable across versions forever). Theirclient_idis their Basic Auth username.
What stays the same on the wire
The compatibility promise for v9:
- JSON field names are unchanged. The registration response from
/api/v1/registerstill returns"tenant_id". The Plugin Pro Stripe checkout still has atenant_idcustom field. Same field, same value — the v9 model just calls itclient_idsemantically. X-Tenant-IDHTTP header is accepted. The agent and orchestrator both honour it as a deprecated alias forX-Client-ID. The SDK and proxy emit bothX-Client-IDandX-Tenant-IDoutbound during the v9 compatibility window.- Basic Auth credentials are unchanged. Existing
clientId/clientSecretpairs continue to work. The SDK already uses the v9-correct field name. - Plugin Pro license tokens are unchanged. The
tenant_idfield in the encoded token payload is still the Basic-Auth-derived value; v9 just calls thatclient_idin code. - License payload — V2 keeps working; V3 adds
deployment_idalongside (shipped 2026-05-19). Phase 5 of the v9 epic (PR #2257) added a V3 license payload that ships a newdeployment_idJSON field with the same semantics. V3 payloads also retainorg_idso v8 (V2-only) readers continue to validate the same signed payload. The agent picksdeployment_idwhen present and falls back toorg_idotherwise. Customers do not need to regenerate licenses; existing V2 licenses keep working through v9 and beyond.
What you should know (but not necessarily do)
A few v9 internals worth understanding, even if you don't need to act on them:
- AxonFlow-operated deployments are now
axonflow--prefixed. Internal deployments such ascommunity-saas,production-us,travel,healthcare, andbankingare migrating toaxonflow-community-saas,axonflow-production-us, etc. This affects only AxonFlow's own deployments — customerorg_idvalues stay free-form (and should not start withaxonflow-unless you're AxonFlow). - The
axonflow-internal-org_id prefix is now load-bearing. Telemetry classification uses theaxonflow-internal-prefix as the primary signal for "this row came from AxonFlow's own infrastructure." If you happen to use anorg_idmatching that prefix in your own deployment, change it. - Row-Level Security is now actually enforced (after Phase 8). Pre-v9, RLS policies were defined but inert because the agent connected as the table owner, which bypasses RLS. v9 introduces a non-owner application role and progressively enables
FORCE ROW LEVEL SECURITYtable-by-table. This affects ad-hoc SQL only — see the "One thing that does break" section below.
What you need to do — usually nothing
Per deployment mode:
Self-hosted Community (Docker / docker compose up)
Required: nothing.
- Your
ORG_IDenv var (or its defaultlocal-dev-orgif unset) keeps working unchanged. - Existing Basic Auth credentials keep working.
- Existing data remains queryable.
- Old
X-Tenant-IDheaders keep working as deprecated aliases.
Optional (recommended): start using client_id terminology in your own application code instead of tenant_id. The SDK already does this; if you've extended it with custom code, the v9 model gives you a cleaner mental model.
In-VPC Enterprise
Required: nothing.
- Your existing license keeps working.
- Existing
clientId/clientSecretpairs keep working. - Existing
org_idrows keep their value.
Recommended (when v9 ships): when you next issue API credentials through your existing customer portal or admin workflow, treat each as a distinct client_id inside the same org_id rather than reusing one credential across environments. For example: org_id=acme-corp with client_id=acme-prod-api and a separate client_id=acme-staging-api. This unlocks per-credential audit attribution and per-credential rate limits in v9. No action is required at upgrade time — existing credentials keep working unchanged.
Community SaaS (using try.getaxonflow.com)
Required: nothing.
- Your existing
cs_*credential keeps working. - Your existing Plugin Pro entitlement keeps working.
- The registration response still returns
tenant_idas the JSON field name. In v9 terminology this value is both yourorg_idand yourclient_id(one customer, one credential).
No re-registration needed.
One thing that will break (after Phase 8 ships) — direct SQL on shared DB
FORCE ROW LEVEL SECURITY is the v9 cut's gating phase. It is not yet enforced on any released platform. This section describes the behaviour change that will land when Phase 8 completes its soak and v9.0.0 is cut. If you're reading this on a current v8.x agent, RLS remains decorative — the section below tells you what to prepare for, not what's failing today.
If you maintain a customer-portal or analytics dashboard that issues SQL directly against the AxonFlow application database (rather than calling the AxonFlow HTTP API), and that connection is using the application role (not the master role), then once Phase 8 enables FORCE ROW LEVEL SECURITY, those queries will return zero rows unless they set the app.current_org_id session variable first:
-- Before SELECT, set the current organization context.
-- The value is the same as your agent's ORG_ID env var (self-hosted / in-VPC)
-- or your cs_<uuid> credential (Community-SaaS). Connect as the master role
-- once to enumerate, e.g.:
-- SELECT DISTINCT org_id FROM organizations;
SELECT set_config('app.current_org_id', 'your-customer-org-id', false);
-- Then your SELECT respects RLS and returns rows for that org only:
SELECT * FROM audit_logs WHERE created_at > now() - interval '1 hour';
This is intentional — it's the security mechanism v9 unlocks. Operators with legitimate cross-org needs (analytics rollups, sweep workers, support tooling) should connect using the axonflow_platform_admin role, which has BYPASSRLS and is the explicit cross-org channel.
This only affects deployments where:
- you have direct DB access (typically self-hosted or your own In-VPC deployment), and
- your tooling bypasses the AxonFlow HTTP API.
Customers using only the SDKs / plugins / HTTP API are not affected.
Field-by-field cheat sheet
For programmatic consumers:
| Where you see it | Pre-v9 name | v9 conceptual name | On-wire field name |
|---|---|---|---|
/api/v1/register response | tenant_id | client_id (and org_id for Community-SaaS, where they share a value) | unchanged: tenant_id |
| Stripe Plugin Pro checkout custom field | tenant_id | client_id | unchanged: tenant_id |
| HTTP request header | X-Tenant-ID | X-Client-ID | both accepted; X-Client-ID is canonical |
| SDK config (TS/Py/Go/Java) | clientId | client_id | unchanged: clientId (was already v9-correct) |
| Basic Auth username | the cs_* / your credential ID | client_id | unchanged: same string |
| License payload field | V2 used org_id only | V3 adds deployment_id (canonical) | V3 mints both deployment_id and org_id; agent prefers deployment_id. V2-only payloads still validate. |
| Audit row column | varies | org_id (customer) + client_id (credential) | new client_id column added; legacy tenant_id remains as deprecated alias |
Header migration timeline
| Version | X-Client-ID | X-Tenant-ID | Agent → orchestrator forwarding |
|---|---|---|---|
| v8 pre-#2233 | not recognized | used internally for cross-process forwarding; tenant derived from Basic Auth on external requests | forwards X-Tenant-ID only |
| v8.x post-#2233 (current main) | recognized inbound + forwarded outbound | accepted as alias | forwards both X-Client-ID and X-Tenant-ID |
| v9 (planned cut, gated on Phase 8) | canonical | accepted as deprecated alias | forwards both, with X-Client-ID as primary |
| v10 (planned, no date) | required | rejected | forwards X-Client-ID only |
You may start using X-Client-ID on inbound requests today — the Phase 4 code path landed in PR #2233. You do not need to switch in v9 — v10 will be the version where X-Tenant-ID stops being accepted. Most callers never set either header explicitly; the SDKs and platform handle this internally.
License payload — V2 and V3
The V2 license payload uses an org_id JSON field for the deployment identity. The naming was a source of confusion because it's distinct from the customer-row org_id. V3 is shipped (Phase 5, PR #2257, 2026-05-19) and adds a new deployment_id field carrying the same semantics with a clearer name.
What v9 code does:
- Mints both
deployment_idandorg_idon the V3 payload, with the same value. V2-only readers continue to validate signed payloads; V3-aware readers preferdeployment_id. - Exposes
LicenseDeploymentID()accessor on the parsed license — call this in new code rather than reading.OrgIDdirectly. - Validates V2 payloads unchanged. Customers do not need to regenerate licenses for v9.
If you parse license JSON yourself and need to know "which field do I read?", read deployment_id first and fall back to org_id. Both will be the same value on a freshly-minted V3 license.
The org_id field on the license payload remains accepted through v9 as a back-compat alias and is planned for removal in a future v10 cut.
Smoke tests for the v9 identity plumbing
These tests work today on any current-main v8.x agent that includes the Phase 4 X-Client-ID code path (PR #2233). Use them now to confirm your callers are compatible with the v9 identity model before the v9 cut, and again after upgrading to v9 to confirm nothing regressed:
# 1. Confirm Basic Auth works (the most common breakage shape if anything regresses)
curl -X POST https://YOUR_ENDPOINT/api/v1/request \
-H "Authorization: Basic $(echo -n 'your-client-id:your-client-secret' | base64)" \
-H "Content-Type: application/json" \
-d '{"query": "ping", "request_type": "chat"}'
# 2. Confirm legacy X-Tenant-ID alias still resolves to your credential
curl -X POST https://YOUR_ENDPOINT/api/v1/request \
-H "Authorization: Basic $(echo -n 'your-client-id:your-client-secret' | base64)" \
-H "X-Tenant-ID: your-client-id" \
-H "Content-Type: application/json" \
-d '{"query": "ping", "request_type": "chat"}'
# 3. Confirm the new X-Client-ID header is accepted
curl -X POST https://YOUR_ENDPOINT/api/v1/request \
-H "Authorization: Basic $(echo -n 'your-client-id:your-client-secret' | base64)" \
-H "X-Client-ID: your-client-id" \
-H "Content-Type: application/json" \
-d '{"query": "ping", "request_type": "chat"}'
All three should return identical responses. The auth-derived identity always wins over headers — if header and Basic Auth credentials disagree, the agent uses the credentials.
Where each concept lives in the docs
- SDK Authentication — the
clientIdyou configure - Auth and Header Matrix — the wire-level identity headers, including the Identity Primer
- License Management — the deployment license identity (the third identifier)
- Community SaaS — where
cs_*values come from and how they map toorg_id/client_id - Plugin Pro — how the
tenant_idStripe field maps to your credential
Getting help
If a specific call started failing after a v9 upgrade, the most useful information for support:
- The exact endpoint (
/api/v1/request,/api/v1/register, etc.). - The Basic Auth
clientId(first 8 chars only — never share the secret). - Any
X-*-IDheaders you set (verbatim). - The HTTP status code and response body.
[email protected] — please mention "v9 migration" in the subject so it routes correctly.
