Skip to content

Reference

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, AgentDelegationMissingError,
GrantNotDelegableError, CredentialRevokedError,
AmbiguousGrantError, GrantCandidate, NoDelegatedGrantError,
PolicyViolationError, StepUpRequiredError, RedactDischargeFailedError,
InsufficientScopeError,
TokenRefreshInProgressError, QuotaExceededError, RateLimitError,
ConnectFlowError, ConnectDeniedError, ConnectConfigError, ConnectTimeoutError,
ProviderAPIError, ScopeReauthRequiredError, ProviderUnauthorizedError,
NetworkError, TimeoutError,
ApprovalError, ApprovalDeniedError, ApprovalExpiredError,
ApprovalTimeoutError, ApprovalExecutionFailedError,
AgentError, AgentNotFoundError, AgentNameExistsError,
CallerAgentMismatchError, CallerAgentResolutionError,
AgentConcurrentUpdateError,
AgentInactiveError, AgentRevokedError,
InvalidKeyError, KeyRevokedError, KeyExpiredError,
KeyRotationExpiredError, KeyInactiveError, KeyAppNotFoundError,
KeyAlreadyRevokedError,
KeyNotFoundError, LastActiveKeyError, AgentKeyLimitError,
MeRequiresAgentKeyError, AgentCannotMintSubagentsError,
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="provider-id")
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
GrantExpiredErrorprovider_id, agent_id, app_user_id — populated only on the delegation-path expiry (a TTL-lapsed delegation chain); None on the root-path TTL expiry
GrantNotFoundErrorprovider_id, agent_id, app_user_id (no grant_id — the grant lookup is what failed)
AgentDelegationMissingErrorsubclass of GrantNotFoundError (same provider_id / agent_id / app_user_id fields) — raised on the agent path when a grant_id isn’t reachable by this agent; delegate via Connect or resolve by provider
GrantNotDelegableError(subclass of BackendError) — raised by agent.delegate() when the held grant is not delegable, or a managed secret’s operator ceiling blocks re-delegable grants (HTTP 403 grant_not_delegable)
ScopeReauthRequiredErrorgrant_id, provider_id
AmbiguousGrantErrorprovider_id, account_identifiers, account_was_provided, app_user_ids, grant_ids, candidates
NoDelegatedGrantErrorprovider_id, agent_id, app_user_id
PolicyViolationErrorpolicy_error
StepUpRequiredErrorsubclass of PolicyViolationError; policy_error="step_up_required" and max_session_age_seconds carries the session-freshness ceiling when supplied
RedactDischargeFailedErrorsubclass of PolicyViolationError; policy_error="redact_discharge_failed" and the request was denied because required redaction could not be applied
SiblingLabelConflictErrorlabel — the sibling label already active on the credential
QuotaExceededErrorretry_after — seconds until the quota window resets (prefers the Retry-After response header over the body value, so it can differ from details["retry_after"], and is capped at 2,678,400 seconds / 31 days — a quota window is a calendar period up to a month long, and the response reports the longest exhausted one); may be None. Raised on a request-quota policy denial; the SDK never auto-retries it
RateLimitErrorretry_after — seconds to wait before retrying (prefers the Retry-After response header over the body value, so it can differ from details["retry_after"], and is capped at 3,600 seconds — one hour, above the platform’s legitimate retry guidance); scope ("ip", "api_key" or "organization") — which platform ceiling was hit (a plain str, not a closed enum, so a ceiling added later stays parseable; the known values are exported as RATE_LIMIT_SCOPE_VALUES, ordered outermost-first); limit; window_seconds. scope, limit and window_seconds may each be None — not every response carrying this error code supplies them, so branch on a missing value rather than assuming one. A transient throttle, not a denial: the same request succeeds once the reported wait elapses
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
ConnectTimeoutErrordetails["reason"] is "poll_deadline_elapsed" when the local polling deadline wins, or "session_expired" when a session observed pending expires before Alter receives a callback. With no callback, an exact provider-side error is unknowable; the provider may have retained an error page, the browser may have closed, or the user may have abandoned the flow
ApprovalError (and subclasses)approval_id
AgentError (and subclasses)code, hint

GrantCandidate is a supporting dataclass, not an exception. Each item in AmbiguousGrantError.candidates has grant_id, label, account_identifier, and account_display_name; retry with the chosen grant_id, or with provider + label when a stable sibling address is preferable.

Identity-mode ambiguity — retry with account=:

from alter_sdk import AmbiguousGrantError
try:
resp = await app.request("GET", url, provider="provider-id")
except AmbiguousGrantError as e:
resp = await app.request(
"GET",
url,
provider="provider-id",
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="provider-id")
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))

Quota window exhausted — wait for the window to reset:

import asyncio
from alter_sdk import QuotaExceededError
try:
resp = await app.request(...)
except QuotaExceededError as e:
# A request-quota policy denial — the SDK never auto-retries it.
await asyncio.sleep(e.retry_after if e.retry_after is not None else 60)
resp = await app.request(...)

Insufficient scope at the version mismatch — rotate the key:

from alter_sdk import InsufficientScopeError
try:
await app.agents.create(
name="bot",
scopes={"provider-id": ["resource:read"]},
)
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.

Report an issue with this page

Necessary

Required for sign-in, security, authorization, and remembering your choices.

Always active

Analytics

Helps us understand which product and documentation features are useful.

Performance diagnostics

Uses performance tracing and privacy-masked session replay to diagnose problems.

You can change these choices at any time from Cookie settings.