Skip to content

Reference

Errors

Exception hierarchy exported from @alter-ai/alter-sdk.

Every public exception thrown by the SDK extends AlterSDKError. The hierarchy mirrors the Python SDK one-for-one — same class names, same semantics, same fields.

For the canonical error code → exception class mapping and recovery guidance, see Errors.

import {
NoDelegatedGrantError,
QuotaExceededError,
RateLimitError,
ScopeReauthRequiredError,
} from "@alter-ai/alter-sdk";
const makeRequest = () =>
app.request("GET", url, { provider: "<provider-id>" });
try {
await makeRequest();
} catch (error) {
if (error instanceof NoDelegatedGrantError) {
const session = await app.createConnectSessionForError(error);
redirectUser(session.connectUrl);
return;
}
if (error instanceof ScopeReauthRequiredError) {
// The user must re-authorize with wider provider scopes.
console.warn(`Missing scopes: ${error.missingScopes?.join(", ")}`);
}
if (error instanceof RateLimitError) {
// A transient throttle. `scope` says WHICH ceiling was hit, which decides
// the remedy: spreading load across keys helps an `api_key` throttle and
// does nothing for an `organization` one.
console.warn(`Throttled on ${error.scope ?? "an unnamed ceiling"}`);
await new Promise((r) => setTimeout(r, (error.retryAfter ?? 1) * 1000));
return makeRequest();
}
if (error instanceof QuotaExceededError) {
// Request-quota policy denial — the SDK never auto-retries it.
await new Promise((r) => setTimeout(r, (error.retryAfter ?? 60) * 1000));
return makeRequest();
}
throw error;
}

Every name imported above is used by the snippet, so it compiles as-is under noUnusedLocals. The classes documented below that it does not branch on (AlterSDKError, GrantNotFoundError, GrantNotDelegableError, …) are deliberately not imported here — add them when you add a branch for them.

  • AlterSDKError — root of the hierarchy. Every other exception extends this.
  • AlterValueError — input validation failed at the SDK boundary. Distinct from BackendError so callers can branch on “I passed something bad” vs “the backend said no.”
  • BackendError — generic backend failure. details.status_code is populated when an HTTP status code is the proximate cause.
  • RestrictedGrantRequiresProxyError — the grant is restricted to the proxy call path; use proxyRequest().
  • SiblingLabelConflictError — the sibling label is already in use on this connection; pick another label.
  • ReAuthRequiredError — the user must re-authorize via Alter Connect. Parent class of the grant-state errors below (GrantExpiredError, GrantRevokedError, GrantDeletedError, CredentialRevokedError), so one instanceof catch covers every “send the user back through Connect” condition.
  • GrantExpiredError — the grant’s TTL has elapsed. Exposes providerId, agentId, appUserId, populated only on the delegation-path expiry (a TTL-lapsed delegation chain); undefined on the root-path TTL expiry.
  • GrantRevokedError
  • GrantDeletedError
  • GrantNotFoundError
  • AgentDelegationMissingError — extends GrantNotFoundError (so an instanceof GrantNotFoundError catch still fires). Thrown on the agent path when request(method, url, { grantId }) / proxyRequest({ grantId }) hits a grant_not_found — the grant is not delegated to this agent, or a user/base grant id was passed where the agent’s own delegation id (listGrants) was required. Recover by delegating the agent through Connect, or resolve by provider (omit grantId). The App/operator path is unaffected.
  • GrantNotDelegableError — extends BackendError. Thrown 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).
  • CredentialRevokedError
  • AmbiguousGrantError — multiple grants match the identity + provider tuple. Pass account or use direct grantId mode.
  • NoDelegatedGrantError — the calling agent has no delegation for the resolved user on this provider. Recoverable via createConnectSessionForError().
  • PolicyViolationError — backend policy denial.
  • StepUpRequiredError — extends PolicyViolationError. A content_match step-up obligation requires a fresher user session.
  • RedactDischargeFailedError — extends PolicyViolationError. The request was denied because a redact obligation could not be applied safely.
  • InsufficientScopeError — API key lacks the required scope.
  • TokenRefreshInProgressError — concurrent refresh in flight; retry shortly.
  • QuotaExceededError — a request-quota policy window is exhausted. Exposes retryAfter (seconds until the window resets, 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 undefined). A policy denial, not a transient outage — the SDK never auto-retries it.
  • RateLimitError — the platform rate limit for the API key (or the organization-wide aggregate, when configured) is exhausted. A transient throttle, not a denial: wait retryAfter seconds and retry the same request (retryAfter is capped at 3,600 seconds — one hour, above the platform’s legitimate retry guidance). Also exposes scope (which ceiling was hit — a plain string, not a closed union, so a ceiling added later stays parseable; the known values are exported as RATE_LIMIT_SCOPE_VALUES, currently "ip", "api_key" and "organization", ordered outermost-first), limit, and windowSeconds. Each of those three may be undefined — not every response carrying this error code supplies them, so branch on a missing value rather than assuming one.
  • ProviderAPIError — provider returned a 4xx/5xx that is not a scope or credential-rejection failure.
  • ScopeReauthRequiredError — provider returned 403 insufficient_scope, or the backend already knew the grant’s scopes are drifted. Exposes missingScopes and providerId.
  • ProviderUnauthorizedError — provider returned 401: the credential was revoked, expired, or otherwise invalidated provider-side. Exposes grantId, providerId, statusCode, responseBody. Recovery: 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.
  • ConnectFlowError — generic flow failure, including an unavailable/expired token on the first poll, an unknown terminal state, or a total usage-limit application failure.
  • ConnectDeniedError — user clicked Deny.
  • ConnectConfigError — provider configuration issue (invalid redirect URI, unknown client, etc.).
  • ConnectTimeoutError — Alter received no completion callback before the local deadline or before a session observed pending expired. details.reason is "poll_deadline_elapsed" or "session_expired"; without a callback, an exact provider-side error is unknowable.
  • ApprovalError — base class.
  • ApprovalDeniedError
  • ApprovalExpiredError
  • ApprovalTimeoutError — local wait elapsed before any decision. Original transient (when present) is preserved on .cause.
  • ApprovalExecutionFailedError
  • AgentError — base class.
  • InvalidKeyError
  • KeyExpiredError
  • KeyRotationExpiredError
  • KeyInactiveError
  • KeyAppNotFoundError
  • AgentNotFoundError
  • CallerAgentMismatchError
  • CallerAgentResolutionError
  • AgentNameExistsError
  • AgentConcurrentUpdateError — the agent changed after it was read. Fetch it again, recompute the full desired scope allowlist, and retry with the fresh version.
  • AgentInactiveError
  • AgentRevokedError
  • MeRequiresAgentKeyErroragent.me() was called through an impersonating Agent returned by App.getAgent(), so the request still used an application key rather than a genuine agent key.
  • KeyRevokedError
  • KeyAlreadyRevokedError
  • KeyNotFoundError
  • LastActiveKeyError — would revoke the agent’s only remaining active key. Pass force: true to override.
  • AgentKeyLimitError — the mint would take the agent past its per-agent active-key cap. Revoke a key it no longer uses, then retry.
  • AgentCannotMintSubagentsError
  • IdempotencyKeyBodyMismatchError
  • IdempotencyKeyAgentRevokedError
  • IdempotencyKeyAgentInactiveError
  • NetworkError — TCP / DNS / socket failure.
  • TimeoutError — extends NetworkError. Local or remote timeout.

Every AlterSDKError carries details: Record<string, unknown>. toString() returns only the safe message and never stringifies details. Common detail keys:

KeyTypeDescription
status_codenumberHTTP status code from the backend or provider.
grant_idstringGrant the call resolved to (when applicable).
provider_idstringProvider slug (when applicable).
method, urlstringHTTP method and pre-injection URL.
reasonstringBackend-reported reason code.
retry_afternumberSeconds to wait before retrying — a quota window reset or a rate-limit backoff. QuotaExceededError and RateLimitError also expose it as the typed retryAfter field (may be undefined). On both, the typed field prefers the Retry-After response header over this body value and caps the result — at 3,600 seconds for RateLimitError, at 2,678,400 (31 days) for QuotaExceededError, whose windows are calendar periods — so the two can differ.

The following public fields supplement message, name, and details:

ClassFields
GrantExpiredErrorproviderId, agentId, appUserId (string | undefined)
GrantRevokedErrorgrantId: string | undefined
CredentialRevokedErrorgrantId, providerId, appUserId (string | undefined)
GrantNotFoundErrorproviderId, agentId, appUserId (string | undefined)
AmbiguousGrantErrorproviderId, accountIdentifiers, accountWasProvided, appUserIds, grantIds, candidates
NoDelegatedGrantErrorproviderId, agentId, appUserId (string | undefined)
PolicyViolationErrorpolicyError: string | undefined
StepUpRequiredErrormaxSessionAgeSeconds: number | undefined; inherited policyError is "step_up_required"
RedactDischargeFailedErrorno extra fields; inherited policyError is "redact_discharge_failed"
SiblingLabelConflictErrorlabel: string | null
InsufficientScopeErrorrequired, granted, missing, scopeVersion, currentScopeVersion, scopeVersionMismatch, documentationUrl; aliases requiredScopes, grantedScopes, docsUrl
TokenRefreshInProgressErrorgrantId: string | undefined
QuotaExceededErrorretryAfter: number | undefined
ProviderAPIErrorstatusCode: number | undefined, responseBody: string | undefined
ScopeReauthRequiredErrorgrantId, providerId, missingScopes; inherits provider response fields
ProviderUnauthorizedErrorgrantId, providerId; inherits provider response fields
ApprovalError and subclassesapprovalId: string | null
AgentError and subclassescode: string, hint?: string

AmbiguousGrantError.candidates contains GrantCandidate entries with grantId, label, accountIdentifier, and accountDisplayName. The account fields are nullable.

For the typed exception fields and recovery flows for each code, see Errors.

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.