Skip to content
Ironflow is pre-1.0: APIs can change between releases. Pin a version. What is ready →

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.


  • 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

Add --as-org <org_id> to an Ironflow command to run it as the specified tenant:

Terminal window
# List a tenant's circuit breakers
ironflow --as-org org_x1y2z3w4 circuit-breaker list --env env_x1y2z3w4
# List a tenant's secret names
ironflow --as-org org_x1y2z3w4 secret list --env env_x1y2z3w4

The --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.


For direct API calls, set the X-Ironflow-Org header to the target organization ID:

Terminal window
# List a tenant's functions
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"

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:

Terminal window
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.

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_x1y2z3w4

The 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.

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.


Impersonation has two permission levels:

PermissionApplies toDescription
platform:impersonate:readEndpoints whose RBAC action is a read (:read, :list, :subscribe)Read-only access to tenant data
platform:impersonateEvery other endpointWrite 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:

RoleReadWrite
platform_adminYesYes
platform_operatorYesYes
platform_viewerYesNo

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:impersonate or platform:impersonate:read)

The HTTP method and path are recorded on the companion authorization-decision event, not on platform.impersonated.

Query impersonation events:

Terminal window
ironflow platform audit --event-type platform.impersonated

Filter by target tenant (via HTTP API — this filter is not available in the CLI):

Terminal window
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.