@alter-ai/alter-sdk 0.3.1 → 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
@@ -14,7 +14,7 @@ Official TypeScript SDK for [Alter Vault](https://alterai.dev) — Credential ma
14
14
  - **API Key and Custom Credential Support**: Handles OAuth tokens, API keys, and custom credential formats automatically
15
15
  - **Actor Tracking**: First-class support for AI agent and MCP server observability
16
16
  - **HMAC Request Signing**: All SDK-to-backend requests are signed with a derived HMAC-SHA256 key for integrity, authenticity, and replay protection
17
- - **Native Promises**: Built on native `fetch` no heavy dependencies
17
+ - **Native Promises**: Built on native `fetch` -no heavy dependencies
18
18
 
19
19
  ## Installation
20
20
 
@@ -33,7 +33,7 @@ const vault = new AlterVault({
33
33
  actorIdentifier: "my-agent",
34
34
  });
35
35
 
36
- // Make API request token injected automatically, never exposed
36
+ // Make API request -token injected automatically, never exposed
37
37
  const response = await vault.request(
38
38
  "CONNECTION_ID", // from Alter Connect (see below)
39
39
  HttpMethod.GET,
@@ -51,16 +51,16 @@ await vault.close();
51
51
 
52
52
  ### Where does `connectionId` come from?
53
53
 
54
- **OAuth connections:**
55
- 1. User completes OAuth via [Alter Connect](https://docs.alterai.dev/sdks/javascript/quickstart) (frontend widget)
56
- 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
57
57
  3. You save it in your database, mapped to your user
58
58
  4. You pass it to `vault.request()` when making API calls
59
59
 
60
- **Managed secrets** (API keys, service tokens):
61
- 1. Store credentials in the [Developer Portal](https://portal.alterai.dev) under **Managed Secrets**
62
- 2. Copy the `connectionId` returned
63
- 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
64
64
 
65
65
  ```typescript
66
66
  // You can also discover connectionIds programmatically:
@@ -184,7 +184,6 @@ Generate a session URL for end-users to authenticate with OAuth providers:
184
184
 
185
185
  ```typescript
186
186
  const session = await vault.createConnectSession({
187
- endUser: { id: "alice" },
188
187
  allowedProviders: ["google", "github"],
189
188
  returnUrl: "https://myapp.com/callback",
190
189
  });
@@ -194,12 +193,50 @@ console.log(`Expires in: ${session.expiresIn}s`);
194
193
 
195
194
  | Parameter | Type | Default | Description |
196
195
  |-----------|------|---------|-------------|
197
- | `endUser` | `{ id: string }` | *required* | End user identity |
198
196
  | `allowedProviders` | `string[]` | - | Restrict to specific providers |
199
197
  | `returnUrl` | `string` | - | Redirect URL after OAuth flow |
200
198
 
201
199
  Returns `ConnectSession` with: `sessionToken`, `connectUrl`, `expiresIn`, `expiresAt`.
202
200
 
201
+ #### Headless Connect (from code)
202
+
203
+ For CLI tools, scripts, and server-side applications -- opens the browser, waits for the user to complete OAuth, and returns the result:
204
+
205
+ ```typescript
206
+ const results = await vault.connect({
207
+ providers: ["google"],
208
+ connectionPolicy: { // optional TTL bounds
209
+ maxTtlSeconds: 86400,
210
+ defaultTtlSeconds: 3600,
211
+ },
212
+ timeout: 300, // max wait in seconds (default: 5 min)
213
+ openBrowser: true, // set false to print URL instead
214
+ });
215
+ for (const result of results) {
216
+ console.log(`Connected: ${result.connectionId} (${result.providerId})`);
217
+ console.log(`Account: ${result.accountIdentifier}`);
218
+ }
219
+
220
+ // Now use the connectionId with vault.request()
221
+ const response = await vault.request(
222
+ results[0].connectionId,
223
+ HttpMethod.GET,
224
+ "https://www.googleapis.com/calendar/v3/calendars/primary/events",
225
+ );
226
+ ```
227
+
228
+ | Parameter | Type | Default | Description |
229
+ |-----------|------|---------|-------------|
230
+ | `providers` | `string[]` | - | Restrict to specific providers |
231
+ | `timeout` | `number` | `300` | Max seconds to wait for completion |
232
+ | `pollInterval` | `number` | `2` | Seconds between status checks |
233
+ | `connectionPolicy` | `{ maxTtlSeconds?: number; defaultTtlSeconds?: number }` | - | TTL bounds (optional) |
234
+ | `openBrowser` | `boolean` | `true` | Open browser automatically |
235
+
236
+ Returns `ConnectResult[]` -one per connected provider. Each has: `connectionId`, `providerId`, `accountIdentifier`, `scopes`, and optionally `connectionPolicy` (if a TTL was set).
237
+
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
+
203
240
  ### AI Agent Actor Tracking
204
241
 
205
242
  ```typescript
@@ -284,20 +321,49 @@ const vault = new AlterVault({
284
321
 
285
322
  ## Error Handling
286
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
+
287
345
  > **Note:** Input validation errors (invalid `apiKey`, invalid/missing `actorType`, missing `actorIdentifier`, invalid URL scheme, missing `pathParams`) throw `AlterSDKError`.
288
346
 
289
347
  ```typescript
290
348
  import {
291
349
  AlterVault,
292
350
  HttpMethod,
293
- AlterSDKError, // Base error (including validation: apiKey, actorType, actorIdentifier, URL scheme, pathParams)
294
- PolicyViolationError,
295
- ConnectionNotFoundError,
296
- TokenExpiredError,
297
- TokenRetrievalError,
298
- NetworkError,
299
- TimeoutError,
300
- ProviderAPIError,
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)
359
+ ConnectFlowError, // Headless connect() failed (denied, provider error)
360
+ ConnectDeniedError, // User denied authorization
361
+ ConnectConfigError, // OAuth app misconfigured
362
+ ConnectTimeoutError, // Headless connect() timed out
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)
301
367
  } from "@alter-ai/alter-sdk";
302
368
 
303
369
  try {
@@ -307,22 +373,54 @@ try {
307
373
  "https://www.googleapis.com/calendar/v3/calendars/primary/events",
308
374
  );
309
375
  } catch (error) {
310
- if (error instanceof PolicyViolationError) {
311
- 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");
312
387
  } else if (error instanceof ConnectionNotFoundError) {
313
- console.error("No OAuth connection — user needs to authenticate");
314
- } else if (error instanceof TokenExpiredError) {
315
- console.error(`Token expired for connection: ${error.connectionId}`);
316
- } else if (error instanceof TimeoutError) {
317
- 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 ---
318
397
  } else if (error instanceof NetworkError) {
319
- 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
320
405
  } else if (error instanceof ProviderAPIError) {
321
406
  console.error(`Provider error ${error.statusCode}: ${error.responseBody}`);
322
407
  }
323
408
  }
324
409
  ```
325
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
+
326
424
  ## Resource Management
327
425
 
328
426
  ```typescript
@@ -338,19 +436,51 @@ try {
338
436
  await vault.close(); // Waits for pending audit logs, cleans up timers
339
437
  }
340
438
 
341
- // close() is idempotent safe to call multiple times
439
+ // close() is idempotent -safe to call multiple times
342
440
  // Calling request() after close() throws AlterSDKError
343
441
  ```
344
442
 
345
443
  ## Supported Providers
346
444
 
347
- 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.
348
446
 
349
447
  ```typescript
350
- // Use the connection_id from Alter Connect
351
- 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);
352
475
  ```
353
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
+
354
484
  ## Requirements
355
485
 
356
486
  - Node.js 18+ (uses native `fetch`)