@alter-ai/alter-sdk 0.4.0 → 0.6.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 +140 -47
- package/dist/index.cjs +408 -107
- package/dist/index.d.cts +232 -34
- package/dist/index.d.ts +232 -34
- package/dist/index.js +399 -104
- package/package.json +1 -1
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)
|
|
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
|
|
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
|
-
- **HMAC Request Signing**: All SDK-to-backend requests are signed
|
|
18
|
-
- **Native Promises**: Built on native `fetch`
|
|
16
|
+
- **HMAC Request Signing**: All SDK-to-backend requests are cryptographically signed for integrity, authenticity, and replay protection
|
|
17
|
+
- **Native Promises**: Built on native `fetch` -no heavy dependencies
|
|
19
18
|
|
|
20
19
|
## Installation
|
|
21
20
|
|
|
@@ -34,11 +33,11 @@ const vault = new AlterVault({
|
|
|
34
33
|
actorIdentifier: "my-agent",
|
|
35
34
|
});
|
|
36
35
|
|
|
37
|
-
// Make API request
|
|
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,
|
|
41
|
-
"https://
|
|
40
|
+
"https://api.example.com/v1/resource",
|
|
42
41
|
{
|
|
43
42
|
queryParams: { maxResults: "10" },
|
|
44
43
|
},
|
|
@@ -52,22 +51,22 @@ await vault.close();
|
|
|
52
51
|
|
|
53
52
|
### Where does `connectionId` come from?
|
|
54
53
|
|
|
55
|
-
**OAuth connections
|
|
56
|
-
1.
|
|
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** (
|
|
62
|
-
1.
|
|
63
|
-
2.
|
|
64
|
-
3. Use the same `vault.request()`
|
|
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:
|
|
68
67
|
const result = await vault.listConnections({ providerId: "google" });
|
|
69
68
|
for (const conn of result.connections) {
|
|
70
|
-
console.log(`${conn.
|
|
69
|
+
console.log(`${conn.connectionId}: ${conn.accountDisplayName}`);
|
|
71
70
|
}
|
|
72
71
|
```
|
|
73
72
|
|
|
@@ -81,7 +80,7 @@ The `request()` method returns the raw `Response` object. The token is injected
|
|
|
81
80
|
const response = await vault.request(
|
|
82
81
|
connectionId,
|
|
83
82
|
HttpMethod.GET,
|
|
84
|
-
"https://
|
|
83
|
+
"https://api.example.com/v1/resource",
|
|
85
84
|
);
|
|
86
85
|
```
|
|
87
86
|
|
|
@@ -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
|
});
|
|
@@ -221,21 +221,21 @@ for (const result of results) {
|
|
|
221
221
|
const response = await vault.request(
|
|
222
222
|
results[0].connectionId,
|
|
223
223
|
HttpMethod.GET,
|
|
224
|
-
"https://
|
|
224
|
+
"https://api.example.com/v1/resource",
|
|
225
225
|
);
|
|
226
226
|
```
|
|
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[]`
|
|
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, `
|
|
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
|
|
|
@@ -254,7 +254,7 @@ const vault = new AlterVault({
|
|
|
254
254
|
const response = await vault.request(
|
|
255
255
|
connectionId,
|
|
256
256
|
HttpMethod.GET,
|
|
257
|
-
"https://
|
|
257
|
+
"https://api.example.com/v1/resource",
|
|
258
258
|
{
|
|
259
259
|
runId: "550e8400-e29b-41d4-a716-446655440000", // auto-generated UUID if omitted
|
|
260
260
|
threadId: "thread-xyz",
|
|
@@ -289,12 +289,12 @@ const calendarAgent = new AlterVault({
|
|
|
289
289
|
await emailAgent.request(
|
|
290
290
|
gmailConnectionId, // from Alter Connect
|
|
291
291
|
HttpMethod.GET,
|
|
292
|
-
"https://
|
|
292
|
+
"https://api.example.com/v1/messages",
|
|
293
293
|
);
|
|
294
294
|
await calendarAgent.request(
|
|
295
295
|
calendarConnectionId, // from Alter Connect
|
|
296
296
|
HttpMethod.GET,
|
|
297
|
-
"https://
|
|
297
|
+
"https://api.example.com/v1/resource",
|
|
298
298
|
);
|
|
299
299
|
|
|
300
300
|
// Clean up each instance
|
|
@@ -321,47 +321,108 @@ 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
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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 {
|
|
343
370
|
const response = await vault.request(
|
|
344
371
|
connectionId,
|
|
345
372
|
HttpMethod.GET,
|
|
346
|
-
"https://
|
|
373
|
+
"https://api.example.com/v1/resource",
|
|
347
374
|
);
|
|
348
375
|
} catch (error) {
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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 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
|
-
|
|
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}`);
|
|
407
|
+
} else {
|
|
408
|
+
throw error;
|
|
361
409
|
}
|
|
362
410
|
}
|
|
363
411
|
```
|
|
364
412
|
|
|
413
|
+
### Re-authorization and Connection IDs
|
|
414
|
+
|
|
415
|
+
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`.
|
|
416
|
+
|
|
417
|
+
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`.
|
|
418
|
+
|
|
419
|
+
| Exception | Same `connectionId` after re-auth? |
|
|
420
|
+
|---|---|
|
|
421
|
+
| `ConnectionExpiredError` | Yes |
|
|
422
|
+
| `ConnectionRevokedError` | Yes |
|
|
423
|
+
| `ConnectionDeletedError` | **No** — new ID generated |
|
|
424
|
+
| `ConnectionNotFoundError` | N/A — ID never existed |
|
|
425
|
+
|
|
365
426
|
## Resource Management
|
|
366
427
|
|
|
367
428
|
```typescript
|
|
@@ -377,19 +438,51 @@ try {
|
|
|
377
438
|
await vault.close(); // Waits for pending audit logs, cleans up timers
|
|
378
439
|
}
|
|
379
440
|
|
|
380
|
-
// close() is idempotent
|
|
441
|
+
// close() is idempotent -safe to call multiple times
|
|
381
442
|
// Calling request() after close() throws AlterSDKError
|
|
382
443
|
```
|
|
383
444
|
|
|
384
445
|
## Supported Providers
|
|
385
446
|
|
|
386
|
-
The SDK
|
|
447
|
+
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
448
|
|
|
388
449
|
```typescript
|
|
389
|
-
|
|
390
|
-
|
|
450
|
+
import { Provider } from "@alter-ai/alter-sdk";
|
|
451
|
+
|
|
452
|
+
// Custom providers (full OAuth implementations)
|
|
453
|
+
Provider.GOOGLE // "google"
|
|
454
|
+
Provider.GITHUB // "github"
|
|
455
|
+
Provider.SLACK // "slack"
|
|
456
|
+
Provider.SENTRY // "sentry"
|
|
457
|
+
|
|
458
|
+
// Config-driven providers (63 total) -- examples:
|
|
459
|
+
Provider.HUBSPOT // "hubspot"
|
|
460
|
+
Provider.SALESFORCE // "salesforce"
|
|
461
|
+
Provider.STRIPE // "stripe"
|
|
462
|
+
Provider.MICROSOFT // "microsoft"
|
|
463
|
+
Provider.DISCORD // "discord"
|
|
464
|
+
Provider.SPOTIFY // "spotify"
|
|
465
|
+
Provider.LINKEDIN // "linkedin"
|
|
466
|
+
Provider.DROPBOX // "dropbox"
|
|
467
|
+
Provider.FIGMA // "figma"
|
|
468
|
+
Provider.CALENDLY // "calendly"
|
|
469
|
+
Provider.CLICKUP // "clickup"
|
|
470
|
+
// ... and 52 more (see Provider enum for full list)
|
|
471
|
+
|
|
472
|
+
// Usage: filter connections by provider
|
|
473
|
+
const result = await vault.listConnections({ providerId: Provider.HUBSPOT });
|
|
474
|
+
|
|
475
|
+
// Usage: make requests with connectionId
|
|
476
|
+
await vault.request(connectionId, HttpMethod.GET, url);
|
|
391
477
|
```
|
|
392
478
|
|
|
479
|
+
<details>
|
|
480
|
+
<summary>All 66 providers</summary>
|
|
481
|
+
|
|
482
|
+
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
|
|
483
|
+
|
|
484
|
+
</details>
|
|
485
|
+
|
|
393
486
|
## Requirements
|
|
394
487
|
|
|
395
488
|
- Node.js 18+ (uses native `fetch`)
|