Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.alterauth.com/llms.txt

Use this file to discover all available pages before exploring further.

The Python SDK ships as the alter-sdk package on PyPI. Python 3.10+.
pip install alter-sdk
For end-to-end walkthroughs, see the Quickstart and Guides. This page is API reference only.

Clients

Two classes, both async.

App

The application-side client. Construct with an alter_key_app_… API key.
from alter_sdk import App

app = App(
    api_key: str,
    *,
    timeout: float = 30.0,
    caller: str | None = None,
    caller_type: CallerType = CallerType.SERVICE,
    user_token_getter: Callable[[], str | Awaitable[str]] | None = None,
)
Constructor args:
ArgTypeDefaultDescription
api_keystrApp API key. Required.
timeoutfloat30.0HTTP timeout in seconds.
callerstr | NoneNoneFree-form audit attribution, or an agent UUID for impersonation.
caller_typeCallerTypeSERVICESERVICE or AGENT. Drives audit grouping in the portal.
user_token_gettercallableNoneReturns the calling user’s JWT for JWT identity.
Supports async context manager: async with App(...) as app:.

Agent

The workload-side client. Construct with an alter_key_agent_… key (or via App.get_agent(agent_id) for the impersonation path).
from alter_sdk import Agent

agent = Agent(
    api_key: str,
    *,
    timeout: float = 30.0,
    caller: str | None = None,
    user_token_getter: Callable[[], str | Awaitable[str]] | None = None,
)
Agent exposes a strict subset of App — the methods that don’t apply to an agent principal (agents.* CRUD, revoke_grant, create_managed_secret_grant from the operator side, authenticate, verify_user_token) are absent. Calling them on an agent principal would raise typed exceptions deep in the request path anyway; the SDK forbids them at the class level.

Making requests

request and proxy_request are the two runtime modes. They share the same argument shape; the difference is where the third-party call happens and what the SDK returns.
request (retrieve)proxy_request (proxy)
Token returned to SDKYes — to the SDK processNo — stays on the backend
Third-party call made byThe SDKThe Alter backend
Return typehttpx.ResponseApprovalResult | PendingApproval
Scope required on the API keytokens:retrieveproxy:execute
HITL grantsRejected (hitl_grant_requires_proxy)Required path
A non-HITL grant works in either mode. Pick proxy when wire-level audit, strong token isolation, or backend-side policy enforcement matter; pick retrieve for long-lived clients, streaming, or any path where the extra hop hurts. See the runtime modes page for the full tradeoffs.

request(method, url, *, grant_id=..., provider=..., json=..., headers=..., query_params=..., reason=..., context=..., user_token=..., account=...) -> httpx.Response

Retrieve-mode call. The SDK fetches a token from the backend, injects it into the outgoing provider request, and makes the call itself. Returns an httpx.Response. Two ways to identify the grant:
  • grant_id=<uuid> — explicit.
  • provider=<slug> — resolved from the user’s JWT via the configured user_token_getter.
Other kwargs:
  • json — request JSON body.
  • headers — additional request headers (Authorization is auto-injected).
  • query_params — query-string parameters.
  • reason — human-readable explanation surfaced to end users in the Wallet and stored in the audit trail.
  • context — application metadata (dict[str, str]).
  • user_token — per-call JWT (overrides user_token_getter).
  • account — disambiguator when the JWT matches multiple grants.
response = await app.request(
    HttpMethod.GET, "https://www.googleapis.com/calendar/v3/calendars/primary/events",
    provider="google", reason="Sync events on page load",
)

proxy_request(method, url, *, grant_id=..., provider=..., json=..., headers=..., query_params=..., reason=..., context=..., user_token=..., account=...) -> ApprovalResult | PendingApproval

Proxy-mode call. The backend holds the token, makes the outgoing call, and returns the result — application code and the SDK never observe the token. Use proxy mode whenever you want the backend to be the credential boundary, regardless of whether HITL is configured. For grants with HITL configured, this is the only path that works: the backend gates the call on the approval decision and only then executes. Two ways to identify the grant:
  • grant_id=<uuid> — explicit.
  • provider=<slug> — resolved from the user’s JWT via the configured user_token_getter.
Other kwargs:
  • json — request JSON body.
  • headers — additional request headers (Authorization is auto-injected on the backend).
  • query_params — query-string parameters.
  • reason — human-readable explanation surfaced to end users in the Wallet, stored in the audit trail, and shown to the approver when HITL is configured.
  • context — application metadata (dict[str, str]).
  • user_token — per-call JWT (overrides user_token_getter).
  • account — disambiguator when the JWT matches multiple grants.
Returns:
  • ApprovalResult — the call ran synchronously (no approval required, or approval was pre-granted). Contains the third-party response.
  • PendingApproval — an approver must act before the call executes. Poll with get_approval_status(approval_id) or block with await_approval(approval_id, timeout=...). See the HITL guide.
Branch on the return type to handle both cases:
result = await app.proxy_request(
    HttpMethod.POST, "https://api.example.com/transfer",
    grant_id=grant_id, json={"amount": 1000}, reason="Quarterly payout",
)
if isinstance(result, PendingApproval):
    # surface to the user, wait for approval, then poll
    ...
else:
    # ran synchronously; result.response is the provider response
    ...

boto3_client(grant_id, service, region=None) -> boto3.Client

Returns a boto3 client that signs requests with credentials from an AWS managed-secret grant.

Grants

list_grants(*, provider_id=None, limit=100, offset=0) -> UnifiedGrantListResult

Paginated list of grants visible to the calling principal. Returns a discriminated union — each item is either an OAuthGrantItem (grant_kind="oauth") or a ManagedSecretGrantItem (grant_kind="managed_secret"). App.list_grants returns OAuth grants only; Agent.list_grants returns both.

revoke_grant(grant_id) -> RevokeGrantResult

Revoke an OAuth grant. App-only.

create_managed_secret_grant(managed_secret_id, *, principal: Principal) -> CreateGrantResult

Issue a managed-secret grant bound to a principal. principal is one of UserPrincipal, GroupPrincipal, SystemPrincipal, AgentPrincipal.

revoke_delegation(grant_id, agent_id=None)

App: revoke_delegation(grant_id, agent_id) revokes a named agent’s delegation on an OAuth grant. Agent: revoke_delegation(grant_id) revokes the calling agent’s own delegation (no agent_id argument).

Connect flow

create_connect_session(*, allowed_providers, allowed_origin=None, user_token=None, agent=None, return_url=None) -> ConnectSession

Mint a short-lived session token for the Connect widget.

create_managed_secret_connect_session(*, template_slug, delegated_agent_id, user_token, requested_ttl_seconds=None, delegated_agent_name=None, allowed_origin=None, return_url=None) -> ManagedSecretConnectSession

Mint a session for the user → agent delegation flow on a managed secret. The end user signs in (verified against the app’s IDP using user_token), reviews the agent identified by delegated_agent_id, and consents to delegate access on the template_slug managed secret. The backend resolves the delegation’s effective expiry as the earliest of four candidate expiry datetimes — all normalized to absolute timestamps before comparison:
CandidateSourceUnit on the wire
Caller-requestednow + requested_ttl_secondsseconds (omit to skip this cap)
Source-grant ceilingsource_grant.expires_atabsolute datetime
Template policy ceilingnow + template.max_delegation_ttl_days × 86400days (operator-set on the template)
Defaultnow + 90 daysapplied when nothing else binds
A hard ceiling of 5 years applies on top. ManagedSecretConnectSession.expires_at on the response carries the resolved datetime.

connect(providers=[...], email=None, scopes=None) -> list[ConnectResult]

Headless OAuth — opens a local browser, walks the OAuth flow, returns the resulting grant metadata. Use for CLIs, notebooks, one-time bootstrap.

Authentication helpers (App only)

authenticate() -> AuthResult

Open a local browser to the IDP’s sign-in flow, return the user’s JWT.

verify_user_token(token) -> str | None

Verify a JWT against the app’s IDP; return the user UUID if valid.

Approvals

  • get_approval_status(approval_id) -> ApprovalStatus — single poll.
  • await_approval(approval_id, timeout=...) -> ApprovalResult — block until terminal.

Managed agents (App only)

The app.agents namespace.
  • app.agents.create(name=..., **opts) -> AgentCreateResult — plaintext API key returned once.
  • app.agents.list(limit=..., offset=...) -> AgentListResult
  • app.agents.get(agent_id) -> AgentInfo
  • app.agents.get_by_name(name) -> AgentInfo | None
  • app.agents.update(agent_id, **opts) -> AgentInfo
  • app.agents.mint_key(agent_id) -> AgentKeyMintResult — plaintext returned once.
  • app.agents.list_keys(agent_id) -> AgentKeyList
  • app.agents.deprecate_key(agent_id, key_id), undeprecate_key(...), revoke_key(agent_id, key_id, force=False)

Scoped key derivation

The app.keys and agent.keys namespaces.
  • keys.derive(scopes=[...], expires_in=..., cidr_allowlist=..., rate_limit_rpm=...) -> MintedKey
  • keys.rotate(key_id) -> MintedKey
  • keys.revoke(key_id) -> APIKeyInfo

Scope catalog

  • app.scopes.get_catalog() -> ScopeCatalog — the server-published scope set at the current version.

Attenuation

  • app.with_constraints(scopes=[...]) -> App — returns a sibling client whose every request signs with X-Alter-Scope-Constraints. The intersection of the parent key’s scopes and the supplied list applies. See Scopes.

Agent introspection (Agent only)

  • agent.me() -> AgentInfo — the calling agent’s own record. Works even when paused, useful for self-diagnostics.
  • agent.trace(run_id=..., thread_id=..., parent=..., **metadata) — async context manager scoping audit identity for nested request() calls. See Give an agent scoped access.

Models

Public types exported from alter_sdk:
TypePurpose
TokenResponseOAuth token metadata (access token is never readable).
GrantInfoSingle grant record (legacy list_grants shape).
OAuthGrantItem, ManagedSecretGrantItemDiscriminated union members in UnifiedGrantListResult.
UnifiedGrantListResultPaginated list_grants response.
ConnectSession, ManagedSecretConnectSessionConnect session response shape.
ConnectResult, AuthResultHeadless flow results.
Principal (discriminated union: UserPrincipal, GroupPrincipal, SystemPrincipal, AgentPrincipal)Input shape for create_managed_secret_grant.
GrantPolicy, GrantPolicyInputTTL and policy fields on a grant.
PendingApproval, ApprovalStatus, ApprovalResultHITL flow types.
AgentInfo, AgentCreateResult, AgentListResultManaged agent records.
AgentKey, AgentKeyMintResult, AgentKeyListPer-agent key records.
APIKeyInfo, MintedKeyDerived API key records.
ScopeCatalog, ResourceScopesScope catalog discovery.
RetryInfo, RetryErrorInfoRetry metadata on last_retry_info.
All models are frozen Pydantic; field access only.

Errors

See Errors for the full hierarchy. The exceptions importable from alter_sdk (selection): AlterSDKError, AlterValueError, BackendError, ReAuthRequiredError, GrantExpiredError, GrantRevokedError, CredentialRevokedError, GrantDeletedError, GrantNotFoundError, AmbiguousGrantError, PolicyViolationError, InsufficientScopeError, TokenRefreshInProgressError, NoDelegatedGrantError, ConnectFlowError, ConnectDeniedError, ConnectConfigError, ConnectTimeoutError, ProviderAPIError, ScopeReauthRequiredError, NetworkError, TimeoutError, ApprovalError, ApprovalDeniedError, ApprovalExpiredError, ApprovalTimeoutError, ApprovalExecutionFailedError, AgentError (and the agent subclasses).

Enums

EnumMembers
HttpMethodGET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
CallerTypeSERVICE, AGENT

Framework recipes

FastAPI

import os
from fastapi import FastAPI, Depends
from contextvars import ContextVar
from alter_sdk import App

current_jwt: ContextVar[str] = ContextVar("current_jwt")
alter = App(api_key=os.environ["ALTER_API_KEY"], user_token_getter=lambda: current_jwt.get())

app = FastAPI()

async def with_user(request):
    current_jwt.set(extract_jwt(request))

@app.get("/api/events", dependencies=[Depends(with_user)])
async def list_events():
    r = await alter.request("GET", "https://www.googleapis.com/calendar/v3/calendars/primary/events", provider="google")
    return r.json()

LangChain

import os
from alter_sdk import Agent
from alter_sdk.langchain import alter_tool

agent = Agent(api_key=os.environ["AGENT_KEY"])

@alter_tool(agent=agent)
async def send_slack(channel: str, text: str) -> dict:
    """Post a Slack message."""
    r = await agent.request("POST", "https://slack.com/api/chat.postMessage",
                            provider="slack", json={"channel": channel, "text": text})
    return r.json()

# `send_slack` is now a LangChain tool. Identity flows through agent.trace() automatically.

MCP

See Integrate with Claude Code (MCP).