@alter-ai/alter-sdk
v0.4.0
Published
Official TypeScript SDK for Alter Vault — OAuth token management with policy enforcement
Readme
Alter SDK for TypeScript / Node.js
Official TypeScript SDK for Alter Vault — Credential management for agents with policy enforcement.
Features
- Zero Token Exposure: Tokens are never exposed to developers — injected automatically
- Single Entry Point: One method (
vault.request()) for all provider APIs - Type-Safe Enums:
HttpMethodenums with autocomplete - URL Templating: Path parameter substitution with automatic URL encoding
- Automatic Audit Logging: All API calls logged with request metadata (HTTP method and URL) for full audit trail
- Real-time Policy Enforcement: Every token request checked against current policies
- Automatic Token Refresh: Tokens refreshed transparently by the backend
- API Key and Custom Credential Support: Handles OAuth tokens, API keys, and custom credential formats automatically
- AWS SigV4 Support: Automatic AWS Signature Version 4 signing for S3, Bedrock, DynamoDB, and other AWS services (no AWS SDK required)
- Actor Tracking: First-class support for AI agent and MCP server observability
- HMAC Request Signing: All SDK-to-backend requests are signed with a derived HMAC-SHA256 key for integrity, authenticity, and replay protection
- Native Promises: Built on native
fetch— no heavy dependencies
Installation
npm install @alter-ai/alter-sdkQuick Start
import { AlterVault, ActorType, Provider, HttpMethod } from "@alter-ai/alter-sdk";
const vault = new AlterVault({
apiKey: "alter_key_...",
actorType: ActorType.AI_AGENT,
actorIdentifier: "my-agent",
});
// Make API request — token injected automatically, never exposed
const response = await vault.request(
"CONNECTION_ID", // from Alter Connect (see below)
HttpMethod.GET,
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
{
queryParams: { maxResults: "10" },
},
);
const events = await response.json();
console.log(events);
await vault.close();Where does connectionId come from?
OAuth connections:
- User completes OAuth via Alter Connect (frontend widget)
- The
onSuccesscallback returns aconnectionId(UUID) - You save it in your database, mapped to your user
- You pass it to
vault.request()when making API calls
Managed secrets (API keys, service tokens):
- Store credentials in the Developer Portal under Managed Secrets
- Copy the
connectionIdreturned - Use the same
vault.request()— credentials are injected automatically
// You can also discover connectionIds programmatically:
const result = await vault.listConnections({ providerId: "google" });
for (const conn of result.connections) {
console.log(`${conn.id}: ${conn.accountDisplayName}`);
}Usage
The request() method returns the raw Response object. The token is injected automatically and never exposed. The backend token response includes connectionId and providerId for audit correlation.
Simple GET Request
const response = await vault.request(
connectionId,
HttpMethod.GET,
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
);POST with JSON Body
const response = await vault.request(
connectionId,
HttpMethod.POST,
"https://api.example.com/v1/items",
{
json: { name: "New Item", price: 99.99 },
reason: "Creating new item",
},
);URL Path Templating
const response = await vault.request(
connectionId,
HttpMethod.PUT,
"https://api.example.com/v1/items/{item_id}",
{
pathParams: { item_id: "123" },
json: { price: 89.99 },
},
);Query Parameters and Extra Headers
const response = await vault.request(
connectionId,
HttpMethod.POST,
"https://api.notion.com/v1/databases/{db_id}/query",
{
pathParams: { db_id: "abc123" },
extraHeaders: { "Notion-Version": "2022-06-28" },
json: { page_size: 10 },
},
);Using Managed Secrets
For your own APIs with API keys or service tokens (no OAuth flow needed):
const vault = new AlterVault({
apiKey: "alter_key_...",
actorType: ActorType.BACKEND_SERVICE,
actorIdentifier: "my-service",
});
const response = await vault.request(
"MANAGED_SECRET_CONNECTION_ID", // from Developer Portal
HttpMethod.GET,
"https://api.internal.com/v1/data",
);
await vault.close();The credential is injected automatically as the configured header type (Bearer, API Key, Basic Auth).
Connection Management
List Connections
Retrieve OAuth connections for your app, optionally filtered by provider:
const result = await vault.listConnections();
for (const conn of result.connections) {
console.log(`${conn.providerId}: ${conn.accountDisplayName} (${conn.status})`);
}
// Filter by provider with pagination
const googleConns = await vault.listConnections({
providerId: "google",
limit: 10,
offset: 0,
});
console.log(`Total: ${googleConns.total}, Has more: ${googleConns.hasMore}`);| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| providerId | string | - | Filter by provider (e.g., "google") |
| limit | number | 100 | Max connections to return |
| offset | number | 0 | Pagination offset |
Returns ConnectionListResult with: connections (ConnectionInfo[]), total, limit, offset, hasMore.
Create Connect Session
Generate a session URL for end-users to authenticate with OAuth providers:
const session = await vault.createConnectSession({
endUser: { id: "alice" },
allowedProviders: ["google", "github"],
returnUrl: "https://myapp.com/callback",
});
console.log(`Connect URL: ${session.connectUrl}`);
console.log(`Expires in: ${session.expiresIn}s`);| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| endUser | { id: string } | required | End user identity |
| allowedProviders | string[] | - | Restrict to specific providers |
| returnUrl | string | - | Redirect URL after OAuth flow |
Returns ConnectSession with: sessionToken, connectUrl, expiresIn, expiresAt.
Headless Connect (from code)
For CLI tools, scripts, and server-side applications -- opens the browser, waits for the user to complete OAuth, and returns the result:
const results = await vault.connect({
endUser: { id: "alice" },
providers: ["google"],
timeout: 300, // max wait in seconds (default: 5 min)
openBrowser: true, // set false to print URL instead
});
for (const result of results) {
console.log(`Connected: ${result.connectionId} (${result.providerId})`);
console.log(`Account: ${result.accountIdentifier}`);
}
// Now use the connectionId with vault.request()
const response = await vault.request(
results[0].connectionId,
HttpMethod.GET,
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
);| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| endUser | { id: string } | required | End user identity |
| providers | string[] | - | Restrict to specific providers |
| timeout | number | 300 | Max seconds to wait for completion |
| pollInterval | number | 2 | Seconds between status checks |
| openBrowser | boolean | true | Open browser automatically |
Returns ConnectResult[] — one per connected provider. Each has: connectionId, providerId, accountIdentifier, scopes.
Throws ConnectTimeoutError if the user doesn't complete in time, ConnectFlowError if denied.
AI Agent Actor Tracking
import { AlterVault, ActorType, Provider, HttpMethod } from "@alter-ai/alter-sdk";
const vault = new AlterVault({
apiKey: "alter_key_...",
actorType: ActorType.AI_AGENT,
actorIdentifier: "email-assistant-v2",
actorName: "Email Assistant",
actorVersion: "2.0.0",
framework: "langgraph",
});
const response = await vault.request(
connectionId,
HttpMethod.GET,
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
{
runId: "550e8400-e29b-41d4-a716-446655440000", // auto-generated UUID if omitted
threadId: "thread-xyz",
toolCallId: "call_abc_123",
},
);Note:
runIdis auto-generated as a UUID v4 if not provided. All sub-actions within a singlerequest()call share the samerunIdfor audit log grouping.
Multi-Agent Deployments
Each agent must create its own AlterVault instance with a unique actor identity. Do not share a single instance across agents.
// Each agent gets its own vault instance
const emailAgent = new AlterVault({
apiKey: "alter_key_...",
actorType: ActorType.AI_AGENT,
actorIdentifier: "email-assistant-v2",
actorName: "Email Assistant",
});
const calendarAgent = new AlterVault({
apiKey: "alter_key_...",
actorType: ActorType.AI_AGENT,
actorIdentifier: "calendar-agent-v1",
actorName: "Calendar Agent",
});
// Audit logs and policies are tracked per agent
await emailAgent.request(
gmailConnectionId, // from Alter Connect
HttpMethod.GET,
"https://gmail.googleapis.com/gmail/v1/users/me/messages",
);
await calendarAgent.request(
calendarConnectionId, // from Alter Connect
HttpMethod.GET,
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
);
// Clean up each instance
await emailAgent.close();
await calendarAgent.close();Configuration
import { AlterVault, ActorType } from "@alter-ai/alter-sdk";
const vault = new AlterVault({
apiKey: "alter_key_...", // Required: Alter Vault API key
actorType: ActorType.AI_AGENT, // Required: ActorType enum
actorIdentifier: "my-agent", // Required: Unique identifier
timeout: 30000, // Optional: HTTP timeout in ms (default: 30000)
actorName: "My Agent", // Optional: Human-readable name
actorVersion: "1.0.0", // Optional: Version string
framework: "langgraph", // Optional: AI framework
clientType: "cursor", // Optional: MCP client type
});Error Handling
Note: Input validation errors (invalid
apiKey, invalid/missingactorType, missingactorIdentifier, invalid URL scheme, missingpathParams) throwAlterSDKError.
import {
AlterVault,
HttpMethod,
AlterSDKError, // Base error (including validation: apiKey, actorType, actorIdentifier, URL scheme, pathParams)
PolicyViolationError,
ConnectionNotFoundError,
TokenExpiredError,
TokenRetrievalError,
ConnectFlowError, // Headless connect() failed (denied, provider error)
ConnectTimeoutError, // Headless connect() timed out
NetworkError,
TimeoutError,
ProviderAPIError,
} from "@alter-ai/alter-sdk";
try {
const response = await vault.request(
connectionId,
HttpMethod.GET,
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
);
} catch (error) {
if (error instanceof PolicyViolationError) {
console.error(`Policy denied: ${error.message}`);
} else if (error instanceof ConnectionNotFoundError) {
console.error("No OAuth connection — user needs to authenticate");
} else if (error instanceof TokenExpiredError) {
console.error(`Token expired for connection: ${error.connectionId}`);
} else if (error instanceof TimeoutError) {
console.error(`Request timed out — safe to retry: ${error.message}`);
} else if (error instanceof NetworkError) {
console.error(`Network issue: ${error.message}`);
} else if (error instanceof ProviderAPIError) {
console.error(`Provider error ${error.statusCode}: ${error.responseBody}`);
}
}Resource Management
// Manual cleanup (recommended)
const vault = new AlterVault({
apiKey: "alter_key_...",
actorType: ActorType.BACKEND_SERVICE,
actorIdentifier: "my-service",
});
try {
const response = await vault.request(...);
} finally {
await vault.close(); // Waits for pending audit logs, cleans up timers
}
// close() is idempotent — safe to call multiple times
// Calling request() after close() throws AlterSDKErrorSupported Providers
The SDK works with any OAuth provider configured in your Alter Vault dashboard. The first parameter to vault.request() is the connection_id (UUID) returned when a user connects via Alter Connect.
// Use the connection_id from Alter Connect
await vault.request("550e8400-e29b-41d4-a716-446655440000", HttpMethod.GET, url);Requirements
- Node.js 18+ (uses native
fetch) - TypeScript 5.0+
License
MIT License
