@alter-ai/alter-sdk 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Alter SDK for TypeScript / Node.js
2
2
 
3
- Official TypeScript SDK for [Alter Vault](https://alterai.dev) Credential management for agents with policy enforcement.
3
+ Official TypeScript SDK for [Alter Vault](https://alterai.dev) -Credential management for agents with policy enforcement.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Zero Token Exposure**: Tokens are never exposed to developers injected automatically
7
+ - **Zero Token Exposure**: Tokens are never exposed to developers -injected automatically
8
8
  - **Single Entry Point**: One method (`vault.request()`) for all provider APIs
9
9
  - **Type-Safe Enums**: `HttpMethod` enums with autocomplete
10
10
  - **URL Templating**: Path parameter substitution with automatic URL encoding
@@ -12,10 +12,9 @@ Official TypeScript SDK for [Alter Vault](https://alterai.dev) — Credential ma
12
12
  - **Real-time Policy Enforcement**: Every token request checked against current policies
13
13
  - **Automatic Token Refresh**: Tokens refreshed transparently by the backend
14
14
  - **API Key and Custom Credential Support**: Handles OAuth tokens, API keys, and custom credential formats automatically
15
- - **AWS SigV4 Support**: Automatic AWS Signature Version 4 signing for S3, Bedrock, DynamoDB, and other AWS services (no AWS SDK required)
16
15
  - **Actor Tracking**: First-class support for AI agent and MCP server observability
17
16
  - **HMAC Request Signing**: All SDK-to-backend requests are signed with a derived HMAC-SHA256 key for integrity, authenticity, and replay protection
18
- - **Native Promises**: Built on native `fetch` no heavy dependencies
17
+ - **Native Promises**: Built on native `fetch` -no heavy dependencies
19
18
 
20
19
  ## Installation
21
20
 
@@ -34,7 +33,7 @@ const vault = new AlterVault({
34
33
  actorIdentifier: "my-agent",
35
34
  });
36
35
 
37
- // Make API request token injected automatically, never exposed
36
+ // Make API request -token injected automatically, never exposed
38
37
  const response = await vault.request(
39
38
  "CONNECTION_ID", // from Alter Connect (see below)
40
39
  HttpMethod.GET,
@@ -52,16 +51,16 @@ await vault.close();
52
51
 
53
52
  ### Where does `connectionId` come from?
54
53
 
55
- **OAuth connections:**
56
- 1. User completes OAuth via [Alter Connect](https://docs.alterai.dev/sdks/javascript/quickstart) (frontend widget)
57
- 2. The `onSuccess` callback returns a `connectionId` (UUID)
54
+ **OAuth connections** (per-user, from end user action):
55
+ 1. Your **end user** completes OAuth via [Alter Connect](https://docs.alterai.dev/sdks/javascript/quickstart) (frontend widget) or `vault.connect()` (headless)
56
+ 2. The `onSuccess` callback returns a `connectionId` (UUID) -one per user per account
58
57
  3. You save it in your database, mapped to your user
59
58
  4. You pass it to `vault.request()` when making API calls
60
59
 
61
- **Managed secrets** (API keys, service tokens):
62
- 1. Store credentials in the [Developer Portal](https://portal.alterai.dev) under **Managed Secrets**
63
- 2. Copy the `connectionId` returned
64
- 3. Use the same `vault.request()` credentials are injected automatically
60
+ **Managed secrets** (per-service, from developer action):
61
+ 1. **You** store credentials in the [Developer Portal](https://portal.alterai.dev) under **Managed Secrets**
62
+ 2. The portal returns a `connectionId` -one per stored credential, shared across your backend
63
+ 3. Use the same `vault.request()` -credentials are injected automatically
65
64
 
66
65
  ```typescript
67
66
  // You can also discover connectionIds programmatically:
@@ -185,7 +184,6 @@ Generate a session URL for end-users to authenticate with OAuth providers:
185
184
 
186
185
  ```typescript
187
186
  const session = await vault.createConnectSession({
188
- endUser: { id: "alice" },
189
187
  allowedProviders: ["google", "github"],
190
188
  returnUrl: "https://myapp.com/callback",
191
189
  });
@@ -195,7 +193,6 @@ console.log(`Expires in: ${session.expiresIn}s`);
195
193
 
196
194
  | Parameter | Type | Default | Description |
197
195
  |-----------|------|---------|-------------|
198
- | `endUser` | `{ id: string }` | *required* | End user identity |
199
196
  | `allowedProviders` | `string[]` | - | Restrict to specific providers |
200
197
  | `returnUrl` | `string` | - | Redirect URL after OAuth flow |
201
198
 
@@ -207,8 +204,11 @@ For CLI tools, scripts, and server-side applications -- opens the browser, waits
207
204
 
208
205
  ```typescript
209
206
  const results = await vault.connect({
210
- endUser: { id: "alice" },
211
207
  providers: ["google"],
208
+ connectionPolicy: { // optional TTL bounds
209
+ maxTtlSeconds: 86400,
210
+ defaultTtlSeconds: 3600,
211
+ },
212
212
  timeout: 300, // max wait in seconds (default: 5 min)
213
213
  openBrowser: true, // set false to print URL instead
214
214
  });
@@ -227,15 +227,15 @@ const response = await vault.request(
227
227
 
228
228
  | Parameter | Type | Default | Description |
229
229
  |-----------|------|---------|-------------|
230
- | `endUser` | `{ id: string }` | *required* | End user identity |
231
230
  | `providers` | `string[]` | - | Restrict to specific providers |
232
231
  | `timeout` | `number` | `300` | Max seconds to wait for completion |
233
232
  | `pollInterval` | `number` | `2` | Seconds between status checks |
233
+ | `connectionPolicy` | `{ maxTtlSeconds?: number; defaultTtlSeconds?: number }` | - | TTL bounds (optional) |
234
234
  | `openBrowser` | `boolean` | `true` | Open browser automatically |
235
235
 
236
- Returns `ConnectResult[]` one per connected provider. Each has: `connectionId`, `providerId`, `accountIdentifier`, `scopes`.
236
+ Returns `ConnectResult[]` -one per connected provider. Each has: `connectionId`, `providerId`, `accountIdentifier`, `scopes`, and optionally `connectionPolicy` (if a TTL was set).
237
237
 
238
- Throws `ConnectTimeoutError` if the user doesn't complete in time, `ConnectFlowError` if denied.
238
+ Throws `ConnectTimeoutError` if the user doesn't complete in time, `ConnectDeniedError` if the user denies authorization, `ConnectConfigError` if the OAuth app is misconfigured.
239
239
 
240
240
  ### AI Agent Actor Tracking
241
241
 
@@ -321,22 +321,49 @@ const vault = new AlterVault({
321
321
 
322
322
  ## Error Handling
323
323
 
324
+ The SDK provides a typed exception hierarchy so you can handle each failure mode precisely:
325
+
326
+ ```
327
+ AlterSDKError (base)
328
+ ├── BackendError // Generic backend error
329
+ │ ├── ReAuthRequiredError // User must re-authorize via Alter Connect
330
+ │ │ ├── ConnectionExpiredError // 403 — connection TTL elapsed
331
+ │ │ ├── ConnectionRevokedError // 400 — auth permanently broken (revoked, invalid_grant)
332
+ │ │ └── ConnectionDeletedError // 410 — user disconnected via Wallet (new ID on re-auth)
333
+ │ ├── ConnectionNotFoundError // 404 — wrong connection_id
334
+ │ └── PolicyViolationError // 403 — policy denied (business hours, IP, etc.)
335
+ ├── ConnectFlowError // Headless connect() failed
336
+ │ ├── ConnectDeniedError // User clicked Deny
337
+ │ ├── ConnectConfigError // OAuth app misconfigured
338
+ │ └── ConnectTimeoutError // User didn't complete in time
339
+ ├── ProviderAPIError // Provider returned 4xx/5xx
340
+ │ └── ScopeReauthRequiredError // 403 + scope mismatch — user must re-authorize
341
+ └── NetworkError // Backend or provider unreachable
342
+ └── TimeoutError // Request timed out (safe to retry)
343
+ ```
344
+
324
345
  > **Note:** Input validation errors (invalid `apiKey`, invalid/missing `actorType`, missing `actorIdentifier`, invalid URL scheme, missing `pathParams`) throw `AlterSDKError`.
325
346
 
326
347
  ```typescript
327
348
  import {
328
349
  AlterVault,
329
350
  HttpMethod,
330
- AlterSDKError, // Base error (including validation: apiKey, actorType, actorIdentifier, URL scheme, pathParams)
331
- PolicyViolationError,
332
- ConnectionNotFoundError,
333
- TokenExpiredError,
334
- TokenRetrievalError,
351
+ AlterSDKError, // Base error (including validation errors)
352
+ BackendError, // Generic backend error
353
+ ReAuthRequiredError, // Parent for all re-auth errors
354
+ ConnectionDeletedError, // User disconnected via Wallet — new ID on re-auth (410)
355
+ ConnectionExpiredError, // TTL expired — user must re-authorize (403)
356
+ ConnectionNotFoundError, // Wrong connection_id — check for typos (404)
357
+ ConnectionRevokedError, // Auth permanently broken — revoked/invalid_grant (400)
358
+ PolicyViolationError, // Policy denied — business hours, IP, etc. (403)
335
359
  ConnectFlowError, // Headless connect() failed (denied, provider error)
360
+ ConnectDeniedError, // User denied authorization
361
+ ConnectConfigError, // OAuth app misconfigured
336
362
  ConnectTimeoutError, // Headless connect() timed out
337
- NetworkError,
338
- TimeoutError,
339
- ProviderAPIError,
363
+ NetworkError, // Backend or provider unreachable
364
+ TimeoutError, // Request timed out (subclass of NetworkError)
365
+ ProviderAPIError, // Provider API returned error (4xx/5xx)
366
+ ScopeReauthRequiredError, // 403 + scope mismatch (subclass of ProviderAPIError)
340
367
  } from "@alter-ai/alter-sdk";
341
368
 
342
369
  try {
@@ -346,22 +373,54 @@ try {
346
373
  "https://www.googleapis.com/calendar/v3/calendars/primary/events",
347
374
  );
348
375
  } catch (error) {
349
- if (error instanceof PolicyViolationError) {
350
- console.error(`Policy denied: ${error.message}`);
376
+ // --- Connection unusable — user must re-authorize via Alter Connect ---
377
+ if (error instanceof ConnectionExpiredError) {
378
+ // TTL set during Connect flow has elapsed
379
+ console.error("Connection expired — prompt user to re-authorize");
380
+ } else if (error instanceof ConnectionRevokedError) {
381
+ // Auth permanently broken — user revoked at provider, refresh token
382
+ // expired, or token refresh permanently failed (invalid_grant)
383
+ console.error("Connection revoked — prompt user to re-authorize");
384
+ } else if (error instanceof ConnectionDeletedError) {
385
+ // User disconnected via Wallet — re-auth generates a NEW connectionId
386
+ console.error("Connection deleted — prompt user to re-connect, store the new ID");
351
387
  } else if (error instanceof ConnectionNotFoundError) {
352
- console.error("No OAuth connection — user needs to authenticate");
353
- } else if (error instanceof TokenExpiredError) {
354
- console.error(`Token expired for connection: ${error.connectionId}`);
355
- } else if (error instanceof TimeoutError) {
356
- console.error(`Request timed out safe to retry: ${error.message}`);
388
+ // No connection with this ID exists check for typos or stale references
389
+ console.error("Connection not found verify your connectionId");
390
+
391
+ // --- Policy restrictions may resolve on its own ---
392
+ } else if (error instanceof PolicyViolationError) {
393
+ // Business hours, IP allowlist, or other Cerbos policy denial
394
+ console.error(`Policy denied: ${error.message} (reason: ${error.policyError})`);
395
+
396
+ // --- Transient / infrastructure errors — safe to retry ---
357
397
  } else if (error instanceof NetworkError) {
358
- console.error(`Network issue: ${error.message}`);
398
+ // TimeoutError is a subclass, so this catches both
399
+ console.error(`Network issue — retry with backoff: ${error.message}`);
400
+
401
+ // --- Provider errors ---
402
+ } else if (error instanceof ScopeReauthRequiredError) {
403
+ console.error(`Scope mismatch on ${error.connectionId} - user needs to re-authorize`);
404
+ // Create a new Connect session so the user can grant updated scopes
359
405
  } else if (error instanceof ProviderAPIError) {
360
406
  console.error(`Provider error ${error.statusCode}: ${error.responseBody}`);
361
407
  }
362
408
  }
363
409
  ```
364
410
 
411
+ ### Re-authorization and Connection IDs
412
+
413
+ When a user re-authorizes through Alter Connect, the **same `connectionId` is preserved** in most cases. The existing connection record is updated in place with fresh tokens. You do **not** need to update your stored `connectionId`.
414
+
415
+ The exception is `ConnectionDeletedError` — the user disconnected via the Wallet, so re-authorization creates a new connection with a **new `connectionId`**. Store the new ID from the `ConnectResult`.
416
+
417
+ | Exception | Same `connectionId` after re-auth? |
418
+ |---|---|
419
+ | `ConnectionExpiredError` | Yes |
420
+ | `ConnectionRevokedError` | Yes |
421
+ | `ConnectionDeletedError` | **No** — new ID generated |
422
+ | `ConnectionNotFoundError` | N/A — ID never existed |
423
+
365
424
  ## Resource Management
366
425
 
367
426
  ```typescript
@@ -377,19 +436,51 @@ try {
377
436
  await vault.close(); // Waits for pending audit logs, cleans up timers
378
437
  }
379
438
 
380
- // close() is idempotent safe to call multiple times
439
+ // close() is idempotent -safe to call multiple times
381
440
  // Calling request() after close() throws AlterSDKError
382
441
  ```
383
442
 
384
443
  ## Supported Providers
385
444
 
386
- 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.
445
+ The SDK includes type-safe `Provider` enums for all 66 supported providers. Use them for filtering connections or as documentation -- `request()` takes a `connectionId` string, not a provider enum.
387
446
 
388
447
  ```typescript
389
- // Use the connection_id from Alter Connect
390
- await vault.request("550e8400-e29b-41d4-a716-446655440000", HttpMethod.GET, url);
448
+ import { Provider } from "@alter-ai/alter-sdk";
449
+
450
+ // Custom providers (full OAuth implementations)
451
+ Provider.GOOGLE // "google"
452
+ Provider.GITHUB // "github"
453
+ Provider.SLACK // "slack"
454
+ Provider.SENTRY // "sentry"
455
+
456
+ // Config-driven providers (63 total) -- examples:
457
+ Provider.HUBSPOT // "hubspot"
458
+ Provider.SALESFORCE // "salesforce"
459
+ Provider.STRIPE // "stripe"
460
+ Provider.MICROSOFT // "microsoft"
461
+ Provider.DISCORD // "discord"
462
+ Provider.SPOTIFY // "spotify"
463
+ Provider.LINKEDIN // "linkedin"
464
+ Provider.DROPBOX // "dropbox"
465
+ Provider.FIGMA // "figma"
466
+ Provider.CALENDLY // "calendly"
467
+ Provider.CLICKUP // "clickup"
468
+ // ... and 52 more (see Provider enum for full list)
469
+
470
+ // Usage: filter connections by provider
471
+ const result = await vault.listConnections({ providerId: Provider.HUBSPOT });
472
+
473
+ // Usage: make requests with connectionId
474
+ await vault.request(connectionId, HttpMethod.GET, url);
391
475
  ```
392
476
 
477
+ <details>
478
+ <summary>All 66 providers</summary>
479
+
480
+ Acuity Scheduling, Adobe, Aircall, Airtable, Apollo, Asana, Atlassian, Attio, Autodesk, Basecamp, Bitbucket, Bitly, Box, Brex, Cal.com, Calendly, Canva, ClickUp, Close, Constant Contact, Contentful, Deel, Dialpad, DigitalOcean, Discord, DocuSign, Dropbox, eBay, Eventbrite, Facebook, Figma, GitHub, Google, HubSpot, Instagram, Linear, LinkedIn, Mailchimp, Mercury, Microsoft, Miro, Monday, Notion, Outreach, PagerDuty, PayPal, Pinterest, Pipedrive, QuickBooks, Ramp, Reddit, RingCentral, Salesforce, Sentry, Slack, Snapchat, Spotify, Square, Squarespace, Stripe, TikTok, Todoist, Twitter, Typeform, Webex, Webflow
481
+
482
+ </details>
483
+
393
484
  ## Requirements
394
485
 
395
486
  - Node.js 18+ (uses native `fetch`)