Skip to main content

Alter SDK for Python

Official Python SDK for Alter Vault — privileged access management for AI agents: vaulted credentials, default-deny policy down to the parameter, approval policies for risky calls, and an audit trail for every call made through Alter.

Provider credentials are never returned to application code. The SDK injects the credential, refreshes it, and writes the audit row — application code only calls vault.request() (or vault.proxy_request() when the backend should make the outgoing call instead of the SDK).

Install

pip install alter-sdk

Requires Python 3.11+.

Quick example

Make an authenticated API call — no token ever touches application code.

import asyncio
from alter_sdk import App, HttpMethod

async def main():
    async with App(api_key="<api-key>") as vault:
        response = await vault.request(
            HttpMethod.POST,
            "https://api.example.com/resource",
            grant_id="<grant-id>",
            json={"example": "payload"},
        )
        print(response.status_code, response.json())

asyncio.run(main())

For a full walkthrough — sign-up, key minting, OAuth — see the Quickstart.

Two runtime modes

The SDK exposes two ways to reach a third-party API:

  • vault.request(...) — retrieve mode. The SDK fetches the token from the backend and makes the outgoing call itself. Returns the third-party response.
  • vault.proxy_request(...) — proxy mode. The backend holds the token, makes the outgoing call, and returns the result. Application code and the SDK never observe the token. Required for any grant configured with human-in-the-loop approval; available for any other grant when wire-level audit, strong token isolation, or backend-side policy enforcement matter.

Proxy mode returns an ApprovalResult with status_code, response headers/body, body_truncated, and duration_ms (provider round-trip milliseconds; None for results created by older workers). Managed-secret secondary header/query injections and AWS SigV4—including query parameters, raw/JSON bodies, and temporary session tokens—behave the same in retrieve and proxy modes.

See runtime modes for the tradeoffs and when to pick each.

Policy rule helpers

content_match_rule(...) builds operation-aware request rules for with_constraints(rule=...) with local validation before the first API call. It accepts attested operation ids and/or operation families, optional parameter conditions, and one of three effects: deny, redact, or step_up. Redaction strips named outbound request-body fields; step-up requires max_session_age_seconds as an integer from 1 through 86400.

Recovering from missing-grant errors

When a request fails because the user hasn't authorized the provider yet, the SDK exposes recovery context on the typed error so you can drive a re-consent flow without re-deriving anything from the call site:

from alter_sdk import NoDelegatedGrantError

try:
    await vault.request(provider="<provider-id>", user_token=jwt, url=..., method=...)
except NoDelegatedGrantError as e:
    session = await vault.create_connect_session_for_error(
        e,
        allowed_origin="https://app.example.com",
    )
    # Surface session.connect_url to the user — popup, redirect,
    # out-of-band message, whatever your framework does.
    results = await vault.poll_connect_session(session.session_token)
    # Missing grants return a new id. A CredentialRevokedError repair keeps the
    # existing one (operation="reauth"), so always read grant_id off the result.
    response = await vault.request(grant_id=results[0].grant_id, url=..., method=...)

create_connect_session_for_error and poll_connect_session are available on both App and Agent so the catch block can recover from whichever client raised. For CredentialRevokedError, the helper binds the session to the error's exact grant. A successful repair reports operation == "reauth" and the same grant_id — as does any completion that returns an already-connected grant: "reauth" means an existing grant came back with its id preserved, not that it was necessarily broken. Naming a target is an optimization, not a requirement: an error carrying no grant id (an older backend), a grant this caller cannot address (pass user_token to reach an end user's connection), or a grant that is no longer active all fall back to an ordinary session that reports operation == "creation" with a new grant_id. Read the id off the result.

If Alter observes the session pending but receives no callback before the local deadline or server expiry, polling raises ConnectTimeoutError. Inspect error.details["reason"] (poll_deadline_elapsed or session_expired). A provider may keep an authorization error on its own page and never redirect, so the exception lists that alongside browser closure or an abandoned flow rather than claiming an exact provider error Alter did not receive.

If a fresh provider authorization cannot start, poll_connect_session raises ConnectConfigError. This includes the typed provider_configuration_unavailable and shared_dev_credential_unavailable terminals; show the exception's safe message to the caller and ask the app administrator to correct provider availability before creating a new session. Existing grants may remain usable.

ConnectConfigError also carries a third terminal, managed_oauth_scope_approval_required, raised by create_connect_session (HTTP 409) as well as by poll_connect_session: the scopes the application requests exceed what Alter's managed OAuth client is approved for. The app administrator cannot widen it — contact Alter to have the scopes approved, or narrow the application's configured scopes to the approved set, and only then create a new session. The response is retryable: false, so every fresh session fails identically until the approval changes. On the create_connect_session path the exception's details is the full response body, so details["details"]["unapproved_scopes"] names exactly which scopes need approving.

NoDelegatedGrantError and GrantNotFoundError carry provider_id / agent_id / app_user_id recovery context when the original lookup was identity-mode; CredentialRevokedError carries provider_id / app_user_id. See the error reference for the full surface.

On the agent path, a grant_not_found for an explicit grant_id surfaces as AgentDelegationMissingError — a subclass of GrantNotFoundError, so an except GrantNotFoundError still fires. It means the grant is not delegated to this agent, or a user/base grant id was passed where the agent's own delegation id is required (get it from agent.list_grants). Recover by delegating the agent through Connect (agent.create_connect_session), or resolve by provider instead of passing a grant_id.

Agent-initiated managed-secret delegation

An agent can ask a user to delegate the user's managed-secret access to it. The method takes no target — the session always targets the calling agent:

from alter_sdk import Agent

agent = Agent(api_key="<agent-api-key>")

session = await agent.create_managed_secret_connect_session(
    template_slug="<template-slug>",
    user_token="<user-jwt>",
    delegable=True,  # optional: ask that this agent may share the access onward
)
# Open session.connect_url in the user's browser (a popup or the system
# browser). The user signs in, picks a grant and approves; the page then
# delivers the delegation id this agent passes as grant_id to
# agent.proxy_request() (a delegation is proxy-only).

The user decides whether the delegation may be shared onward: for an agent's own request the page's checkbox starts off for a new delegation, so the user opts the agent in; for an existing delegation it starts from the stored setting. session.delegable_requested echoes the request. An application key names the agent instead: app.create_managed_secret_connect_session(template_slug=..., delegated_agent_id=..., user_token=...). When the user holds no grant the agent can be delegated from, that call raises ConnectConfigError and error.details["details"]["reason"] says why (no_grant, grant_expired, delegation_depth_exceeded, group_source_not_allowed, agent_not_allowlisted); an agent's own key always gets a session, and the approval page names the cause instead. Against an Alter backend that predates this method (or delegable), the SDK raises ServerFeatureUnsupportedError.

Onward delegation (agent to agent)

An agent that holds a grant can hand a scoped-down copy to another agent — without asking the credential owner to consent again. Use agent.delegate() to mint a child grant for the second agent:

from alter_sdk import Agent, GrantNotDelegableError

agent = Agent(api_key="<agent-api-key>")

try:
    result = await agent.delegate(
        "<grant-id>",              # a grant this agent already holds
        "<other-agent-id>",        # the agent that should receive access
        scope_constraint=["chat:write"],   # optional: narrow to fewer scopes
        ttl_seconds=3600,                  # optional: shorten the lifetime
    )
    print(result.grant_id, result.depth, result.expires_at)
except GrantNotDelegableError as e:
    # e.detail_reason says why: "holder_not_delegable" (the user did not allow
    # onward sharing), "ceiling_off" (the operator ceiling is off), ...
    print("cannot share onward:", e.detail_reason)

The held grant must have been created as delegable: the user allowed onward sharing when they approved it (Connect with delegable=True asks them; only their choice is stored), and the credential's operator ceiling — a managed secret's delegation policy or the OAuth provider's agent sharing setting — must still allow it on every call. The child can only narrow — fewer scopes, a shorter lifetime — never widen. A grant narrowed with scope_constraint is proxy-only: call it with agent.proxy_request(...). Onward delegation is opt-in at every hop: pass delegable=True only when the recipient should be allowed to delegate further. Omit delegable to keep the existing delegation's setting — a new delegation is then a leaf, and delegating again to the same agent leaves its current setting unchanged; pass delegable=False to turn onward delegation off.

agent.list_grants() returns each grant with parent_grant_id (the grant it was minted under, or None for a root) and depth (its distance from the root), so the full delegation chain can be reconstructed from the flat list.

OpenTelemetry trace propagation

When the application runs an OpenTelemetry SDK, Alter requests automatically carry the active span's W3C traceparent, so the audit trail — and any spans the organization streams to its own OTLP collector — join the application's traces. No configuration is required and opentelemetry is never installed by the SDK itself — it uses whatever OpenTelemetry the application installed; without it (or without an active span) the SDK behaves exactly as before. (The optional alter-sdk[otel] extra is available to record the supported opentelemetry-api version range in the application's dependency tree.)

from opentelemetry import trace

tracer = trace.get_tracer("the-application")

# Inside an async function; `vault` from the quick start above.
with tracer.start_as_current_span("handle-user-request"):
    # This call's audit events share the surrounding trace's ids.
    response = await vault.request(HttpMethod.GET, url, grant_id=grant_id)

Documentation

Full docs are at https://docs.alterauth.com.

Topic Page
Getting started end-to-end Quickstart
The mental model How Alter works
Calling APIs on behalf of users (OAuth + JWT) Guide
Identity provider setup Administration guide
Provisioning backend secrets Guide
Scoped credentials for AI agents Guide
Human-in-the-loop approvals Guide
OpenTelemetry trace propagation Calling APIs
Runtime modes (retrieve vs proxy) Concept
Exposing credentials to Claude Code Guide
Per-method API reference Python SDK reference
Errors Error reference

License

MIT. See LICENSE.

Support

Email founders@alterauth.com or open an issue at https://github.com/alter-ai/alter-vault.

Metadata

Release files for alter-sdk 0.28.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for alter-sdk 0.28.0
File Size Uploaded Provenance
alter_sdk-0.28.0.tar.gz 313.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for alter-sdk 0.28.0
File Interpreter ABI Platform
alter_sdk-0.28.0-py3-none-any.whl Python 3 none any Details

Total release size: 643.1 kB

Release files / alter_sdk-0.28.0.tar.gz

Download URL alter_sdk-0.28.0.tar.gz
Size 313.7 kB
Tags Source
SHA-256 checksum
How to use checksums
5b7f1398e6c4e7d3456e7c47b9e8c4af9cd57068d7fbc92e0e4445ee013397bd
BLAKE2b-256 checksum
How to use checksums
44261f20fabed8dbf2b3988b756a3c89724e146c1f0751c871b34ffb94c27e08
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / alter_sdk-0.28.0-py3-none-any.whl

Download URL alter_sdk-0.28.0-py3-none-any.whl
Size 329.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e82e3768e48e5c083322bdf9257d160bd2dab41fc4540f05506e9c3205a0402c
BLAKE2b-256 checksum
How to use checksums
2f5f42985ad9a18931b7f69c5a374dcffdda0f91a54616c9b7625eea59d99040
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Security for alter-sdk 0.28.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for alter_sdk-0.28.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.28.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page