Reference
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 expose async network and lifecycle methods 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, logger: logging.Logger | 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(). |
logger | logging.Logger | None | module logger | Optional logger for SDK diagnostics. |
Raises AlterSDKError if api_key is missing or malformed, if user_token_getter is present but not callable, or if the configured backend URL is invalid.
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. |
last_rate_limit | RateLimitSnapshot | None | Latest complete Alter rate-limit reading from a metered backend response. None means unknown, never unlimited. |
base_url | str | Backend base URL the client is pinned to. Diagnostic. |
Methods
Section titled “Methods”App exposes:
request(),proxy_request()list_grants(),mint_grant(),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)provider_specsnamespace (see Provider discovery)spansnamespace —spans.emit()(user-defined trace spans)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 request rule, evaluated server-side, that can only further restrict each request the returned client makes — it can never widen access. Supported shapes includejson_matchdeny rules,require_approvalHITL rules, andcontent_matchrules built withcontent_match_rule(...). 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. 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 request rule 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).
from alter_sdk import content_match_rule
narrowed = app.with_constraints(scopes=["grants:read"])await narrowed.list_grants()
# Attach a json_match deny rule to every request from this constrained client:restricted = app.with_constraints( rule={ "rule_type": "json_match", "rule_body": {"when": {"method": "POST"}, "effect": "deny"}, },)
# Attach an operation-aware content rule. The helper validates that# max_session_age_seconds is an integer in [1, 86400].fresh_session = app.with_constraints( rule=content_match_rule( operations=["messages.send"], effect="step_up", max_session_age_seconds=300, ),)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 completes any pending work. 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 through the Developer Portal.
App.create_managed_secret_grant()does not accept agent principals.
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, logger: logging.Logger | 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. |
logger | logging.Logger | None | module logger | Optional logger for SDK diagnostics. |
Agent pins caller_type=AGENT internally; it is not constructor-configurable.
It raises AlterSDKError for the same malformed key, getter, and backend-URL inputs as App.
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. |
last_rate_limit | RateLimitSnapshot | None | Latest complete Alter rate-limit reading from a metered backend response. None means unknown, never unlimited. |
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). |
oauth_providers | namespace | OAuth provider catalog (see Connect & Grants). |
provider_specs | namespace | Provider operation discovery (see Provider discovery). |
spans | namespace | User-defined trace spans (see spans.emit()). |
Methods
Section titled “Methods”Agent exposes:
me()— self-introspection (agent-only).request(),proxy_request()—request()requires a grant the agent may retrieve directly; a delegated grant is proxy-only, so useproxy_request()for it.list_grants()oauth_providers.list()— OAuth provider catalog.provider_specs.*— provider operation discovery.spans.emit()— user-defined trace spans.create_connect_session(),connect(),poll_connect_session(),create_connect_session_for_error()revoke_delegation()— self-revoke only.delegate()— onward agent-to-agent delegation.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 request 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: ...Identity helpers
Section titled “Identity helpers”verify_user_token
Section titled “verify_user_token”App only. Verify an IDP-issued JWT against the application’s configured identity provider.
async def verify_user_token(token: str) -> str | NoneReturns the verified sub claim on success. It returns None for an empty, invalid, expired, or malformed token; a missing identity-provider configuration; a closed client; an unexpected backend response; or a network failure. Verification fails closed and does not raise for these outcomes. Treat every None as unauthenticated.
resolve_identity
Section titled “resolve_identity”Return the canonical IdentityContext for downstream memory, routing, and authorization layers.
# Appasync def resolve_identity( *, user_token: str | None = None, include_profile: bool = False,) -> IdentityContext
# Agentasync def resolve_identity( *, user_token: str | None = None, app_user_id: str | None = None, include_profile: bool = False,) -> IdentityContextOn App, an explicit user_token wins over the configured user_token_getter; the getter is used when the argument is omitted. Agent never reads its configured getter for this identity-export method: pass user_token explicitly, or use app_user_id to resolve through an existing consent edge. include_profile=True asks the backend to include the optional email and display_name fields. Requires identity:resolve.
assert_identity
Section titled “assert_identity”Mint a short-lived, Alter-signed identity assertion for a downstream service.
# Appasync def assert_identity( *, audience: str, user_token: str | None = None, ttl_seconds: int | None = None, include_profile: bool = False,) -> IdentityAssertion
# Agentasync def assert_identity( *, audience: str, user_token: str | None = None, app_user_id: str | None = None, ttl_seconds: int | None = None, include_profile: bool = False,) -> IdentityAssertionaudience must be a URI with a scheme and at most 512 characters. ttl_seconds, when supplied, must be an integer from 10 through 300. User resolution follows the same App versus Agent rules as resolve_identity(). Returns IdentityAssertion; its token proves identity to the named audience but grants no credential access. Requires identity:assert.
Both methods raise AlterValueError for malformed local input and use the standard typed backend-error mapping for rejected identity requests or malformed responses.
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)