Reference
alter sdk-passthrough
Make one-off runtime provider requests and inspect resolved identity.
sdk-passthrough is an escape hatch for making one-off runtime SDK calls — the kind of thing you’d otherwise do with the runtime SDK’s request() or resolveIdentity(). It exists for debugging (“fetch a token for this grant and hit the provider once to see what comes back”, “which identity does the backend resolve for this key?”), deliberately namespaced so it reads as “I’m doing something unusual” rather than blending in with the management commands.
alter sdk-passthrough request <grant-id> --url <url> [--method ...] [--body ...] [--header ...] [--api-key <key>]alter sdk-passthrough resolve-identity [--user-token <jwt>] [--app-user-id <uuid>] [--include-profile] [--agent-only] [--api-key <key>]request
Section titled “request”Make a single authenticated call against a grant. The grant must be active and owned by the calling app.
Required key scopes. request retrieves the credential over the token channel, calls the provider directly, then reports the outcome over the audit channel — the key needs tokens:retrieve and audit:emit. Without audit:emit the provider call still succeeds but its api-call audit row is dropped, and the CLI prints Audit log failed with status 403 (non-fatal) naming the missing scope. (Proxied calls made with proxy_request() / proxyRequest() in the SDKs need proxy:execute instead.)
| Flag | Required | Description |
|---|---|---|
--url <url> | yes | The full provider URL to call (e.g. https://www.googleapis.com/oauth2/v3/userinfo). Must be http:// or https://. There is no relative-path resolution — pass the complete URL. |
--method <method> | no | HTTP method (default GET). One of GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. |
--body <json> | no | JSON text parsed locally, then passed as the provider request body. Invalid JSON exits with usage code 2 before any request. |
--header <kv> | no | Extra header in Name: value form. Repeatable; a later occurrence of the same header name overwrites the earlier value. Reserved headers (the Authorization header and Alter’s own request headers) are rejected — Alter injects the credential for you. |
--api-key <key> | no | Runtime API key, if not using ALTER_API_KEY. The CLI validates the complete format locally: scoped keys use a supported prefix, 22 lowercase-base32 random characters, and a matching 6-character CRC; legacy alter_key_… keys use exactly 40 URL-safe body characters. PAT plaintext (alter_pat_…) is rejected — use alter auth login for that. Inline values print a shell-history warning. |
The response body goes to stdout (pipe it into jq); the status line and response headers go to stderr, so the body stays clean for downstream tools.
A provider error keeps the same layout: the HTTP <status> line and an explanatory message go to stderr, the provider’s error body goes to stdout, and the command exits 1. The body is the diagnostic (a validation error naming the field, a “database resuming” notice, a rejected credential), so a script that keys off the exit code can still read it.
Stderr also carries an alter-rate-limit: line reporting Alter’s own remaining budget for the key — alter-rate-limit: 994/1000 remaining, resets 2027-01-15T08:00:00.000Z. It is namespaced so it cannot be confused with the provider’s response headers printed beside it, which describe a different budget entirely. The line is omitted when the backend reported no budget for the call.
export ALTER_API_KEY="alter_rk_..."
alter sdk-passthrough request <grant-id> \ --url https://www.googleapis.com/oauth2/v3/userinfo \ | jq .
# With a bodyalter sdk-passthrough request <grant-id> \ --method POST \ --url https://slack.com/api/chat.postMessage \ --body '{"channel":"#general","text":"hello"}'Stub applications
Section titled “Stub applications”When the grant belongs to a stub application, the call is served by the stub provider rather than the real one. If that application is reached on a deployment where stub-provider mode is not enabled, the request is refused with conflict code 5 and a message explaining that the application cannot be served there.
That refusal is terminal, not transient: it holds for as long as the application and the deployment disagree, so retrying the same call can never succeed. Scripts should treat exit 5 here as “this application cannot run against this deployment” and stop, rather than backing off and retrying.
resolve-identity
Section titled “resolve-identity”Resolve the canonical identity context for the runtime key (the SDK’s resolveIdentity() — requires the identity:resolve scope on the key). Prints the identity JSON plus, when the key’s identity is eligible, the derived private memory partition (memory_scope), so an operator can verify exactly which app / agent / end-user identity the backend resolves and which partition the SDK’s ScopedMemoryStore would enforce for it. The partition is diagnostic metadata, never a credential.
| Flag | Required | Description |
|---|---|---|
--user-token <jwt> | no | End-user IDP JWT to resolve. Validated against the application’s configured identity provider. Because this value is on argv, the CLI warns that it may appear in shell history/process listings; prefer an environment variable or file-backed command substitution. |
--app-user-id <uuid> | no | Agent-only consent-edge shortcut: resolve this end user without a token. Requires a key bound to a managed agent that holds a delegation from that user — keys minted on the agent-keys surface currently use the legacy alter_key_… prefix; alter_ak_… keys are accepted too. The backend authorizes on the key’s agent binding, not its prefix; the CLI only rejects definitively non-agent prefixes (alter_rk_…, alter_dk_…, alter_pk_…) locally. |
--agent-only | no | Derive the headless application + agent partition for a verified agent key with no represented user. Off by default; without it an active represented user is required. The flag can never drop a user from an identity that represents one (memory_user_cannot_be_dropped). |
--include-profile | no | Opt into PII (email + display name) on the response. Off by default. |
--api-key <key> | no | Runtime API key, if not using ALTER_API_KEY. Same rules as request above. |
With neither --user-token nor --app-user-id, the call is headless and resolves the key’s own identity — the application, plus the agent identity when the key is bound to a managed agent.
The JSON document also carries a rate_limit object — limit, remaining and reset_at — reporting Alter’s own remaining budget for the key after this call, so a script can throttle itself before it is throttled. It is null when the backend reported no budget: unknown, never unlimited. See Errors for the header contract behind it.
There is deliberately no assert-identity counterpart: assertion-minting produces a signed credential-shaped token whose only consumers are programmatic verifiers, so it ships on the SDKs only.
# An agent-BOUND key (minted on the agent-keys surface).export ALTER_API_KEY="alter_key_..."
# Which identity does this key resolve for this user's JWT?alter sdk-passthrough resolve-identity --user-token "$USER_JWT" | jq .
# Consent-edge shortcut (agent key + a delegation from this user)alter sdk-passthrough resolve-identity --app-user-id 22222222-2222-2222-2222-222222222222 | jq .memory_scope
# Headless agent: the explicit application + agent partitionalter sdk-passthrough resolve-identity --agent-only | jq .memory_scopePrivate memory scope
Section titled “Private memory scope”Two fields describe the partition, and ineligible identities still print successfully for inspection:
| Field | Shape | Meaning |
|---|---|---|
memory_scope | object or null | The SDK’s MemoryScope as JSON: the versioned v2 namespace array, the slash-joined partition_key, and the user_key / agent_key / app_key correlation labels. null when no private partition can be derived for this identity. |
memory_scope_error | { "code": ..., "message": ... } or null | Why no partition was derived. code is the SDK’s stable MemoryIsolationError.code (for example memory_agent_required for an application-only key, or memory_user_required for a headless agent key without --agent-only); scripts branch on code, never on message. null when memory_scope is set. |
# A script branches on the stable code, never on the message text.alter sdk-passthrough resolve-identity --output json \ | jq -r '.memory_scope_error.code // "ok"'These keys are metadata, not credentials; never accept this JSON as remote identity proof, and never derive ownership from conversation, task or run labels. The full code table is in Errors; the enforcement model is in memory enforcement and migration.
Recipes
Section titled “Recipes”Debug a grant from scratch
# 1. Mint a runtime key (printed once) and export it.alter keys mint --name debug --scopes tokens:retrieve --output jsonexport ALTER_API_KEY="alter_rk_..."# 2. Call the provider through the grant and inspect the response.alter sdk-passthrough request <grant-id> --url https://www.googleapis.com/oauth2/v3/userinfo | jq .Related
Section titled “Related”alter keys mint— create the runtime key this command uses- Python SDK / TypeScript SDK — the supported way to make requests from application code