Skip to content

Python SDK

Errors

Typed exception hierarchy for the Python SDK.

Every SDK exception inherits from AlterSDKError. Branching on the typed subclasses is the supported way to handle failure modes — never parse error message strings.

from alter_sdk import (
AlterSDKError, AlterValueError,
BackendError, ReAuthRequiredError,
GrantExpiredError, GrantRevokedError, GrantDeletedError,
GrantNotFoundError, CredentialRevokedError,
AmbiguousGrantError, NoDelegatedGrantError,
PolicyViolationError, InsufficientScopeError,
TokenRefreshInProgressError,
ConnectFlowError, ConnectDeniedError, ConnectConfigError, ConnectTimeoutError,
ProviderAPIError, ScopeReauthRequiredError, ProviderUnauthorizedError,
NetworkError, TimeoutError,
ApprovalError, ApprovalDeniedError, ApprovalExpiredError,
ApprovalTimeoutError, ApprovalExecutionFailedError,
AgentError, AgentNotFoundError, AgentNameExistsError,
AgentInactiveError, AgentRevokedError,
InvalidKeyError, KeyRevokedError, KeyAlreadyRevokedError,
KeyNotFoundError, LastActiveKeyError,
MeRequiresAgentKeyError, AgentCannotMintSubagentsError,
AgentScopeNarrowingNotSupportedError,
RestrictedGrantRequiresProxyError, SiblingLabelConflictError,
IdempotencyKeyBodyMismatchError,
IdempotencyKeyAgentRevokedError,
IdempotencyKeyAgentInactiveError,
)

The full hierarchy and a per-error recovery playbook are documented at /reference/errors. This page is a short index of what’s exported from alter_sdk so you know what to import.

from alter_sdk import ReAuthRequiredError, BackendError
try:
resp = await app.request("GET", url, provider="google")
except ReAuthRequiredError:
# GrantExpiredError, GrantRevokedError, CredentialRevokedError, GrantDeletedError
# — user must re-authorize via Connect
...
except BackendError:
# Any backend-origin failure not in the re-auth bucket
...

Every typed error carries .message and .details: dict[str, Any]. Specific subclasses add fields:

ExceptionExtra attributes
GrantRevokedError, TokenRefreshInProgressErrorgrant_id
CredentialRevokedErrorgrant_id, provider_id, app_user_id
GrantNotFoundErrorprovider_id, agent_id, app_user_id (no grant_id — the grant lookup is what failed)
ScopeReauthRequiredErrorgrant_id, provider_id
AmbiguousGrantErrorprovider_id, account_identifiers, account_was_provided, app_user_ids, grant_ids
NoDelegatedGrantErrorprovider_id, agent_id, app_user_id
PolicyViolationErrorpolicy_error
InsufficientScopeErrorrequired, granted, missing, scope_version, current_scope_version, scope_version_mismatch, documentation_url (aliases: required_scopes, granted_scopes, docs_url)
ProviderAPIError, ScopeReauthRequiredError, ProviderUnauthorizedErrorstatus_code, response_body
ScopeReauthRequiredErroralso missing_scopes (from WWW-Authenticate: Bearer error="insufficient_scope")
ProviderUnauthorizedErrorgrant_id, provider_id — provider returned 401 (credential rejected provider-side); re-authorize an OAuth grant via a new Connect session, or update the stored managed secret. A 401 whose WWW-Authenticate challenge carries error="insufficient_scope" (RFC 6750 — the credential is still valid, it just lacks a scope) surfaces as a generic ProviderAPIError instead
ApprovalError (and subclasses)approval_id
AgentError (and subclasses)code, hint

Identity-mode ambiguity — retry with account=:

from alter_sdk import AmbiguousGrantError
try:
resp = await app.request("GET", url, provider="google")
except AmbiguousGrantError as e:
resp = await app.request("GET", url, provider="google", account=e.account_identifiers[0])

Missing or broken credential — mint a recovery Connect session:

from alter_sdk import NoDelegatedGrantError, CredentialRevokedError
try:
resp = await agent.request("GET", url, provider="slack")
except (NoDelegatedGrantError, CredentialRevokedError) as e:
session = await agent.create_connect_session_for_error(e)
redirect_to(session.connect_url)
results = await agent.poll_connect_session(session.session_token)

Transient refresh conflict — exponential backoff:

import asyncio
from alter_sdk import TokenRefreshInProgressError
for attempt in range(3):
try:
resp = await app.request(...)
break
except TokenRefreshInProgressError:
await asyncio.sleep(1 * (2 ** attempt))

Insufficient scope at the version mismatch — rotate the key:

from alter_sdk import InsufficientScopeError
try:
await app.agents.create(name="bot")
except InsufficientScopeError as e:
if e.scope_version_mismatch:
print(f"Key minted at v{e.scope_version}; server at v{e.current_scope_version}. Rotate.")
else:
print(f"Missing: {e.missing}. See {e.docs_url}")

See /reference/errors for the full hierarchy with descriptions and recovery guidance for every error.