@alter-ai/alter-sdk 0.5.0 → 0.7.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,6 +1,6 @@
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://alterauth.com) -Credential management for agents with policy enforcement.
4
4
 
5
5
  ## Features
6
6
 
@@ -12,8 +12,8 @@ Official TypeScript SDK for [Alter Vault](https://alterai.dev) -Credential manag
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
- - **Actor Tracking**: First-class support for AI agent and MCP server observability
16
- - **HMAC Request Signing**: All SDK-to-backend requests are signed with a derived HMAC-SHA256 key for integrity, authenticity, and replay protection
15
+ - **Actor Tracking**: First-class support for AI agent and backend service observability
16
+ - **Signed Requests**: All SDK-to-backend requests are cryptographically signed for integrity, authenticity, and replay protection
17
17
  - **Native Promises**: Built on native `fetch` -no heavy dependencies
18
18
 
19
19
  ## Installation
@@ -35,9 +35,9 @@ const vault = new AlterVault({
35
35
 
36
36
  // Make API request -token injected automatically, never exposed
37
37
  const response = await vault.request(
38
- "CONNECTION_ID", // from Alter Connect (see below)
38
+ "GRANT_ID", // from Alter Connect (see below)
39
39
  HttpMethod.GET,
40
- "https://www.googleapis.com/calendar/v3/calendars/primary/events",
40
+ "https://api.example.com/v1/resource",
41
41
  {
42
42
  queryParams: { maxResults: "10" },
43
43
  },
@@ -49,38 +49,38 @@ console.log(events);
49
49
  await vault.close();
50
50
  ```
51
51
 
52
- ### Where does `connectionId` come from?
52
+ ### Where does `grantId` come from?
53
53
 
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
54
+ **OAuth grants** (per-user, from end user action):
55
+ 1. Your **end user** completes OAuth via [Alter Connect](https://docs.alterauth.com/sdks/javascript/quickstart) (frontend widget) or `vault.connect()` (headless)
56
+ 2. The `onSuccess` callback returns a `grantId` (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
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
61
+ 1. **You** store credentials in the [Developer Portal](https://portal.alterauth.com) under **Managed Secrets**
62
+ 2. The portal returns a `grantId` -one per stored credential, shared across your backend
63
63
  3. Use the same `vault.request()` -credentials are injected automatically
64
64
 
65
65
  ```typescript
66
- // You can also discover connectionIds programmatically:
67
- const result = await vault.listConnections({ providerId: "google" });
68
- for (const conn of result.connections) {
69
- console.log(`${conn.id}: ${conn.accountDisplayName}`);
66
+ // You can also discover grantIds programmatically:
67
+ const result = await vault.listGrants({ providerId: "google" });
68
+ for (const grant of result.grants) {
69
+ console.log(`${grant.grantId}: ${grant.accountDisplayName}`);
70
70
  }
71
71
  ```
72
72
 
73
73
  ## Usage
74
74
 
75
- 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.
75
+ The `request()` method returns the raw `Response` object. The token is injected automatically and never exposed. The backend token response includes `grantId` and `providerId` for audit correlation.
76
76
 
77
77
  ### Simple GET Request
78
78
 
79
79
  ```typescript
80
80
  const response = await vault.request(
81
- connectionId,
81
+ grantId,
82
82
  HttpMethod.GET,
83
- "https://www.googleapis.com/calendar/v3/calendars/primary/events",
83
+ "https://api.example.com/v1/resource",
84
84
  );
85
85
  ```
86
86
 
@@ -88,7 +88,7 @@ const response = await vault.request(
88
88
 
89
89
  ```typescript
90
90
  const response = await vault.request(
91
- connectionId,
91
+ grantId,
92
92
  HttpMethod.POST,
93
93
  "https://api.example.com/v1/items",
94
94
  {
@@ -102,7 +102,7 @@ const response = await vault.request(
102
102
 
103
103
  ```typescript
104
104
  const response = await vault.request(
105
- connectionId,
105
+ grantId,
106
106
  HttpMethod.PUT,
107
107
  "https://api.example.com/v1/items/{item_id}",
108
108
  {
@@ -116,7 +116,7 @@ const response = await vault.request(
116
116
 
117
117
  ```typescript
118
118
  const response = await vault.request(
119
- connectionId,
119
+ grantId,
120
120
  HttpMethod.POST,
121
121
  "https://api.notion.com/v1/databases/{db_id}/query",
122
122
  {
@@ -139,7 +139,7 @@ const vault = new AlterVault({
139
139
  });
140
140
 
141
141
  const response = await vault.request(
142
- "MANAGED_SECRET_CONNECTION_ID", // from Developer Portal
142
+ "MANAGED_SECRET_GRANT_ID", // from Developer Portal
143
143
  HttpMethod.GET,
144
144
  "https://api.internal.com/v1/data",
145
145
  );
@@ -149,34 +149,49 @@ await vault.close();
149
149
 
150
150
  The credential is injected automatically as the configured header type (Bearer, API Key, Basic Auth).
151
151
 
152
- ### Connection Management
152
+ ### Grant Management
153
153
 
154
- #### List Connections
154
+ #### List Grants
155
155
 
156
- Retrieve OAuth connections for your app, optionally filtered by provider:
156
+ Retrieve OAuth grants for your app, optionally filtered by provider:
157
157
 
158
158
  ```typescript
159
- const result = await vault.listConnections();
160
- for (const conn of result.connections) {
161
- console.log(`${conn.providerId}: ${conn.accountDisplayName} (${conn.status})`);
159
+ const result = await vault.listGrants();
160
+ for (const grant of result.grants) {
161
+ console.log(`${grant.providerId}: ${grant.accountDisplayName} (${grant.status})`);
162
162
  }
163
163
 
164
164
  // Filter by provider with pagination
165
- const googleConns = await vault.listConnections({
165
+ const googleGrants = await vault.listGrants({
166
166
  providerId: "google",
167
167
  limit: 10,
168
168
  offset: 0,
169
169
  });
170
- console.log(`Total: ${googleConns.total}, Has more: ${googleConns.hasMore}`);
170
+ console.log(`Total: ${googleGrants.total}, Has more: ${googleGrants.hasMore}`);
171
171
  ```
172
172
 
173
173
  | Parameter | Type | Default | Description |
174
174
  |-----------|------|---------|-------------|
175
175
  | `providerId` | `string` | - | Filter by provider (e.g., `"google"`) |
176
- | `limit` | `number` | `100` | Max connections to return |
176
+ | `limit` | `number` | `100` | Max grants to return |
177
177
  | `offset` | `number` | `0` | Pagination offset |
178
178
 
179
- Returns `ConnectionListResult` with: `connections` (`ConnectionInfo[]`), `total`, `limit`, `offset`, `hasMore`.
179
+ Returns `GrantListResult` with: `grants` (`GrantInfo[]`), `total`, `limit`, `offset`, `hasMore`.
180
+
181
+ Each `GrantInfo` has:
182
+
183
+ | Field | Type | Description |
184
+ |-------|------|-------------|
185
+ | `grantId` | `string` | Unique grant identifier (UUID). **Not `.id`** — always use `.grantId` |
186
+ | `providerId` | `string` | Provider slug (e.g., `"google"`, `"slack"`) |
187
+ | `scopes` | `string[]` | Granted OAuth scopes |
188
+ | `accountIdentifier` | `string \| null` | Provider account email or username |
189
+ | `accountDisplayName` | `string \| null` | Human-readable account name |
190
+ | `status` | `string` | Grant status (e.g., `"active"`) |
191
+ | `scopeMismatch` | `boolean` | `true` if granted scopes don't match requested scopes |
192
+ | `expiresAt` | `string \| null` | Expiry timestamp (if a grant policy TTL was set) |
193
+ | `createdAt` | `string` | When the grant was created |
194
+ | `lastUsedAt` | `string \| null` | When the grant was last used for an API call |
180
195
 
181
196
  #### Create Connect Session
182
197
 
@@ -205,7 +220,7 @@ For CLI tools, scripts, and server-side applications -- opens the browser, waits
205
220
  ```typescript
206
221
  const results = await vault.connect({
207
222
  providers: ["google"],
208
- connectionPolicy: { // optional TTL bounds
223
+ grantPolicy: { // optional TTL bounds
209
224
  maxTtlSeconds: 86400,
210
225
  defaultTtlSeconds: 3600,
211
226
  },
@@ -213,15 +228,15 @@ const results = await vault.connect({
213
228
  openBrowser: true, // set false to print URL instead
214
229
  });
215
230
  for (const result of results) {
216
- console.log(`Connected: ${result.connectionId} (${result.providerId})`);
231
+ console.log(`Connected: ${result.grantId} (${result.providerId})`);
217
232
  console.log(`Account: ${result.accountIdentifier}`);
218
233
  }
219
234
 
220
- // Now use the connectionId with vault.request()
235
+ // Now use the grantId with vault.request()
221
236
  const response = await vault.request(
222
- results[0].connectionId,
237
+ results[0].grantId,
223
238
  HttpMethod.GET,
224
- "https://www.googleapis.com/calendar/v3/calendars/primary/events",
239
+ "https://api.example.com/v1/resource",
225
240
  );
226
241
  ```
227
242
 
@@ -230,10 +245,10 @@ const response = await vault.request(
230
245
  | `providers` | `string[]` | - | Restrict to specific providers |
231
246
  | `timeout` | `number` | `300` | Max seconds to wait for completion |
232
247
  | `pollInterval` | `number` | `2` | Seconds between status checks |
233
- | `connectionPolicy` | `{ maxTtlSeconds?: number; defaultTtlSeconds?: number }` | - | TTL bounds (optional) |
248
+ | `grantPolicy` | `{ maxTtlSeconds?: number; defaultTtlSeconds?: number }` | - | TTL bounds (optional) |
234
249
  | `openBrowser` | `boolean` | `true` | Open browser automatically |
235
250
 
236
- Returns `ConnectResult[]` -one per connected provider. Each has: `connectionId`, `providerId`, `accountIdentifier`, `scopes`, and optionally `connectionPolicy` (if a TTL was set).
251
+ Returns `ConnectResult[]` -one per connected provider. Each has: `grantId`, `providerId`, `accountIdentifier`, `scopes`, and optionally `grantPolicy` (if a TTL was set).
237
252
 
238
253
  Throws `ConnectTimeoutError` if the user doesn't complete in time, `ConnectDeniedError` if the user denies authorization, `ConnectConfigError` if the OAuth app is misconfigured.
239
254
 
@@ -252,13 +267,14 @@ const vault = new AlterVault({
252
267
  });
253
268
 
254
269
  const response = await vault.request(
255
- connectionId,
270
+ grantId,
256
271
  HttpMethod.GET,
257
- "https://www.googleapis.com/calendar/v3/calendars/primary/events",
272
+ "https://api.example.com/v1/resource",
258
273
  {
259
274
  runId: "550e8400-e29b-41d4-a716-446655440000", // auto-generated UUID if omitted
260
275
  threadId: "thread-xyz",
261
276
  toolCallId: "call_abc_123",
277
+ toolId: "read_calendar", // specific tool name
262
278
  },
263
279
  );
264
280
  ```
@@ -287,14 +303,14 @@ const calendarAgent = new AlterVault({
287
303
 
288
304
  // Audit logs and policies are tracked per agent
289
305
  await emailAgent.request(
290
- gmailConnectionId, // from Alter Connect
306
+ gmailGrantId, // from Alter Connect
291
307
  HttpMethod.GET,
292
- "https://gmail.googleapis.com/gmail/v1/users/me/messages",
308
+ "https://api.example.com/v1/messages",
293
309
  );
294
310
  await calendarAgent.request(
295
- calendarConnectionId, // from Alter Connect
311
+ calendarGrantId, // from Alter Connect
296
312
  HttpMethod.GET,
297
- "https://www.googleapis.com/calendar/v3/calendars/primary/events",
313
+ "https://api.example.com/v1/resource",
298
314
  );
299
315
 
300
316
  // Clean up each instance
@@ -319,6 +335,27 @@ const vault = new AlterVault({
319
335
  });
320
336
  ```
321
337
 
338
+ ### End-User Authentication
339
+
340
+ For identity-based grant resolution, authenticate end users via the configured IDP:
341
+
342
+ ```typescript
343
+ // Trigger browser-based IDP login
344
+ const auth = await vault.authenticate({ timeout: 300_000 });
345
+
346
+ console.log(auth.userInfo); // { sub: "user-123", email: "user@example.com", ... }
347
+
348
+ // Subsequent requests automatically use identity resolution
349
+ const response = await vault.request(
350
+ null, // no grantId — resolved via user identity
351
+ HttpMethod.GET,
352
+ "https://api.example.com/endpoint",
353
+ { provider: "<provider>" },
354
+ );
355
+ ```
356
+
357
+ The `authenticate()` method opens the IDP login page in the user's browser, polls for completion, and returns an `AuthResult` with the user's token and profile info. After authentication, `request()` calls with `provider` automatically resolve to the correct grant for that user.
358
+
322
359
  ## Error Handling
323
360
 
324
361
  The SDK provides a typed exception hierarchy so you can handle each failure mode precisely:
@@ -327,10 +364,11 @@ The SDK provides a typed exception hierarchy so you can handle each failure mode
327
364
  AlterSDKError (base)
328
365
  ├── BackendError // Generic backend error
329
366
  │ ├── 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 // 410user disconnected via Wallet (new ID on re-auth)
333
- ├── ConnectionNotFoundError // 404wrong connection_id
367
+ │ │ ├── GrantExpiredError // 403 — grant TTL elapsed
368
+ │ │ ├── CredentialRevokedError // 400 — underlying auth permanently broken (revoked, invalid_grant)
369
+ │ │ ├── GrantRevokedError // 400grant revoked
370
+ │ └── GrantDeletedError // 410user disconnected via Wallet (new ID on re-auth)
371
+ │ ├── GrantNotFoundError // 404 — wrong grant_id
334
372
  │ └── PolicyViolationError // 403 — policy denied (business hours, IP, etc.)
335
373
  ├── ConnectFlowError // Headless connect() failed
336
374
  │ ├── ConnectDeniedError // User clicked Deny
@@ -351,10 +389,11 @@ import {
351
389
  AlterSDKError, // Base error (including validation errors)
352
390
  BackendError, // Generic backend error
353
391
  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)
392
+ GrantDeletedError, // User disconnected via Wallet — new ID on re-auth (410)
393
+ GrantExpiredError, // TTL expired — user must re-authorize (403)
394
+ GrantNotFoundError, // Wrong grant_id — check for typos (404)
395
+ CredentialRevokedError, // Underlying auth permanently broken — revoked/invalid_grant (400)
396
+ GrantRevokedError, // Grant revoked (400)
358
397
  PolicyViolationError, // Policy denied — business hours, IP, etc. (403)
359
398
  ConnectFlowError, // Headless connect() failed (denied, provider error)
360
399
  ConnectDeniedError, // User denied authorization
@@ -368,29 +407,32 @@ import {
368
407
 
369
408
  try {
370
409
  const response = await vault.request(
371
- connectionId,
410
+ grantId,
372
411
  HttpMethod.GET,
373
- "https://www.googleapis.com/calendar/v3/calendars/primary/events",
412
+ "https://api.example.com/v1/resource",
374
413
  );
375
414
  } catch (error) {
376
- // --- Connection unusable — user must re-authorize via Alter Connect ---
377
- if (error instanceof ConnectionExpiredError) {
415
+ // --- Grant unusable — user must re-authorize via Alter Connect ---
416
+ if (error instanceof GrantExpiredError) {
378
417
  // 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
418
+ console.error("Grant expired — prompt user to re-authorize");
419
+ } else if (error instanceof CredentialRevokedError) {
420
+ // Underlying auth permanently broken — user revoked at provider, refresh token
382
421
  // expired, or token refresh permanently failed (invalid_grant)
383
422
  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");
387
- } else if (error instanceof ConnectionNotFoundError) {
388
- // No connection with this ID exists check for typos or stale references
389
- console.error("Connection not found verify your connectionId");
423
+ } else if (error instanceof GrantRevokedError) {
424
+ // Grant itself was revoked
425
+ console.error("Grant revoked — prompt user to re-authorize");
426
+ } else if (error instanceof GrantDeletedError) {
427
+ // User disconnected via Walletre-auth generates a NEW grantId
428
+ console.error("Grant deletedprompt user to re-connect, store the new ID");
429
+ } else if (error instanceof GrantNotFoundError) {
430
+ // No grant with this ID exists — check for typos or stale references
431
+ console.error("Grant not found — verify your grantId");
390
432
 
391
433
  // --- Policy restrictions — may resolve on its own ---
392
434
  } else if (error instanceof PolicyViolationError) {
393
- // Business hours, IP allowlist, or other Cerbos policy denial
435
+ // Business hours, IP allowlist, or other policy denial
394
436
  console.error(`Policy denied: ${error.message} (reason: ${error.policyError})`);
395
437
 
396
438
  // --- Transient / infrastructure errors — safe to retry ---
@@ -400,26 +442,29 @@ try {
400
442
 
401
443
  // --- Provider errors ---
402
444
  } else if (error instanceof ScopeReauthRequiredError) {
403
- console.error(`Scope mismatch on ${error.connectionId} - user needs to re-authorize`);
445
+ console.error(`Scope mismatch on ${error.grantId} - user needs to re-authorize`);
404
446
  // Create a new Connect session so the user can grant updated scopes
405
447
  } else if (error instanceof ProviderAPIError) {
406
448
  console.error(`Provider error ${error.statusCode}: ${error.responseBody}`);
449
+ } else {
450
+ throw error;
407
451
  }
408
452
  }
409
453
  ```
410
454
 
411
- ### Re-authorization and Connection IDs
455
+ ### Re-authorization and Grant IDs
412
456
 
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`.
457
+ When a user re-authorizes through Alter Connect, the **same `grantId` is preserved** in most cases. The existing grant record is updated in place with fresh tokens. You do **not** need to update your stored `grantId`.
414
458
 
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`.
459
+ The exception is `GrantDeletedError` — the user disconnected via the Wallet, so re-authorization creates a new grant with a **new `grantId`**. Store the new ID from the `ConnectResult`.
416
460
 
417
- | Exception | Same `connectionId` after re-auth? |
461
+ | Exception | Same `grantId` after re-auth? |
418
462
  |---|---|
419
- | `ConnectionExpiredError` | Yes |
420
- | `ConnectionRevokedError` | Yes |
421
- | `ConnectionDeletedError` | **No** — new ID generated |
422
- | `ConnectionNotFoundError` | N/A — ID never existed |
463
+ | `GrantExpiredError` | Yes |
464
+ | `CredentialRevokedError` | Yes |
465
+ | `GrantRevokedError` | Yes |
466
+ | `GrantDeletedError` | **No**new ID generated |
467
+ | `GrantNotFoundError` | N/A — ID never existed |
423
468
 
424
469
  ## Resource Management
425
470
 
@@ -442,7 +487,7 @@ try {
442
487
 
443
488
  ## Supported Providers
444
489
 
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.
490
+ The SDK includes type-safe `Provider` enums for all 66 supported providers. Use them for filtering grants or as documentation -- `request()` takes a `grantId` string, not a provider enum.
446
491
 
447
492
  ```typescript
448
493
  import { Provider } from "@alter-ai/alter-sdk";
@@ -467,11 +512,11 @@ Provider.CALENDLY // "calendly"
467
512
  Provider.CLICKUP // "clickup"
468
513
  // ... and 52 more (see Provider enum for full list)
469
514
 
470
- // Usage: filter connections by provider
471
- const result = await vault.listConnections({ providerId: Provider.HUBSPOT });
515
+ // Usage: filter grants by provider
516
+ const result = await vault.listGrants({ providerId: Provider.HUBSPOT });
472
517
 
473
- // Usage: make requests with connectionId
474
- await vault.request(connectionId, HttpMethod.GET, url);
518
+ // Usage: make requests with grantId
519
+ await vault.request(grantId, HttpMethod.GET, url);
475
520
  ```
476
521
 
477
522
  <details>