API Keys
API keys are the primary authentication mechanism for Ironflow’s API. Most /api/*, /ws, and ConnectRPC (/ironflow.v1.*) endpoints require a valid API key, with the exception of public endpoints: /health, /ready, /metrics, /api/v1/capabilities, /api/v1/auth/validate, /api/v1/platform/auth/login, and login routes.
Unified Key Model
Ironflow uses a single API key type with RBAC-based authorization. All keys are stored in the same api_keys table and follow the same authentication path. What a key can do is determined entirely by its assigned roles, not by its type.
Keys are differentiated only by a cosmetic prefix:
| Prefix | Purpose | Typical Roles |
|---|---|---|
ifkey_ | Tenant operations (functions, events, runs, etc.) | admin, developer, viewer |
ifplatform_ | Platform/cross-tenant operations | platform_admin, platform_operator, platform_viewer |
Both prefixes go through the same auth middleware and are validated identically. The prefix is a visual hint for operators — it has no effect on authorization.
All key database IDs use the ak_ prefix (e.g., ak_550e8400-e29b-41d4-a716-446655440000). This is distinct from the display prefix shown in key listings, which uses the original ifkey_ or ifplatform_ prefix plus the first 8 hex characters (e.g., ifkey_a1b2c3d4).
Key Format
API keys use a prefix followed by 32 random hex characters (16 cryptographically random bytes):
ifkey_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 (38 chars, tenant)ifplatform_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 (43 chars, platform)Keys are stored as SHA-256 hashes in the database — the raw key is never persisted. Once generated, a key can only be verified, not retrieved.
Creating Keys
Tenant Keys
# Org-scoped key (works across all environments)ironflow apikey create my-key
# With specific rolesironflow apikey create my-key --role role_abc123 --role role_def456
# Env-scoped key with expirationironflow apikey create my-key --env env_abc123 --expires 720h# Org-scoped keycurl -X POST http://localhost:9123/api/v1/apikeys \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "my-key", "role_ids": ["role_abc123"]}'
# Env-scoped keycurl -X POST http://localhost:9123/api/v1/apikeys \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "my-key", "env_id": "env_abc123", "expires_in": "720h"}'- Navigate to API Keys in the sidebar
- Click Create API Key
- Enter a name
- Choose scope:
- Select a specific environment from the dropdown to create an env-scoped key
- Select “All environments” to create an org-scoped key
- Optionally assign roles and set an expiration
- Click Create and copy the key immediately
Platform Keys
Platform keys authenticate cross-tenant operations (user management, tenant provisioning, impersonation).
ironflow apikey create my-platform-key --platformCreated Platform Key: my-platform-key (id: ak_h8i9j0k1)Key: ifplatform_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
Save this key — it will not be shown again.curl -X POST http://localhost:9123/api/v1/apikeys \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "my-platform-key", "platform": true}'Platform keys are assigned platform roles (platform_admin, platform_operator, platform_viewer) which control access to platform management endpoints.
The full key value is only returned once at creation time. Store it in a secrets manager.
Environment Scoping
Tenant API keys can optionally be scoped to a single environment. When an environment-scoped key authenticates, the server resolves the full hierarchy (environment → project → organization) and injects it into the request context for downstream authorization.
ironflow apikey create staging-key --env env_staging_abc123Scoping rules:
- One environment per key — A key can be scoped to at most one environment. It cannot be scoped to multiple environments.
- Org-scoped keys — When no environment is set on the key, the
EnvironmentIDin the request context remains empty. Some handlers (e.g., outbox, secrets) require the caller to explicitly passX-Ironflow-Environmentor?envin the request itself. The header/query fallback applies to JWT dashboard auth, not org-scoped API keys. - Platform keys — Never environment-scoped. They use impersonation (
X-Ironflow-Orgheader) to act on behalf of specific tenants. - Immutable scope — A key’s environment and organization cannot be changed after creation. To change scope, create a new key and delete the old one.
Authentication
HTTP Requests
Use the Authorization header with a Bearer prefix:
curl -H "Authorization: Bearer ifkey_a1b2c3d4..." http://localhost:9123/api/v1/functionsWebSocket Connections
WebSocket connections cannot always set custom headers. Use the token query parameter as a fallback:
ws://localhost:9123/ws?token=ifkey_a1b2c3d4...Both ifkey_ and ifplatform_ keys work with either method.
SDK Authentication
Both the Go and JavaScript SDKs pass the API key as a Bearer token in the Authorization header on every request.
client := ironflow.NewClient(ironflow.ClientConfig{ ServerURL: "http://localhost:9123", APIKey: "ifkey_a1b2c3d4...",})import { createClient } from "@ironflow/node";
const client = createClient({ serverUrl: "http://localhost:9123", apiKey: "ifkey_a1b2c3d4...",});import { ironflow } from "@ironflow/browser";
ironflow.configure({ serverUrl: "http://localhost:9123", auth: { apiKey: "ifkey_a1b2c3d4..." },});Dashboard Authentication
The web dashboard uses JWT cookies for authentication, not API keys. Users log in via /api/v1/auth/login and receive an ironflow_dashboard_token cookie (24-hour TTL). The auth middleware tries API key auth first; if no key is present, it falls back to the JWT cookie.
Validation Flow
The auth middleware runs on every non-public request. Here is the full validation sequence:
sequenceDiagram
participant Client
participant Middleware as Auth Middleware
participant Store as Database
participant RBAC as RBAC Evaluator
participant Audit as Audit Emitter
participant Handler as Route Handler
Client->>Middleware: HTTP request
Note over Middleware: Check if public path<br/>(/health, /ready, /metrics, etc.)
alt Public path
Middleware->>Handler: Pass through (no auth)
else Protected path
Middleware->>Middleware: Extract API key from<br/>Authorization header or ?token param
alt API key present
Middleware->>Store: Lookup key by SHA-256 hash
alt Key not found
Middleware-->>Client: 401 Invalid API key
else Key found
Middleware->>Middleware: Check expiration
alt Key expired
Middleware-->>Client: 401 API key expired
else Key valid
Middleware->>Store: Load roles via api_key_roles
Middleware->>Middleware: Resolve project from environment
Middleware->>RBAC: Evaluate policy (roles, action, resource)
Middleware->>Audit: Emit authz decision (async)
alt Denied
Middleware-->>Client: 403 Insufficient permissions
else Allowed
Middleware->>Store: Update last_used_at (async)
Middleware->>Middleware: Build RequestContext<br/>(org, project, env, roles)
alt Platform key with X-Ironflow-Org header
Middleware->>Middleware: Handle impersonation flow
end
Middleware->>Handler: Forward with RequestContext
Handler-->>Client: Response
end
end
end
else No API key
Middleware->>Middleware: Check for JWT dashboard cookie
alt Valid JWT
Middleware->>Handler: Forward with JWT context
Handler-->>Client: Response
else No valid auth
Middleware-->>Client: 401 Authentication required
end
end
end
Key Lifecycle
Rotation
Rotation creates a new key (new ID, new raw value) while preserving the name, environment, expiration, and role assignments from the old key. The old key is deleted atomically.
ironflow apikey rotate ak_abc123curl -X POST http://localhost:9123/api/v1/apikeys/ak_abc123/rotate \ -H "Authorization: Bearer $API_KEY"Rotation produces a new key ID (ak_*) and a new raw key value (ifkey_* / ifplatform_*). The raw key is only printed once — save it immediately. If your systems reference the old key ID, update them along with the raw key value.
Expiration
Keys can be created with an optional expiration duration:
ironflow apikey create short-lived-key --expires 720h # 30 daysironflow apikey create ci-key --expires 24h # 1 dayThe expiration is enforced at authentication time — any request with an expired key receives a 401 API key expired response. Expired keys cannot be rotated; delete them and create a new key instead.
Deletion
ironflow apikey delete ak_abc123curl -X DELETE http://localhost:9123/api/v1/apikeys/ak_abc123 \ -H "Authorization: Bearer $API_KEY"The key is immediately revoked. Any requests using this key will start receiving 401 errors.
Listing Keys
# List all tenant keysironflow apikey list
# List platform keysironflow apikey list --platform
# JSON outputironflow apikey list --jsonID NAME PREFIX ROLES CREATED LAST USED EXPIRESak_abc123 my-key ifkey_a1b2c3d4 role_abc123 2026-01-01T00:00:00Z 2026-03-01T12:00:00Z -# List tenant keyscurl -H "Authorization: Bearer $API_KEY" http://localhost:9123/api/v1/apikeys
# List platform keyscurl -H "Authorization: Bearer $API_KEY" http://localhost:9123/api/v1/apikeys?platform=trueThe full key value is never shown in list output — only the display prefix (ifkey_ or ifplatform_ + first 8 hex chars) for identification.
Bootstrap Key
On first boot, Ironflow generates a default admin API key (ID: ak_admin_bootstrap) with the admin role. The plaintext key is written to a file (mode 0400) and the server prints the file path; it is never written to stdout or journalctl. On subsequent restarts, only the key prefix is shown.
The file path is controlled by --bootstrap-key-file:
| Deployment | Default | Notes |
|---|---|---|
| Single binary / dev | <db-dir>/.ironflow_bootstrap_key.json | Same dir as the SQLite database. |
| Meta-cluster systemd | /run/ironflow/bootstrap-key.json | Set explicitly by the unit; tmpfs (auto-clears on reboot, never persisted to disk). |
Read the key with:
cat /path/to/bootstrap-key.json | jq -r .keyThe file contains JSON: {"key":"…","key_id":"ak_admin_bootstrap","org_id":"org_default","role":"admin","timestamp":"…"}.
Read, delete, rotate
Read the file once, delete it immediately (rm -f /path/to/bootstrap-key.json), then rotate via ironflow apikey rotate ak_admin_bootstrap. Do not leave the file on disk — anything that can read the file as the ironflow user can take over the meta-cluster. The structured log event BOOTSTRAP_ADMIN_KEY_ISSUED carries the file path (file_path field) but never the key, so central log shipping is safe to enable.
If the file write fails (read-only fs, ENOSPC, etc.), the server logs BOOTSTRAP_ADMIN_KEY_ISSUED with a file_path_error field and intentionally does not print the key anywhere — the operator must reset the bootstrap key (see the meta-cluster runbook’s Reset bootstrap admin key section).
Roles
Keys derive all their permissions from assigned roles. See Roles & Permissions for the full permissions matrix.
Tenant roles (for ifkey_ keys):
| Role | Description |
|---|---|
admin | Full access — all read, write, and delete operations |
developer | Read and write access — can create and modify resources |
viewer | Read-only access — can list and read resources |
Platform roles (for ifplatform_ keys):
| Role | Description |
|---|---|
platform_admin | Full access to all platform operations (wildcard) |
platform_operator | Tenant management, impersonation, read access to users/keys/roles |
platform_viewer | Read-only access to all platform resources |
Custom roles can be created for fine-grained access control.
FAQ
Can a key be scoped to multiple environments?
No. A key can be scoped to at most one environment. If you need access across multiple environments, create an org-scoped key (omit --env) or create separate keys per environment for tighter security.
Can I change a key’s environment or organization after creation?
No. A key’s scope (org, environment) and name are immutable. There is no update endpoint for keys. To change scope, create a new key and delete the old one. This prevents a distributed key from silently gaining or losing access.
What can I change on an existing key?
Only role assignments can be modified (PUT /api/v1/apikeys/{id}/roles). Everything else — name, environment, organization — is fixed for the lifetime of the key.
What’s the difference between omitting --env and using --env?
- Omit
--env: org-scoped key. The environment is resolved per-request from theX-Ironflow-Environmentheader, giving the key flexibility across environments. - Use
--env env_id: env-scoped key. The key is locked to that environment permanently.
How do I move a key from one environment to another?
You can’t. Delete the old key and create a new one scoped to the new environment.
What happens to expired keys?
Expired keys return 401 API key expired on every request. They cannot be rotated — delete and recreate. Expired keys remain in listings until explicitly deleted.