Skip to main content

v8 → v9 Migration Guide

v9 is pre-release — read before acting

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 clientId and clientSecret; the platform separates your customer organization (org_id) and your API credential (client_id) as distinct concepts internally; tenant_id JSON fields and the X-Tenant-ID header 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:

  1. The customer organization that owns the data (the natural boundary for Row-Level Security).
  2. 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:

IdentifierWhat it isRLS-relevant?
org_idCustomer organization. Tenant-isolation boundary.Yes — Row-Level Security uses this in v9.
client_idAPI 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 identityThe 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_id is acme-corp. They issue three Basic Auth credentials — acme-prod-api, acme-staging-api, acme-batch-jobs. Each one is a distinct client_id inside the same org_id. Their license identifies the deployment — the value they used at deployment time (often the same as their org_id, but conceptually a separate field).
  • A Community-SaaS user cs_abc123 registered through try.getaxonflow.com. Their org_id is cs_abc123 and their client_id is also cs_abc123 (one customer, one credential — same value for both). The deployment identity is axonflow-community-saas (AxonFlow's installation, not theirs).
  • A self-hosted Community user who never set ORG_ID runs with org_id = local-dev-org (the default — stable across versions forever). Their client_id is 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/register still returns "tenant_id". The Plugin Pro Stripe checkout still has a tenant_id custom field. Same field, same value — the v9 model just calls it client_id semantically.
  • X-Tenant-ID HTTP header is accepted. The agent and orchestrator both honour it as a deprecated alias for X-Client-ID. The SDK and proxy emit both X-Client-ID and X-Tenant-ID outbound during the v9 compatibility window.
  • Basic Auth credentials are unchanged. Existing clientId / clientSecret pairs continue to work. The SDK already uses the v9-correct field name.
  • Plugin Pro license tokens are unchanged. The tenant_id field in the encoded token payload is still the Basic-Auth-derived value; v9 just calls that client_id in code.
  • License payload — V2 keeps working; V3 adds deployment_id alongside (shipped 2026-05-19). Phase 5 of the v9 epic (PR #2257) added a V3 license payload that ships a new deployment_id JSON field with the same semantics. V3 payloads also retain org_id so v8 (V2-only) readers continue to validate the same signed payload. The agent picks deployment_id when present and falls back to org_id otherwise. 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:

  1. AxonFlow-operated deployments are now axonflow--prefixed. Internal deployments such as community-saas, production-us, travel, healthcare, and banking are migrating to axonflow-community-saas, axonflow-production-us, etc. This affects only AxonFlow's own deployments — customer org_id values stay free-form (and should not start with axonflow- unless you're AxonFlow).
  2. The axonflow-internal- org_id prefix is now load-bearing. Telemetry classification uses the axonflow-internal- prefix as the primary signal for "this row came from AxonFlow's own infrastructure." If you happen to use an org_id matching that prefix in your own deployment, change it.
  3. 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 SECURITY table-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_ID env var (or its default local-dev-org if unset) keeps working unchanged.
  • Existing Basic Auth credentials keep working.
  • Existing data remains queryable.
  • Old X-Tenant-ID headers 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 / clientSecret pairs keep working.
  • Existing org_id rows 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_id as the JSON field name. In v9 terminology this value is both your org_id and your client_id (one customer, one credential).

No re-registration needed.

One thing that will break (after Phase 8 ships) — direct SQL on shared DB

Phase 8 — not yet shipped

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 itPre-v9 namev9 conceptual nameOn-wire field name
/api/v1/register responsetenant_idclient_id (and org_id for Community-SaaS, where they share a value)unchanged: tenant_id
Stripe Plugin Pro checkout custom fieldtenant_idclient_idunchanged: tenant_id
HTTP request headerX-Tenant-IDX-Client-IDboth accepted; X-Client-ID is canonical
SDK config (TS/Py/Go/Java)clientIdclient_idunchanged: clientId (was already v9-correct)
Basic Auth usernamethe cs_* / your credential IDclient_idunchanged: same string
License payload fieldV2 used org_id onlyV3 adds deployment_id (canonical)V3 mints both deployment_id and org_id; agent prefers deployment_id. V2-only payloads still validate.
Audit row columnvariesorg_id (customer) + client_id (credential)new client_id column added; legacy tenant_id remains as deprecated alias

Header migration timeline

VersionX-Client-IDX-Tenant-IDAgent → orchestrator forwarding
v8 pre-#2233not recognizedused internally for cross-process forwarding; tenant derived from Basic Auth on external requestsforwards X-Tenant-ID only
v8.x post-#2233 (current main)recognized inbound + forwarded outboundaccepted as aliasforwards both X-Client-ID and X-Tenant-ID
v9 (planned cut, gated on Phase 8)canonicalaccepted as deprecated aliasforwards both, with X-Client-ID as primary
v10 (planned, no date)requiredrejectedforwards 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_id and org_id on the V3 payload, with the same value. V2-only readers continue to validate signed payloads; V3-aware readers prefer deployment_id.
  • Exposes LicenseDeploymentID() accessor on the parsed license — call this in new code rather than reading .OrgID directly.
  • 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

Getting help

If a specific call started failing after a v9 upgrade, the most useful information for support:

  1. The exact endpoint (/api/v1/request, /api/v1/register, etc.).
  2. The Basic Auth clientId (first 8 chars only — never share the secret).
  3. Any X-*-ID headers you set (verbatim).
  4. The HTTP status code and response body.

[email protected] — please mention "v9 migration" in the subject so it routes correctly.