- Platform & Tenancy
- Impersonating Tenants
Impersonating Tenants
Impersonation lets platform administrators perform operations within a tenant’s organization without needing that tenant’s credentials. Every impersonated request is recorded in the audit trail.
When to Use Impersonation
Section titled “When to Use Impersonation”- Debugging — inspect a tenant’s functions, runs, or events
- Support — fix configuration issues on behalf of a tenant
- Provisioning — set up initial resources in a newly created tenant
CLI: The --as-org Flag
Section titled “CLI: The --as-org Flag”Add --as-org <org_id> to an Ironflow command to run it as the specified tenant:
# List a tenant's circuit breakersironflow --as-org org_x1y2z3w4 circuit-breaker list --env env_x1y2z3w4
# List a tenant's secret namesironflow --as-org org_x1y2z3w4 secret list --env env_x1y2z3w4The --as-org flag requires platform credentials (from ironflow platform login).
--as-org sends no environment selector. Without one the server targets env_default, and it answers 403 unless that environment belongs to the tenant. Name the tenant’s environment by ID with --env (circuit-breaker and secret take it), with IRONFLOW_ENV (inspect, subscribe, projection watch and stream subscribe read it), or use the HTTP form below. A command with no --env flag, such as env list or project list, works only for a tenant that owns env_default.
A tenant made with ironflow platform tenants provision is an organization with no environment, so it cannot be impersonated yet. Use ironflow tenant provision, which also creates the built-in roles, an environment and an admin key.
--as-org is a persistent root flag. These commands act on it: the REST commands
apikey, circuit-breaker, dead-letters, debounce, env, org, outbox,
policy, project, role, secret, tenant and webhook, plus inspect,
subscribe, projection watch and stream subscribe. The other ConnectRPC
commands — run, function, emit, invoke, sql, stream, projection —
accept the flag and ignore it, running against your own organization. Use the
HTTP form below to reach those resources in a tenant.
Two more command groups gain nothing from the flag. platform is the surface
you impersonate from, not into: its subcommands act on the whole instance, so
they refuse the flag with an error, as does policy --platform. capacity sends the header, but the server
ignores it there. It serves a global cross-tenant view filtered by --env /
--function alone, so the org never narrows it. The server ignores the header
on every /api/v1/platform/ and /api/v1/capacity/ route.
HTTP: The X-Ironflow-Org Header
Section titled “HTTP: The X-Ironflow-Org Header”For direct API calls, set the X-Ironflow-Org header to the target organization ID:
# List a tenant's functionscurl -H "Content-Type: application/json" -d '{}' http://localhost:9123/ironflow.v1.IronflowService/ListFunctions \ -H "Authorization: Bearer $PLATFORM_TOKEN" \ -H "X-Ironflow-Org: org_x1y2z3w4"Specifying an Environment
Section titled “Specifying an Environment”By default, impersonated requests target the tenant’s env_default environment. To target a different environment, add the X-Ironflow-Environment-ID header or the env_id query parameter:
curl -H "Content-Type: application/json" -d '{}' http://localhost:9123/ironflow.v1.IronflowService/ListFunctions \ -H "Authorization: Bearer $PLATFORM_TOKEN" \ -H "X-Ironflow-Org: org_x1y2z3w4" \ -H "X-Ironflow-Environment-ID: env_default_staging"The environment must belong to the impersonated organization. A platform principal has no environment of its own, so current is not special here. The server does not look up environment names: a name answers 400. An unknown ID answers 403. current becomes env_default, and the request answers 403 unless env_default belongs to the impersonated organization. Name the environment by ID.
WebSocket: The org_id Query Parameter
Section titled “WebSocket: The org_id Query Parameter”A browser cannot set a custom header on a WebSocket. To impersonate on /ws, put the organization in the org_id query parameter:
wss://ironflow.example.com/ws?env_id=env_x1y2z3w4&org_id=org_x1y2z3w4The server reads org_id only on a WebSocket upgrade of /ws. It ignores org_id on all other routes. If the request also has X-Ironflow-Org, the header wins. The checks are the same as for the header: the organization must exist, the caller must have platform:impersonate:read, and the environment must belong to the organization.
If the request has an Origin header, its host and port must be the Host the server receives, or the server answers 403. If you set IRONFLOW_WS_ALLOWED_ORIGINS, that allowlist decides instead. A client that sends no Origin, such as an SDK, is not affected. The dashboard adds org_id while you impersonate an organization.
Behind a reverse proxy, forward the Host header with its port. In nginx, use proxy_set_header Host $http_host;. $host drops the port, so an impersonated /ws gets 403. If your proxy cannot keep the port, set IRONFLOW_WS_ALLOWED_ORIGINS to the dashboard origin.
Tenant Credentials
Section titled “Tenant Credentials”Only platform credentials can impersonate. On an authenticated route, a tenant API key or a tenant session that sends X-Ironflow-Org (or org_id on /ws) with a different organization gets 403. The same header with the caller’s own organization is accepted. The public GET /api/v1/auth/validate ignores the header for a tenant and returns the tenant’s own data.
Read vs Write Access
Section titled “Read vs Write Access”Impersonation has two permission levels:
| Permission | Applies to | Description |
|---|---|---|
platform:impersonate:read | Endpoints whose RBAC action is a read (:read, :list, :subscribe) | Read-only access to tenant data |
platform:impersonate | Every other endpoint | Write access (create, update, delete) to tenant data |
The endpoint’s RBAC action decides, not the HTTP method. Every ConnectRPC call
is a POST, so ListFunctions (functions:list) needs only read
impersonation, while GET /api/v1/export (org:export) needs write. An
endpoint with no RBAC action falls back to the method: GET, HEAD and
OPTIONS count as reads. HEAD on a mapped route is a rough edge — the
action map has no HEAD arm, so it resolves to the resource’s write action
and needs full impersonation.
Built-in role assignments:
| Role | Read | Write |
|---|---|---|
platform_admin | Yes | Yes |
platform_operator | Yes | Yes |
platform_viewer | Yes | No |
Audit Trail
Section titled “Audit Trail”Every impersonated request generates a platform.impersonated audit event containing:
- The platform user or key that initiated the request
- The target organization ID and environment ID
- The impersonation level used (
platform:impersonateorplatform:impersonate:read)
The HTTP method and path are recorded on the companion authorization-decision event, not on platform.impersonated.
Query impersonation events:
ironflow platform audit --event-type platform.impersonatedFilter by target tenant (via HTTP API — this filter is not available in the CLI):
curl "http://localhost:9123/api/v1/platform/audit?impersonated_org_id=org_x1y2z3w4" \ -H "Authorization: Bearer $PLATFORM_TOKEN"See Platform Architecture for details on how the dual audit model works.
Next Steps
Section titled “Next Steps”- Managing Platform Users & Keys — create users and keys with impersonation roles
- Platform RBAC — configure which roles have impersonation access
- Platform API Reference — full endpoint documentation