@alter-ai/alter-sdk 0.6.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 +116 -73
- package/dist/index.cjs +394 -148
- package/dist/index.d.cts +142 -77
- package/dist/index.d.ts +142 -77
- package/dist/index.js +387 -143
- package/package.json +1 -1
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://
|
|
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
|
|
16
|
-
- **
|
|
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,7 +35,7 @@ const vault = new AlterVault({
|
|
|
35
35
|
|
|
36
36
|
// Make API request -token injected automatically, never exposed
|
|
37
37
|
const response = await vault.request(
|
|
38
|
-
"
|
|
38
|
+
"GRANT_ID", // from Alter Connect (see below)
|
|
39
39
|
HttpMethod.GET,
|
|
40
40
|
"https://api.example.com/v1/resource",
|
|
41
41
|
{
|
|
@@ -49,36 +49,36 @@ console.log(events);
|
|
|
49
49
|
await vault.close();
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
### Where does `
|
|
52
|
+
### Where does `grantId` come from?
|
|
53
53
|
|
|
54
|
-
**OAuth
|
|
55
|
-
1. Your **end user** completes OAuth via [Alter Connect](https://docs.
|
|
56
|
-
2. The `onSuccess` callback returns a `
|
|
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.
|
|
62
|
-
2. The portal returns a `
|
|
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
|
|
67
|
-
const result = await vault.
|
|
68
|
-
for (const
|
|
69
|
-
console.log(`${
|
|
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 `
|
|
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
|
-
|
|
81
|
+
grantId,
|
|
82
82
|
HttpMethod.GET,
|
|
83
83
|
"https://api.example.com/v1/resource",
|
|
84
84
|
);
|
|
@@ -88,7 +88,7 @@ const response = await vault.request(
|
|
|
88
88
|
|
|
89
89
|
```typescript
|
|
90
90
|
const response = await vault.request(
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
###
|
|
152
|
+
### Grant Management
|
|
153
153
|
|
|
154
|
-
#### List
|
|
154
|
+
#### List Grants
|
|
155
155
|
|
|
156
|
-
Retrieve OAuth
|
|
156
|
+
Retrieve OAuth grants for your app, optionally filtered by provider:
|
|
157
157
|
|
|
158
158
|
```typescript
|
|
159
|
-
const result = await vault.
|
|
160
|
-
for (const
|
|
161
|
-
console.log(`${
|
|
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
|
|
165
|
+
const googleGrants = await vault.listGrants({
|
|
166
166
|
providerId: "google",
|
|
167
167
|
limit: 10,
|
|
168
168
|
offset: 0,
|
|
169
169
|
});
|
|
170
|
-
console.log(`Total: ${
|
|
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
|
|
176
|
+
| `limit` | `number` | `100` | Max grants to return |
|
|
177
177
|
| `offset` | `number` | `0` | Pagination offset |
|
|
178
178
|
|
|
179
|
-
Returns `
|
|
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
|
-
|
|
223
|
+
grantPolicy: { // optional TTL bounds
|
|
209
224
|
maxTtlSeconds: 86400,
|
|
210
225
|
defaultTtlSeconds: 3600,
|
|
211
226
|
},
|
|
@@ -213,13 +228,13 @@ 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.
|
|
231
|
+
console.log(`Connected: ${result.grantId} (${result.providerId})`);
|
|
217
232
|
console.log(`Account: ${result.accountIdentifier}`);
|
|
218
233
|
}
|
|
219
234
|
|
|
220
|
-
// Now use the
|
|
235
|
+
// Now use the grantId with vault.request()
|
|
221
236
|
const response = await vault.request(
|
|
222
|
-
results[0].
|
|
237
|
+
results[0].grantId,
|
|
223
238
|
HttpMethod.GET,
|
|
224
239
|
"https://api.example.com/v1/resource",
|
|
225
240
|
);
|
|
@@ -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
|
-
| `
|
|
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: `
|
|
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
|
-
|
|
270
|
+
grantId,
|
|
256
271
|
HttpMethod.GET,
|
|
257
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,12 +303,12 @@ const calendarAgent = new AlterVault({
|
|
|
287
303
|
|
|
288
304
|
// Audit logs and policies are tracked per agent
|
|
289
305
|
await emailAgent.request(
|
|
290
|
-
|
|
306
|
+
gmailGrantId, // from Alter Connect
|
|
291
307
|
HttpMethod.GET,
|
|
292
308
|
"https://api.example.com/v1/messages",
|
|
293
309
|
);
|
|
294
310
|
await calendarAgent.request(
|
|
295
|
-
|
|
311
|
+
calendarGrantId, // from Alter Connect
|
|
296
312
|
HttpMethod.GET,
|
|
297
313
|
"https://api.example.com/v1/resource",
|
|
298
314
|
);
|
|
@@ -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
|
-
│ │ ├──
|
|
331
|
-
│ │ ├──
|
|
332
|
-
│ │
|
|
333
|
-
│
|
|
367
|
+
│ │ ├── GrantExpiredError // 403 — grant TTL elapsed
|
|
368
|
+
│ │ ├── CredentialRevokedError // 400 — underlying auth permanently broken (revoked, invalid_grant)
|
|
369
|
+
│ │ ├── GrantRevokedError // 400 — grant revoked
|
|
370
|
+
│ │ └── GrantDeletedError // 410 — user 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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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,25 +407,28 @@ import {
|
|
|
368
407
|
|
|
369
408
|
try {
|
|
370
409
|
const response = await vault.request(
|
|
371
|
-
|
|
410
|
+
grantId,
|
|
372
411
|
HttpMethod.GET,
|
|
373
412
|
"https://api.example.com/v1/resource",
|
|
374
413
|
);
|
|
375
414
|
} catch (error) {
|
|
376
|
-
// ---
|
|
377
|
-
if (error instanceof
|
|
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("
|
|
380
|
-
} else if (error instanceof
|
|
381
|
-
//
|
|
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
|
|
385
|
-
//
|
|
386
|
-
console.error("
|
|
387
|
-
} else if (error instanceof
|
|
388
|
-
//
|
|
389
|
-
console.error("
|
|
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 Wallet — re-auth generates a NEW grantId
|
|
428
|
+
console.error("Grant deleted — prompt 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) {
|
|
@@ -400,7 +442,7 @@ try {
|
|
|
400
442
|
|
|
401
443
|
// --- Provider errors ---
|
|
402
444
|
} else if (error instanceof ScopeReauthRequiredError) {
|
|
403
|
-
console.error(`Scope mismatch on ${error.
|
|
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}`);
|
|
@@ -410,18 +452,19 @@ try {
|
|
|
410
452
|
}
|
|
411
453
|
```
|
|
412
454
|
|
|
413
|
-
### Re-authorization and
|
|
455
|
+
### Re-authorization and Grant IDs
|
|
414
456
|
|
|
415
|
-
When a user re-authorizes through Alter Connect, the **same `
|
|
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`.
|
|
416
458
|
|
|
417
|
-
The exception is `
|
|
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`.
|
|
418
460
|
|
|
419
|
-
| Exception | Same `
|
|
461
|
+
| Exception | Same `grantId` after re-auth? |
|
|
420
462
|
|---|---|
|
|
421
|
-
| `
|
|
422
|
-
| `
|
|
423
|
-
| `
|
|
424
|
-
| `
|
|
463
|
+
| `GrantExpiredError` | Yes |
|
|
464
|
+
| `CredentialRevokedError` | Yes |
|
|
465
|
+
| `GrantRevokedError` | Yes |
|
|
466
|
+
| `GrantDeletedError` | **No** — new ID generated |
|
|
467
|
+
| `GrantNotFoundError` | N/A — ID never existed |
|
|
425
468
|
|
|
426
469
|
## Resource Management
|
|
427
470
|
|
|
@@ -444,7 +487,7 @@ try {
|
|
|
444
487
|
|
|
445
488
|
## Supported Providers
|
|
446
489
|
|
|
447
|
-
The SDK includes type-safe `Provider` enums for all 66 supported providers. Use them for filtering
|
|
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.
|
|
448
491
|
|
|
449
492
|
```typescript
|
|
450
493
|
import { Provider } from "@alter-ai/alter-sdk";
|
|
@@ -469,11 +512,11 @@ Provider.CALENDLY // "calendly"
|
|
|
469
512
|
Provider.CLICKUP // "clickup"
|
|
470
513
|
// ... and 52 more (see Provider enum for full list)
|
|
471
514
|
|
|
472
|
-
// Usage: filter
|
|
473
|
-
const result = await vault.
|
|
515
|
+
// Usage: filter grants by provider
|
|
516
|
+
const result = await vault.listGrants({ providerId: Provider.HUBSPOT });
|
|
474
517
|
|
|
475
|
-
// Usage: make requests with
|
|
476
|
-
await vault.request(
|
|
518
|
+
// Usage: make requests with grantId
|
|
519
|
+
await vault.request(grantId, HttpMethod.GET, url);
|
|
477
520
|
```
|
|
478
521
|
|
|
479
522
|
<details>
|