Skip to main content

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.

Core Concepts

vault.request() — The Authenticated HTTP Client

vault.request() acts as the authenticated HTTP client. Instead of calling fetch directly and managing auth headers, pass a grantId and URL — the SDK makes the request with the correct credential injected automatically. Application code never sees tokens or secrets.
const response = await vault.request(
  method,           // HttpMethod enum or string (e.g., HttpMethod.GET, "GET")
  url,              // Full URL of the provider API endpoint
  {
    grantId: "...",    // Grant ID (UUID string) from Alter Connect
    json: {...},       // Optional: JSON request body
    queryParams: {},   // Optional: URL query parameters
    pathParams: {},    // Optional: URL path template substitutions
    extraHeaders: {},  // Optional: Additional headers (Authorization is auto-injected)
    reason: "...",     // Optional: Reason for audit trail
    context: {},       // Optional: Per-request identity context for audit
  }
);

Type-Safe Enums

Use enums for autocomplete and type safety:
import { HttpMethod } from "@alter-ai/alter-sdk";

// HTTP method enums
HttpMethod.GET        // "GET"
HttpMethod.POST       // "POST"
HttpMethod.PUT        // "PUT"
HttpMethod.PATCH      // "PATCH"
HttpMethod.DELETE     // "DELETE"
HttpMethod.HEAD       // "HEAD"
HttpMethod.OPTIONS    // "OPTIONS"
Strings also work for forward compatibility:
// These are equivalent
await vault.request(HttpMethod.GET, url, { grantId });
await vault.request("GET", url, { grantId });

Complete Examples

List Tasks

import { AlterVault, HttpMethod } from "@alter-ai/alter-sdk";

async function listTasks(grantId: string) {
  const vault = new AlterVault({
    apiKey: "alter_key_...",
    caller: "my-agent",
  });

  try {
    const response = await vault.request(
      HttpMethod.GET,
      "https://api.example.com/v1/tasks",
      {
        grantId,
        queryParams: {
          limit: "10",
          status: "active",
          sort: "created_at",
        },
      }
    );

    const data = await response.json();
    for (const task of data.items ?? []) {
      console.log(`${task.id}: ${task.title ?? "Untitled"}`);
    }
  } finally {
    await vault.close();
  }
}

List Users

async function listUsers(grantId: string) {
  const vault = new AlterVault({
    apiKey: "alter_key_...",
    caller: "my-agent",
  });

  try {
    const response = await vault.request(
      HttpMethod.GET,
      "https://api.example.com/v1/users",
      {
        grantId,
        queryParams: { limit: "10", sort: "name" },
      }
    );

    const users = await response.json();
    for (const user of users.items ?? []) {
      console.log(`${user.name} - ${user.email ?? "No email"}`);
    }
  } finally {
    await vault.close();
  }
}

Using Managed Secrets

For APIs where the credentials are already available (API keys, service tokens), use Managed Secrets. The grantId comes from the Developer Portal (not from an end user), and usage is identical to OAuth via vault.request():
const vault = new AlterVault({
  apiKey: "alter_key_...",
  caller: "my-agent",
});

try {
  // Call your internal API — secret injected automatically
  const response = await vault.request(
    HttpMethod.GET,
    "https://api.internal.com/v1/loyalty/points",
    {
      grantId: "MANAGED_SECRET_GRANT_ID",  // from Developer Portal (not from a user)
      queryParams: { user_id: "alice" },
      reason: "Checking loyalty points for rewards calculation",
    }
  );
  const data = await response.json();
} finally {
  await vault.close();
}
OAuth vs Managed Secrets: For OAuth, the grantId comes from an end user completing Alter Connect (per-user). For Managed Secrets, the grantId comes from the Developer Portal when a credential is stored (per-service, shared across the backend). The SDK auto-detects the credential type and injects it as the correct header.
Policy enforcement, audit logging, and error handling all work identically for both OAuth and managed secrets. See the Managed Secrets guide for setup details.

Caller Tracking (AI Agents)

Registering a Caller

Pass a caller string at SDK initialization to identify this SDK instance in audit logs:
import { AlterVault } from "@alter-ai/alter-sdk";

const vault = new AlterVault({
  apiKey: "alter_key_...",
  caller: "email-assistant-v2",  // Optional: Unique identifier for this SDK instance
});
The backend auto-registers the caller in the audit attribution system on first request.

Per-Request Context

Add execution-level context on each request using the context parameter (a freeform object stored as JSONB in audit logs):
const response = await vault.request(
  HttpMethod.GET,
  "https://www.googleapis.com/calendar/v3/calendars/primary/events",
  {
    grantId: "GRANT_ID",  // from Alter Connect
    context: {
      tool: "read_calendar",
      agent: "cursor",
    },
  }
);
The TypeScript SDK currently exposes the core credential-access surface only. Developer applications populate context manually for each request — framework-specific integration packages (FastMCP, LangChain) are available in the Python SDK via alter-sdk[mcp] / alter-sdk[langchain] extras and are planned for the TypeScript SDK in a future release.

Multi-Agent Deployments

Each agent must create its own AlterVault instance with a unique caller identity. Do not share a single instance across agents — audit logs, policies, and caller registration are tied to each instance.
// Each agent gets its own vault instance
const emailAgent = new AlterVault({
  apiKey: "alter_key_...",
  caller: "email-assistant-v2",
});

const calendarAgent = new AlterVault({
  apiKey: "alter_key_...",
  caller: "calendar-agent-v1",
});

// Audit logs and policies are tracked per caller
await emailAgent.request(
  HttpMethod.GET,
  "https://gmail.googleapis.com/gmail/v1/users/me/messages",
  { grantId: gmailGrantId },  // from Alter Connect
);
await calendarAgent.request(
  HttpMethod.GET,
  "https://www.googleapis.com/calendar/v3/calendars/primary/events",
  { grantId: calendarGrantId },  // from Alter Connect
);

// Clean up each instance
await emailAgent.close();
await calendarAgent.close();
All agents can use the same API key — the caller identity (set at initialization) is what differentiates them in audit logs and policy enforcement.

Caller Tracking Parameters Reference

Initialization (set once per SDK instance):
ParameterTypeDescriptionExample
callerstringUnique identifier for this SDK instance (optional)"email-assistant-v2"
Per-request (set on each vault.request() call):
ParameterTypeDescriptionExample
contextRecord<string, string>Per-request identity and execution context (audit log JSONB){ tool: "read_calendar", agent: "cursor" }
The context object is validated by the SDK before being sent: keys and values must be strings, the object must have at most 20 keys, no key longer than 64 chars, no value longer than 512 chars, and the JSON-encoded payload must fit in 4 KB. Violations throw AlterValueError so a malformed context never silently disappears from the audit trail.

Grant Management

List Grants

Retrieve OAuth grants for the application:
const result = await vault.listGrants();
for (const grant of result.grants) {
  console.log(`${grant.providerId}: ${grant.accountDisplayName} (${grant.status})`);
}

// Filter by provider with pagination
const googleGrants = await vault.listGrants({ providerId: "google", limit: 10 });
Returns GrantListResult with grants (GrantInfo[]), total, limit, offset, hasMore. Each GrantInfo has:
FieldTypeDescription
grantIdstringUnique connection identifier (UUID). Not .id — always use .grantId
providerIdstringProvider slug (e.g., "google", "slack")
scopesstring[]Granted OAuth scopes
accountIdentifierstring | nullProvider account email or username
accountDisplayNamestring | nullHuman-readable account name
statusstringConnection status (e.g., "active")
scopeMismatchbooleantrue if granted scopes don’t match requested scopes
expiresAtstring | nullExpiry timestamp (if a grant policy TTL was set)
createdAtstringWhen the grant was created
lastUsedAtstring | nullWhen the grant was last used for an API call

Create Connect Session

Generate a URL for end-users to connect OAuth providers:
const session = await vault.createConnectSession({
  allowedProviders: ["google", "github"],
  returnUrl: "https://myapp.com/callback",
});
console.log(`Connect URL: ${session.connectUrl}`);
Returns ConnectSession with sessionToken, connectUrl, expiresIn, expiresAt.

Scoping grant operations to the authenticated user

In user-facing applications — where many end users share a single AlterVault instance — configure userTokenGetter so every grant operation is tied to the caller’s identity. The SDK forwards the end user’s IDP JWT and the backend resolves each call against that user’s own grants.
const vault = new AlterVault({
  apiKey: "alter_key_...",
  userTokenGetter: () => getCurrentUserJwt(),
});

// listGrants() returns the caller's grants.
const result = await vault.listGrants();

// revokeGrant() targets a grant the caller owns.
// Passing a grantId that does not belong to the caller throws
// GrantNotFoundError.
await vault.revokeGrant(grantId);

// createConnectSession() produces a Connect URL tied to the caller's identity.
const session = await vault.createConnectSession({
  allowedProviders: ["google"],
});

// vault.request({ grantId, ... }) retrieves a token only when the grant
// belongs to the caller. (Or use { provider: "..." } for identity resolution.)
await vault.request(HttpMethod.GET, url, { grantId });
If userTokenGetter is configured but cannot produce a token (e.g. the end user’s session has expired), the SDK throws AlterSDKError. Surface this as a 401 to the end user so they can re-authenticate. Omit userTokenGetter for headless tools (scripts, cron jobs, backend migrations) that operate on grants the developer manages directly. The same methods then run under the application’s own credentials.

Headless Connect (from code)

For CLI tools, scripts, and server-side applications, use connect() to open the browser, wait for the user to complete OAuth, and get the result back:
const results = await vault.connect({
  providers: ["google"],
  timeout: 300_000,    // max wait in milliseconds (default: 300000 = 5 min)
  pollInterval: 2000,  // poll cadence in milliseconds
  openBrowser: true,   // set false to print URL instead
});
for (const result of results) {
  console.log(`Connected: ${result.grantId} (${result.providerId})`);
}

// Now use the grantId with vault.request()
const response = await vault.request(
  HttpMethod.GET,
  "https://www.googleapis.com/calendar/v3/calendars/primary/events",
  { grantId: results[0].grantId },
);
Returns ConnectResult[] — one per connected provider. Each has grantId, providerId, accountIdentifier, scopes, and optionally grantPolicy (with expiresAt if a TTL was set). Throws ConnectTimeoutError if the user doesn’t complete in time, ConnectFlowError if denied.

Grant Policy (TTL)

Use grantPolicy to control how long a grant stays active. When set, the Connect UI shows an expiry picker and the grant automatically expires after the chosen duration.
const results = await vault.connect({
  providers: ["google"],
  grantPolicy: {
    maxTtlSeconds: 604800,     // longest duration the user can pick
    defaultTtlSeconds: 86400,  // pre-selected in the dropdown
  },
});
The Connect UI offers these expiry options: 1 hour, 1 day, 7 days, 30 days, 90 days. Setting maxTtlSeconds hides any option that exceeds it.
ParameterDescription
maxTtlSecondsMaximum TTL the end user can select. Options above this are hidden from the picker.
defaultTtlSecondsPre-selected option in the dropdown. Falls back to 1 day if not set.
The expiry picker only appears when grantPolicy is passed. Without it, grants have no automatic expiry. After a grant expires, token retrieval throws GrantExpiredError and the user must re-authorize via Alter Connect.
The same parameter works with createConnectSession():
const session = await vault.createConnectSession({
  allowedProviders: ["google"],
  grantPolicy: {
    maxTtlSeconds: 2592000,    // max 30 days
    defaultTtlSeconds: 604800, // default 7 days
  },
});

Error Handling

Always handle errors explicitly. Policy enforcement happens on EVERY token retrieval and can deny access based on configured rules.

Exception Hierarchy

AlterSDKError (base — also thrown for input validation: invalid apiKey, URL validation, missing pathParams)
├── AlterValueError (SDK rejected input — fix application code, e.g. malformed `context` object)
├── BackendError (backend errors)
│   ├── ReAuthRequiredError (user must re-authorize via Alter Connect)
│   │   ├── GrantExpiredError (403 — TTL elapsed)
│   │   ├── CredentialRevokedError (400 — auth permanently broken)
│   │   ├── GrantRevokedError (400 — grant revoked)
│   │   └── GrantDeletedError (410 — user disconnected via Wallet)
│   ├── GrantNotFoundError (404 — wrong grant_id)
│   └── PolicyViolationError (403 — policy denied access)
├── ConnectFlowError (headless connect() failed)
│   ├── ConnectDeniedError (user denied authorization)
│   ├── ConnectConfigError (OAuth app misconfigured)
│   └── ConnectTimeoutError (user didn't complete OAuth in time)
├── ProviderAPIError (provider returned 4xx/5xx)
│   └── ScopeReauthRequiredError (403 + scope mismatch — user must re-authorize)
└── NetworkError (connection/timeout failures)
    └── TimeoutError (request timed out)

Complete Error Handling Example

import { AlterVault, HttpMethod } from "@alter-ai/alter-sdk";
import {
  ReAuthRequiredError,
  PolicyViolationError,
  GrantNotFoundError,
  ScopeReauthRequiredError,
  NetworkError,
  ProviderAPIError,
} from "@alter-ai/alter-sdk";

async function safeApiCall(grantId: string) {
  const vault = new AlterVault({
    apiKey: "alter_key_...",
    caller: "my-agent",
  });

  try {
    const response = await vault.request(
      HttpMethod.GET,
      "https://www.googleapis.com/calendar/v3/calendars/primary/events",
      { grantId },
    );
    return await response.json();

  } catch (e) {
    if (e instanceof ReAuthRequiredError) {
      // Catches GrantExpiredError, CredentialRevokedError, GrantRevokedError, GrantDeletedError
      console.log("User must re-authorize via Alter Connect");
    } else if (e instanceof PolicyViolationError) {
      console.log(`Access denied by policy: ${e.message} (rule: ${e.policyError})`);
    } else if (e instanceof GrantNotFoundError) {
      console.log("User hasn't connected Google yet");
    } else if (e instanceof NetworkError) {
      // TimeoutError is a subclass, so this catches both
      console.log(`Network issue: ${e.message}`);
    } else if (e instanceof ScopeReauthRequiredError) {
      console.log(`Scope mismatch on ${e.grantId}: re-auth needed`);
      // Create a new Connect session so the user can grant updated scopes
    } else if (e instanceof ProviderAPIError) {
      console.log(`Provider error ${e.statusCode}: ${e.responseBody}`);
    } else {
      throw e;
    }
  } finally {
    await vault.close();
  }
}

Common Error Scenarios

ExceptionHTTP StatusCauseResolution
ReAuthRequiredError400/403/410Grant expired/revoked/deletedRedirect to Alter Connect
PolicyViolationError403Policy denied accessCheck policy rules in dashboard
GrantNotFoundError404User has no OAuth grantRedirect to Alter Connect UI
BackendError401Invalid API keyCheck the apiKey value
ScopeReauthRequiredError403Provider 403 + scope mismatchUser must re-authorize via Connect
ProviderAPIError4xx/5xxProvider API errorCheck provider docs
TimeoutError-Request timed outRetry with backoff
NetworkError-Connection refusedCheck network

Next Steps

Quickstart Guide

Full integration walkthrough with frontend + backend

Architecture

Understand the security architecture

Python SDK

Python SDK equivalent