Python SDK
Client
App and Agent constructors, lifecycle, and identity helpers.
The SDK exposes two top-level client classes — App for application/operator credentials and Agent for workload-scoped credentials. Both are async and share the request surface defined in request().
from alter_sdk import App, Agent, CallerType
# Application-sideapp = App(api_key="alter_rk_…")
# Workload-sideagent = Agent(api_key="alter_ak_…")
# Lifecycleawait app.close()async with Agent(api_key="alter_ak_…") as a: ...The application-side client. Use for operator/admin work (provisioning agents, minting Connect sessions, managing grants) and for application-backend workloads acting under a stored grant_id.
App( api_key: str, *, timeout: float = 30.0, caller: str | None = None, caller_type: CallerType | str = CallerType.SERVICE, user_token_getter: Callable[[], str | Awaitable[str]] | None = None,)| Parameter | Type | Default | Description |
|---|---|---|---|
api_key | str | — | App API key (alter_rk_…). Required. |
timeout | float | 30.0 | HTTP request timeout in seconds. |
caller | str | None | None | Optional caller identifier for audit attribution. |
caller_type | CallerType | str | CallerType.SERVICE | SERVICE (default — backend infrastructure) or AGENT (shows in the Agents tab). |
user_token_getter | callable | None | Optional sync/async callable returning a user JWT for identity-mode request(). |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
actor_id | str | None | Cached actor UUID once the first authenticated call resolves. |
last_retry_info | RetryInfo | None | Retry metadata from the most recent request() call. |
base_url | str | Backend base URL the client is pinned to. Diagnostic. |
Methods
Section titled “Methods”App exposes:
request(),proxy_request(),boto3_client()list_grants(),revoke_grant(),revoke_delegation(),create_managed_secret_grant()create_connect_session(),create_managed_secret_connect_session(),connect(),poll_connect_session(),create_connect_session_for_error()authenticate(),create_auth_session(),poll_auth_session(),verify_user_token()get_approval_status(),await_approval()resolve_identity(),assert_identity()— the identity-export surface (see Propagate identity into memory layers and Identity types)agentsnamespace (see Agents & Keys)keysnamespace (see Agents & Keys)scopesnamespace (see Calling APIs)oauth_providersnamespace —oauth_providers.list()(OAuth provider catalog)with_constraints(),get_agent()(below)
with_constraints
Section titled “with_constraints”Return a macaroon-style constrained sub-client. Every request from the returned App carries a permanent attenuation that can only narrow access, never broaden it. Two attenuations are available, and at least one of scopes / rule must be supplied:
scopesnarrows the underlying key to the supplied scope set. The supplied scopes must already be implied by the key — the backend rejects broadening attempts with HTTP 400constraint_not_narrowing.ruleattaches an optional one-off rule, evaluated server-side, that can only further restrict each request the returned client makes — it can never widen access. It is either a deny rule or a require-approval (HITL) rule. Deny shape:{"rule_type": "json_match", "rule_body": {"when": {<attr>: str | list[str]}, "effect": "deny"}}, where<attr>is one ofmethod,provider_id,app_id,agent_id,api_key_id,environment,client_ip,resource_kind, andeffectis always"deny". Require-approval shape:{"rule_type": "require_approval", "rule_body": {"effect": "require_approval", "approval": {"approvers"?: [...], "channels"?: ["email"], ...}, "when"?: {...}}}— a matching request is held for human approval (returned as a pending approval) instead of denied;channels: []sends no email. A raw-token retrieval carries no request method/URL, somethod-keyed conditions are treated as satisfied there (a raw token can issue any method) — scope method restrictions through the proxy surface, which carries the request method. Endpoints cannot be scoped by a rule (the URL is not a matchable attribute). If a request is frozen for human approval (HITL 202), the per-request rule is not re-evaluated on the deferred post-approval execution — it cannot widen access (the rule did not match the frozen request, and the grant plus every stored policy rule still bound the execution), but prefer a stored rule when the constraint must hold at execute time.
App.with_constraints(*, scopes: list[str] | None = None, rule: RequestRule | dict[str, Any] | None = None) -> App| Parameter | Type | Default | Description |
|---|---|---|---|
scopes | list[str] | None | None | Optional non-empty list of scope strings to attenuate to. |
rule | RequestRule | dict | None | None | Optional one-off rule (deny, or require-approval/HITL) carried on every request the returned client makes; enforced on credential-using calls (token retrieval, proxied provider requests). |
At least one of scopes / rule is required.
Raises: AlterValueError (neither scopes nor rule supplied, malformed scopes / rule, nested constraint call), AlterSDKError (client closed).
narrowed = app.with_constraints(scopes=["grants:read"])await narrowed.list_grants()
# Attach a one-off deny rule:restricted = app.with_constraints( rule={ "rule_type": "json_match", "rule_body": {"when": {"method": "POST"}, "effect": "deny"}, },)get_agent
Section titled “get_agent”App.get_agent(agent_id: str | UUID) -> Agent| Parameter | Type | Default | Description |
|---|---|---|---|
agent_id | str | UUID | — | Managed-agent UUID. Must parse as a UUID. |
Returns: an Agent instance impersonating agent_id.
Raises: AlterValueError (empty / non-UUID input, App has been closed).
agent = app.get_agent(agent_id="11111111-2222-3333-4444-555555555555")me = await agent.me()The parent App’s user_token_getter is intentionally NOT inherited. To bridge a user JWT through the impersonated agent, construct Agent(api_key=…, user_token_getter=…) directly.
Lifecycle
Section titled “Lifecycle”await app.close()
async with App(api_key="alter_rk_…") as app: ...close() shuts down the underlying HTTP clients and drains background tasks. The async context manager is the recommended pattern.
The workload SDK client. Use for AI agents and any code that should be identity-scoped to a managed agent. The agent can reach two kinds of credentials:
- User-delegated OAuth grants — a user completed Connect with
agent=<this-agent>, creating a per-agent delegation record that authorizes the agent against the user’s grant. - Agent-owned managed-secret grants — an operator provisioned a managed secret directly to the agent via the Developer Portal or
App.create_managed_secret_grant(principal={"type": "agent", …}).
Agent.list_grants() returns both in one merged view.
Agent( api_key: str, *, timeout: float = 30.0, caller: str | None = None, user_token_getter: Callable[[], str | Awaitable[str]] | None = None,)| Parameter | Type | Default | Description |
|---|---|---|---|
api_key | str | — | Agent API key (alter_ak_…). Required. |
timeout | float | 30.0 | HTTP request timeout in seconds. |
caller | str | None | None | Optional caller identifier for audit attribution. |
user_token_getter | callable | None | Optional sync/async callable returning a user JWT. Used for Connect-time delegation and per-call delegation disambiguation. The JWT does NOT grant access — the agent’s authority comes from the per-agent delegation record bound at Connect time. |
Agent pins caller_type=AGENT internally; it is not constructor-configurable.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
actor_id | str | None | Cached actor UUID once the first authenticated call resolves. |
last_retry_info | RetryInfo | None | Retry metadata from the most recent request() call. |
base_url | str | Backend base URL the client is pinned to. |
keys | namespace | Scoped-key lifecycle (see Agents & Keys). |
scopes | namespace | Scope catalog discovery (see Calling APIs). |
Methods
Section titled “Methods”Agent exposes:
me()— self-introspection (agent-only).request(),proxy_request(),boto3_client()list_grants()oauth_providers.list()— OAuth provider catalog.create_connect_session(),connect()revoke_delegation()— self-revoke only.get_approval_status(),await_approval()resolve_identity(),assert_identity()— the identity-export surface; the agent shape additionally accepts the consent-edgeapp_user_idshortcut (see Propagate identity into memory layers)trace()— audit-scope context manager.with_constraints()(below)
Operator surfaces (agents namespace CRUD, authenticate, verify_user_token, revoke_grant, create_managed_secret_grant) are intentionally absent — calling them with an agent key would raise typed errors deep in the request path.
with_constraints
Section titled “with_constraints”Identical semantics to App.with_constraints() — returns a constrained sub-Agent whose every request carries the same attenuation (the rule is enforced on credential-using calls). Pass scopes to narrow the scope set, rule to attach a one-off deny rule, or both; at least one is required.
Agent.with_constraints(*, scopes: list[str] | None = None, rule: RequestRule | dict[str, Any] | None = None) -> AgentAsync context manager that scopes audit identity for every nested request() call. See Agent.trace() on the Agents page for the full surface.
Lifecycle
Section titled “Lifecycle”await agent.close()
async with Agent(api_key="alter_ak_…") as agent: ...is_valid_key
Section titled “is_valid_key”Validate the syntactic shape of an Alter API key without a network round-trip. Re-derives the CRC checksum from the key body and compares — catches single-character transcription errors.
from alter_sdk import is_valid_key
is_valid_key(plain_key: object) -> bool| Parameter | Type | Default | Description |
|---|---|---|---|
plain_key | object | — | Candidate key string. Non-strings and empty strings return False. |
Returns True only for syntactically-valid keys (legacy alter_key_* prefix-only, or scoped alter_<type>_<random>_<checksum> with a matching CRC). The check is a usability gate — a forged checksum still cannot pass backend verification.
from alter_sdk import App, is_valid_key
raw = input("Paste API key: ").strip()if not is_valid_key(raw): raise SystemExit("Invalid key shape")app = App(api_key=raw)