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+.
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:
| Arg | Type | Default | Description |
|---|
api_key | str | — | App API key. Required. |
timeout | float | 30.0 | HTTP timeout in seconds. |
caller | str | None | None | Free-form audit attribution, or an agent UUID for impersonation. |
caller_type | CallerType | SERVICE | SERVICE or AGENT. Drives audit grouping in the portal. |
user_token_getter | callable | None | Returns 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 SDK | Yes — to the SDK process | No — stays on the backend |
| Third-party call made by | The SDK | The Alter backend |
| Return type | httpx.Response | ApprovalResult | PendingApproval |
| Scope required on the API key | tokens:retrieve | proxy:execute |
| HITL grants | Rejected (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.
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-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:
| Candidate | Source | Unit on the wire |
|---|
| Caller-requested | now + requested_ttl_seconds | seconds (omit to skip this cap) |
| Source-grant ceiling | source_grant.expires_at | absolute datetime |
| Template policy ceiling | now + template.max_delegation_ttl_days × 86400 | days (operator-set on the template) |
| Default | now + 90 days | applied 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:
| Type | Purpose |
|---|
TokenResponse | OAuth token metadata (access token is never readable). |
GrantInfo | Single grant record (legacy list_grants shape). |
OAuthGrantItem, ManagedSecretGrantItem | Discriminated union members in UnifiedGrantListResult. |
UnifiedGrantListResult | Paginated list_grants response. |
ConnectSession, ManagedSecretConnectSession | Connect session response shape. |
ConnectResult, AuthResult | Headless flow results. |
Principal (discriminated union: UserPrincipal, GroupPrincipal, SystemPrincipal, AgentPrincipal) | Input shape for create_managed_secret_grant. |
GrantPolicy, GrantPolicyInput | TTL and policy fields on a grant. |
PendingApproval, ApprovalStatus, ApprovalResult | HITL flow types. |
AgentInfo, AgentCreateResult, AgentListResult | Managed agent records. |
AgentKey, AgentKeyMintResult, AgentKeyList | Per-agent key records. |
APIKeyInfo, MintedKey | Derived API key records. |
ScopeCatalog, ResourceScopes | Scope catalog discovery. |
RetryInfo, RetryErrorInfo | Retry 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
| Enum | Members |
|---|
HttpMethod | GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS |
CallerType | SERVICE, 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).