An open API service indexing awesome lists of open source software.

https://github.com/amigo-ai/amigo-platform-typescript-sdk

Official TypeScript SDK for the Amigo Platform API at api.platform.amigo.ai
https://github.com/amigo-ai/amigo-platform-typescript-sdk

amigo api-client healthcare-ai openapi platform-api sdk typescript

Last synced: about 5 hours ago
JSON representation

Official TypeScript SDK for the Amigo Platform API at api.platform.amigo.ai

Awesome Lists containing this project

README

          


Amigo banner

@amigo-ai/platform-sdk

Official TypeScript SDK for the Amigo Platform API.


Product Docs
·
Developer Guide
·
API Reference
·
Examples
·
API Surface
·
Changelog


npm version
CI
MIT License

Typed from the committed `openapi.json` snapshot, validated on active LTS Node releases (20, 22, and 24), and tested as packaged ESM and CommonJS tarballs before release.

## Platform context

The SDK is the typed client boundary between your runtime and the workspace-scoped Platform API. The API then fronts the platform systems that power agents, actions, calls, analytics, world state, connectors, and webhooks.

![TypeScript SDK platform context](./assets/readme/platform-architecture.svg)

## Documentation

| Need | Best entry point |
| ------------------------------------------- | ---------------------------------------------------------------------------------- |
| Product architecture and deployment context | [docs.amigo.ai](https://docs.amigo.ai/) |
| Tutorials and integration guidance | [Developer Guide](https://docs.amigo.ai/developer-guide/platform-api/platform-sdk) |
| Endpoint-by-endpoint REST reference | [API Reference](https://docs.amigo.ai/api-reference) |
| Repo-local SDK examples | [examples/README.md](./examples/README.md) |
| Generated package surface | [api.md](./api.md) |
| Published release history | [CHANGELOG.md](./CHANGELOG.md) |

### Guides

| Guide | Description |
| ------------------------------------------------------------ | --------------------------------------------------------------- |
| [Build a Custom Patient Form](./docs/guides/build-a-form.md) | Create, deliver, and render patient intake forms using surfaces |

The docs site remains the primary reference. The repo-local examples stay close to the shipped package surface and are typechecked in CI to reduce drift.

## Installation

```bash
npm install @amigo-ai/platform-sdk
```

## Quick start

```typescript
import { AmigoClient } from '@amigo-ai/platform-sdk'

const client = new AmigoClient({
apiKey: 'your-api-key',
workspaceId: 'your-workspace-id',
})

// List agents
const { items: agents } = await client.agents.list({ limit: 10 })
console.log(agents.map((agent) => agent.name))

// Search entities in the world model
const entityResults = await client.world.listEntities({
q: 'Jane Doe',
entity_type: ['patient'],
limit: 5,
})
console.log(entityResults.entities[0]?.display_name)

// Get call analytics for the last 30 days
const stats = await client.analytics.getCalls({ days: 30 })
console.log(stats.total_calls, stats.avg_duration_seconds)
```

## Authentication

### API key (server-to-server)

Pass `apiKey` and `workspaceId` to `AmigoClient`. Best for backend services and scripts.

### Device code flow (CLI and desktop apps)

For interactive apps where users sign in via the browser, use `loginWithDeviceCode`:

```typescript
import {
loginWithDeviceCode,
openBrowser,
formatDeviceCodeInstructions,
TokenManager,
FileTokenStorage,
} from '@amigo-ai/platform-sdk'

const result = await loginWithDeviceCode({
onCode: async (issuance) => {
console.log(formatDeviceCodeInstructions(issuance))
await openBrowser(issuance.verification_uri_complete)
},
onWorkspaceRequired: async (workspaces) => {
// Prompt user to pick a workspace
return workspaces[0].workspace_id
},
})

// Persist credentials across runs
const tokens = new TokenManager({ storage: new FileTokenStorage() })
await tokens.store(result)

// Use the token
const client = new AmigoClient({ apiKey: result.accessToken, workspaceId: result.workspaceId })
```

See [`examples/auth/device-code-login.ts`](./examples/auth/device-code-login.ts) for a complete working example.

### Exchange an API key for a JWT

Use `client.tokens.exchangeApiKey()` to swap a long-lived API key for a
short-lived identity-issued JWT. This is useful when you want to mint a
narrowly scoped, time-bound token from a privileged server (for example to
forward to a browser via a BFF proxy, or to call the platform from a runtime
that can't safely hold the raw API key).

The call posts to `POST /token` on the configured `baseUrl` and is **not**
workspace-scoped — `workspaceId` is still required on the client because it
governs every other resource call on the same instance, but `POST /token`
itself ignores it. The SDK also unconditionally strips the configured
`Authorization` header for this one request and sends the exchange key only
as the `api_key` form field, so the configured client key is never sent over
the wire on the exchange call.

```typescript
import { AmigoClient } from '@amigo-ai/platform-sdk'

const apiKey = process.env.AMIGO_API_KEY
const workspaceId = process.env.AMIGO_WORKSPACE_ID
if (!apiKey || !workspaceId) {
throw new Error('AMIGO_API_KEY and AMIGO_WORKSPACE_ID must be set')
}

const client = new AmigoClient({ apiKey, workspaceId })

const { access_token, expires_in, scope } = await client.tokens.exchangeApiKey({
apiKey,
// Optional: request a narrower scope on the issued JWT. Enforcement is
// server-side — the SDK just forwards the value as a form field.
scope: 'entities:read agents:read',
})

console.log(`Got JWT, expires in ${expires_in}s with scope "${scope}"`)

// Use the JWT in a second client. JWTs are passed as `apiKey` — Bearer auth.
const scopedClient = new AmigoClient({ apiKey: access_token, workspaceId })

const { items: agents } = await scopedClient.agents.list({ limit: 5 })
console.log(agents.map((agent) => agent.name))
```

The response is the standard OAuth-style token payload (`access_token`,
`token_type`, `expires_in`, `scope`, plus optional `session_id` /
`refresh_token` when applicable). The `apiKey` you pass to
`exchangeApiKey()` can be a different key than the one configured on the
client — the configured key is not used to authenticate the exchange
request itself.

### External-user sessions for customer text chat

Use external-user sessions when a customer backend needs to start or continue
text conversations on behalf of one of its own users without giving that user a
workspace membership. The customer backend first obtains a constrained parent
JWT with `external_user_sessions:create`, then mints a short-lived
`external_user` child JWT bound to one workspace, subject, and service.
For a complete setup walkthrough, see
[`docs/guides/external-user-text-conversations.md`](./docs/guides/external-user-text-conversations.md).

Create a parent external-integration credential from an admin/owner backend.
The plaintext `client_secret` is returned only once, from create or rotate:

```typescript
import { AmigoClient } from '@amigo-ai/platform-sdk'

const admin = new AmigoClient({ apiKey: process.env.AMIGO_API_KEY!, workspaceId })

const integration = await admin.externalIntegrations.create({
name: 'customer-portal',
display_name: 'Customer Portal',
description: 'Backend that mints external-user chat sessions',
})

const { client_secret, credential } = await admin.externalIntegrations.createCredential(
integration.id,
{
name: 'production backend',
service_ids: [serviceId],
},
)

console.log(credential.client_id)
// Store client_secret immediately in your secrets manager. It is shown only once.
```

At runtime, the backend sequence is:

1. Exchange `client_credentials` for the parent JWT.
2. Mint an `external_user` session for `external_subject_key`, `subject_type`,
and `service_id`.
3. Create a conversation with the child JWT.
4. Send turns or stream turns with the child JWT.
5. Rotate the refresh token before the access token expires.

```typescript
import { EXTERNAL_USER_SESSION_CREATE_SCOPE } from '@amigo-ai/platform-sdk'

const backend = new AmigoClient({ apiKey: process.env.AMIGO_API_KEY!, workspaceId })

const parent = await backend.tokens.exchangeClientCredentials({
clientId: process.env.AMIGO_EXTERNAL_INTEGRATION_CLIENT_ID!,
clientSecret: process.env.AMIGO_EXTERNAL_INTEGRATION_CLIENT_SECRET!,
scope: EXTERNAL_USER_SESSION_CREATE_SCOPE,
})

const session = await backend.tokens.createExternalUserSession({
parentAccessToken: parent.access_token,
externalSubjectKey: 'customer-user-123',
subjectType: 'user',
serviceId,
// Optional: include only when this subject is already linked to a world entity.
consumerEntityId,
ttlSeconds: 1800,
})

const externalUser = new AmigoClient({
apiKey: session.access_token,
workspaceId,
})

const conversation = await externalUser.conversations.create({ service_id: serviceId })
await externalUser.conversations.createTurn(conversation.id, {
message: 'Hello, I need help scheduling',
})

const refreshed = await backend.tokens.refresh({
refreshToken: session.refresh_token!,
workspaceId,
})
```

`external_user` tokens are intentionally conversation-scoped. They can create,
read, close, send turns, and stream turns for their own service-bound
conversation, but they cannot list workspace conversations or request
`include_tool_calls`. A service or entity mismatch is rejected by the API.
Handle `token_expired` by rotating with `client.tokens.refresh()`. Treat refresh
reuse/theft errors as terminal and restart the session from the parent
credential.

## Configuration

| Option | Type | Required | Description |
| ------------- | -------------- | -------- | -------------------------------------------------------------------- |
| `apiKey` | `string` | Yes | API key or JWT from device code flow (Bearer auth) |
| `workspaceId` | `string` | Yes | Your workspace ID — all resource operations are scoped to this |
| `baseUrl` | `string` | No | Override the API base URL (default: `https://api.platform.amigo.ai`) |
| `retry` | `RetryOptions` | No | Retry configuration for transient failures |
| `maxRetries` | `number` | No | Convenience alias for retry count |
| `timeout` | `number` | No | Default request timeout in milliseconds |
| `headers` | `HeadersInit` | No | Default headers added to every request |
| `hooks` | `ClientHooks` | No | Request/response lifecycle hooks for tracing or logging |
| `fetch` | `typeof fetch` | No | Custom fetch for BFF proxy, cookie forwarding, or test mocking |

### Retry options

```typescript
const client = new AmigoClient({
apiKey: 'your-key',
workspaceId: 'your-workspace-id',
retry: {
maxAttempts: 3, // Total attempts including first. Default: 3
baseDelayMs: 250, // Base delay for exponential backoff. Default: 250
maxDelayMs: 30000, // Cap on delay. Default: 30_000
},
})
```

GET requests are retried on 408, 429, 500, 502, 503, 504. POST requests are only retried on 429 with a `Retry-After` header. Backoff uses full jitter.

### Runtime requirements

The SDK is built around web-standard primitives. Use it in runtimes that provide:

- `fetch`, `Request`, `Response`, `Headers`, `URL`
- `AbortController`
- `TextEncoder` / `TextDecoder`
- `crypto.subtle` for webhook signature verification

CI currently validates active LTS Node releases. Standards-based edge/server runtimes with the same APIs work well with the low-level request wrappers.

## Generated Types

The SDK ships with generated OpenAPI types and re-exports them for direct use:

```typescript
import type { components, operations, paths } from '@amigo-ai/platform-sdk'

type Agent = components['schemas']['AgentResponse']
type ListAgentsQuery = operations['list_agents_v1__workspace_id__agents_get']['parameters']['query']
```

Public builds are generated from the committed [`openapi.json`](./openapi.json) snapshot in this repo so type output stays deterministic across machines and CI runs. When you need to refresh that snapshot, run:

```bash
npm run openapi:sync
```

For a repo-local overview of the exported client surface, see the generated [api.md](./api.md).

## Advanced request control

The normal resource surface supports scoped request overrides, so you can keep the ergonomic API while adding timeout, retry, and header controls:

```typescript
const agents = await client
.withOptions({
timeout: 5_000,
maxRetries: 1,
headers: { 'X-Debug-Trace': 'true' },
})
.agents.list({ limit: 10 })

console.log(agents._request_id)
console.log(agents.lastResponse.statusCode)
console.log(agents.items)
```

You can scope options to a single resource as well:

```typescript
const agent = await client.agents.withOptions({ timeout: 2_000 }).get('agent-id')
```

For lower-level control, use the built-in typed HTTP helpers. Workspace-scoped routes automatically receive your configured `workspaceId`, and the configured value wins if `workspace_id` is provided manually.

```typescript
const result = await client.GET('/v1/{workspace_id}/agents', {
params: { query: { limit: 10 } },
timeout: 5_000,
maxRetries: 1,
headers: { 'X-Debug-Trace': 'true' },
})

console.log(result.requestId)
console.log(result.data.items)
console.log(result.rateLimit.remaining)
```

Available helpers:

- `client.GET(...)`
- `client.POST(...)`
- `client.PUT(...)`
- `client.PATCH(...)`
- `client.DELETE(...)`
- `client.HEAD(...)`
- `client.OPTIONS(...)`
- `client.withOptions(...)`
- `client..withOptions(...)`

### Response metadata

Object responses from resource methods include non-enumerable request metadata:

```typescript
const agent = await client.agents.get('agent-id')

console.log(agent._request_id)
console.log(agent.lastResponse.statusCode)
console.log(agent.lastResponse.rateLimit.remaining)
```

Low-level request helpers return the raw `Response` alongside parsed data:

```typescript
const { data, response, requestId } = await client.GET('/v1/{workspace_id}/agents')

console.log(requestId)
console.log(response.headers.get('content-type'))
console.log(data.items)
```

### Request hooks

Use hooks for logging, tracing, and metrics without wrapping `fetch` yourself:

```typescript
const client = new AmigoClient({
apiKey: 'your-api-key',
workspaceId: 'your-workspace-id',
hooks: {
onRequest({ request, schemaPath }) {
console.log('request', request.method, schemaPath)
},
onResponse({ response, requestId }) {
console.log('response', response.status, requestId)
},
},
})
```

## Resources

### Tokens

Exchange a long-lived API key for a short-lived identity-issued JWT, or mint
external-user session tokens for customer text chat. See
[Exchange an API key for a JWT](#exchange-an-api-key-for-a-jwt) and
[External-user sessions for customer text chat](#external-user-sessions-for-customer-text-chat)
under Authentication for the full walkthroughs.

```typescript
import { EXTERNAL_USER_SESSION_CREATE_SCOPE } from '@amigo-ai/platform-sdk'

const { access_token, expires_in } = await client.tokens.exchangeApiKey({
apiKey: process.env.AMIGO_API_KEY!,
scope: 'entities:read agents:read',
})

const parent = await client.tokens.exchangeClientCredentials({
clientId: process.env.AMIGO_EXTERNAL_INTEGRATION_CLIENT_ID!,
clientSecret: process.env.AMIGO_EXTERNAL_INTEGRATION_CLIENT_SECRET!,
scope: EXTERNAL_USER_SESSION_CREATE_SCOPE,
})
```

### Agents

```typescript
import type { VoiceSessionProvider } from '@amigo-ai/platform-sdk'

// Create an agent
const agent = await client.agents.create({
name: 'Patient Intake Agent',
description: 'Handles inbound scheduling calls',
})

// Create a version (the versioned config object)
const version = await client.agents.createVersion(agent.id, {
name: 'v1',
identity: {
name: 'Alex',
role: 'Scheduling Coordinator',
developed_by: 'Acme Health',
default_spoken_language: 'en',
relationship_to_developer: {
ownership: 'Acme Health',
type: 'assistant',
conversation_visibility: 'public',
thought_visibility: 'private',
},
},
voice_config: {
voice_id: 'voice-abc123',
session_provider: 'inhouse' satisfies VoiceSessionProvider,
},
})

// Get the latest version
const latest = await client.agents.getVersion(agent.id, 'latest')

const { items: agents } = await client.agents.list({ search: 'intake' })
```

### Actions

Actions are reusable agent capabilities (formerly "skills").

```typescript
const action = await client.actions.create({
slug: 'schedule-appointment',
name: 'Schedule Appointment',
description: 'Books appointments in the scheduling system',
input_schema: {
type: 'object',
properties: {
patient_id: { type: 'string' },
appointment_type: { type: 'string' },
},
required: ['patient_id', 'appointment_type'],
},
})

// Test with a sample input
const result = await client.actions.test(action.id, {
input: { patient_id: 'ID-001', appointment_type: 'follow-up' },
})
console.log(result.result, result.duration_ms)
```

### Services

Services wire together an agent + context graph + phone channel.

```typescript
import type { VoiceSessionProvider } from '@amigo-ai/platform-sdk'

const { items: services } = await client.services.list()
const service = await client.services.get('service-id')
console.log(service.agent_name, service.channel_type, service.version_sets)

await client.services.update(service.id, {
voice_config: {
...(service.voice_config ?? {}),
session_provider: 'inhouse' satisfies VoiceSessionProvider,
},
})
```

### World Model

The world model tracks entities (patients, contacts, appointments) and the events that flow through them.

```typescript
// Filter entities with simple list queries
const patients = await client.world.listEntities({
q: 'Jane Doe',
entity_type: ['patient'],
limit: 10,
})
console.log(patients.entities.length)

// Get a single entity
const patient = await client.world.getEntity('entity-id')
console.log(patient.display_name, patient.entity_type)

// Query timeline
const timeline = await client.world.getTimeline('entity-id', { limit: 20 })

// Semantic search over the world model
const results = await client.world.search({
q: 'Jane Doe',
entity_type: 'patient',
limit: 5,
})

// View sync status from connectors
const syncStatus = await client.world.getSyncStatusBySink()
```

### Calls

Calls are read-only — they are created by the voice pipeline.

```typescript
const { items: calls } = await client.calls.list({
direction: 'inbound',
service_id: 'service-id',
})

// Get full detail with transcript and intelligence
const detail = await client.calls.get(calls[0].call_sid)
console.log(detail.intelligence?.summary)
console.log(detail.transcript)

// Analytics benchmarks
const benchmarks = await client.calls.getBenchmarks({ days: 30 })
```

### Text conversations

Use `client.conversations.sendMessage()` for user-first synchronous text turns. Omit
`conversation_id` to start a new durable conversation; pass the returned ID to resume it.

```typescript
const firstTurn = await client.conversations.sendMessage({
service_id: 'service-id',
message: 'Hello, I need help scheduling',
entity_id: 'entity-id',
})

const nextTurn = await client.conversations.sendMessage({
service_id: 'service-id',
conversation_id: firstTurn.conversation_id,
message: 'Tuesday morning works',
})

console.log(nextTurn.messages.map((message) => message.text))
```

For real-time browser clients, build the text-stream URL and use WebSocket
subprotocol auth so the token is not placed in the URL:

```typescript
import { textStreamAuthProtocols } from '@amigo-ai/platform-sdk'

const apiKey = process.env.AMIGO_API_KEY!
const url = client.conversations.textStreamUrl({ serviceId: 'service-id' })
const socket = new WebSocket(url, textStreamAuthProtocols(apiKey))

socket.addEventListener('open', () => {
socket.send(JSON.stringify({ type: 'message', text: 'Hello' }))
})
```

If a browser rejects your API key as a WebSocket subprotocol value, use the
query-token fallback only in trusted contexts. URL tokens can appear in browser
history, server access logs, HTTP proxy logs, and referrer headers:

```typescript
// WARNING: query tokens can be captured by URL logs and browser history.
const url = client.conversations.textStreamUrl({ serviceId: 'service-id', token: apiKey })
const socket = new WebSocket(url)
```

### Real-time event streams

For workspace-wide events (calls, surfaces, pipeline, operators, channels), use the typed SSE consumer:

```typescript
const handle = client.events.subscribeToWorkspace({
onEvent: (event) => {
switch (event.event_type) {
case 'call.started':
console.log('call started:', event.call_sid)
break
case 'pipeline.error':
console.error('pipeline error:', event)
break
}
},
onError: (err) => console.error('terminal:', err),
onReconnect: (attempt) => console.warn(`reconnect #${attempt}`),
})

// Later: handle.unsubscribe(); await handle.done
```

The helper handles automatic reconnect (exp backoff with jitter, server-sent
`retry:` honored), gapless replay via `Last-Event-ID`, and discriminated-union
dispatch. Server emits a structured `error` frame with a stable `code` on
terminal failures — narrow with the typed error guard:

```typescript
import { isWorkspaceEventStreamError } from '@amigo-ai/platform-sdk'

onError: (err) => {
if (isWorkspaceEventStreamError(err)) {
if (err.code === 'too_many_streams') {
// workspace has too many open streams; close another tab
} else if (err.retryable) {
// SDK already retried up to maxReconnects; safe to try again later
}
}
}
```

For per-call voice observation (`agent_transcript_delta`, `latency`, `session_*`,
`participant_*`, etc.), use the WebSocket-based observer helper:

```typescript
const observerHandle = client.observers.subscribe({
callSid: 'CAxxx',
token: bearerToken,
onEvent: (event) => {
switch (event.type) {
case 'agent_transcript_delta':
renderAgentDelta(event.delta)
break
case 'session_end':
showSummary(event)
break
}
},
onError: (err) => console.error('observer terminal:', err.reason, err.closeCode),
})
```

Both helpers are built on the shared `ReconnectingWebSocket` primitive, which
maps platform-specific WebSocket close codes (4001 client error, 4029 rate
limit, 4403 auth, 4100 token expired) to a typed reason taxonomy and applies an
idle watchdog (default 45s) so a half-dead socket forces a reconnect rather
than hanging indefinitely. Compose it directly via `createReconnectingWebSocket`
when you need a managed WebSocket outside these resources.

### Analytics

```typescript
// Dashboard KPIs with period-over-period deltas
const dashboard = await client.analytics.getDashboard({ days: 7 })
console.log(dashboard.call_volume.value, dashboard.call_volume.delta_pct)
console.log(dashboard.avg_quality.value)

// Call volume time series
const calls = await client.analytics.getCalls({ days: 30, interval: '1d' })
console.log(calls.total_calls, calls.calls_by_date)

// Per-agent performance
const { agents } = await client.analytics.getAgents({ period: '7d' })

// Compare two periods
const comparison = await client.analytics.compareCallPeriods({
current_from: '2026-04-01',
current_to: '2026-04-15',
previous_from: '2026-03-15',
previous_to: '2026-03-31',
})
```

### Integrations

```typescript
const { items: integrations } = await client.integrations.list({ enabled: true })

// Test a specific endpoint
const result = await client.integrations.testEndpoint('integration-id', 'geocode', {
textQuery: '123 Main St, Springfield',
})
```

### Data Sources

```typescript
const { items: sources } = await client.dataSources.list()
const source = await client.dataSources.get('source-id')
console.log(source.source_type, source.health_status, source.last_sync_at)
```

### Settings

```typescript
import type { SttProvider, TtsProvider } from '@amigo-ai/platform-sdk'

// Voice
const voice = await client.settings.voice.get()
await client.settings.voice.update({
voice_id: 'new-voice-id',
speed: 1.1,
stt_provider: 'deepgram' satisfies SttProvider,
tts_provider: 'cartesia' satisfies TtsProvider,
})

// Retention
const retention = await client.settings.retention.get()
await client.settings.retention.update({ call_recordings_days: 90 })
```

### Surfaces (Patient Forms)

Surfaces are workspace-scoped form specs for collecting patient data. Create a form, deliver it via SMS or email, and track completion. See the full guide: [Build a Custom Patient Form](./docs/guides/build-a-form.md).

```typescript
// Create a patient intake form
const surface = await client.POST('/v1/{workspace_id}/surfaces', {
body: {
entity_id: patientId,
title: 'New Patient Intake',
channel: 'sms',
fields: [
{ key: 'full_name', label: 'Full Name', field_type: 'text', required: true },
{ key: 'date_of_birth', label: 'Date of Birth', field_type: 'date', required: true },
{
key: 'allergies',
label: 'Allergies',
field_type: 'multiselect',
options: ['Penicillin', 'Sulfa', 'None'],
},
],
},
})
console.log(surface.data.url) // Patient-facing token URL

// Deliver via SMS
await client.POST('/v1/{workspace_id}/surfaces/{surface_id}/deliver', {
params: { path: { surface_id: surface.data.id } },
body: { channel_address: '+15551234567' },
})

// Track completion
const { data: rates } = await client.GET(
'/v1/{workspace_id}/analytics/surfaces/completion-rates',
{},
)
```

Public token routes (`/s/{token}/spec`, `/s/{token}/submit`, etc.) require no API key -- use `openapi-fetch` with the SDK's `paths` type for full type safety on unauthenticated endpoints.

### Billing

```typescript
const dashboard = await client.billing.getDashboard()
const usage = await client.billing.getUsage()
const { items: invoices } = await client.billing.listInvoices()
const pdf = await client.billing.getInvoicePdf('invoice-id')
```

### Operators

```typescript
const { items: operators } = await client.operators.list()
const dashboard = await client.operators.getDashboard()
const queue = await client.operators.getQueue()
const escalations = await client.operators.getActiveEscalations()

// Join/leave calls, switch mode, send guidance
await client.operators.joinCall('operator-id', { call_sid: 'call-sid' })
await client.operators.sendGuidance('operator-id', { text: 'Ask about allergies' })
await client.operators.wrapUp('operator-id', { outcome: 'resolved' })
```

### Triggers (Automations)

```typescript
const trigger = await client.triggers.create({
name: 'Daily outreach',
schedule: '0 9 * * 1-5',
timezone: 'America/New_York',
action_id: 'skill-id',
event_type: 'trigger.scheduled',
input_template: { campaign: 'follow-up' },
})

await client.triggers.fire(trigger.id)
await client.triggers.pause(trigger.id)
await client.triggers.resume(trigger.id)
const runs = await client.triggers.listRuns(trigger.id)
```

### Review Queue

```typescript
const stats = await client.reviewQueue.getStats()
const dashboard = await client.reviewQueue.getDashboard()
const { items } = await client.reviewQueue.list({ status: 'pending' })

// Claim, approve, reject, correct
await client.reviewQueue.claim('item-id')
await client.reviewQueue.approve('item-id', { notes: 'Verified correct' })
await client.reviewQueue.reject('item-id', { reason: 'Data mismatch' })
await client.reviewQueue.batchApprove({ item_ids: ['id1', 'id2'] })
```

### Compliance

```typescript
const hipaa = await client.compliance.getHipaa()
```

### Audit

```typescript
const { items: events } = await client.audit.list({ limit: 50 })
const summary = await client.audit.getSummary()
await client.audit.createExport({ start_date: '2026-01-01', end_date: '2026-03-31' })
```

### Recordings

```typescript
const urls = await client.recordings.getUrls('call-sid')
const metadata = await client.recordings.getMetadata('call-sid')
```

### Functions (UC Functions)

```typescript
const { items: functions } = await client.functions.list()
const fn = await client.functions.get('my-function')
const result = await client.functions.test('my-function', { input: { query: 'test' } })
```

### Workspace Data Queries

```typescript
const query = await client.workspaceDataQueries.create({
name: 'recent_orders',
description: 'Recent orders by status',
sql_template: 'select * from custom.orders where status = :status',
parameters: [{ name: 'status', type: 'string', description: 'Order status' }],
})
const result = await client.workspaceDataQueries.invoke(query.id, {
input: { status: 'open' },
})
```

## Webhook Verification

Use the raw request body when verifying webhook deliveries. Timestamped signatures are replay-protected by default.

```typescript
import { parseWebhookEvent, WebhookVerificationError } from '@amigo-ai/platform-sdk'

const body = await request.text()

try {
const event = await parseWebhookEvent({
payload: body,
signature: request.headers.get('x-amigo-signature') ?? '',
timestamp: request.headers.get('x-amigo-timestamp') ?? undefined,
secret: process.env.AMIGO_WEBHOOK_SECRET!,
})

console.log(event.type, event.data)
} catch (error) {
if (error instanceof WebhookVerificationError) {
console.error('Rejected webhook:', error.message)
} else {
throw error
}
}
```

If your delivery channel only provides a legacy HMAC without a timestamp, the original helper signature still works:

```typescript
import { parseWebhookEvent } from '@amigo-ai/platform-sdk'

const event = await parseWebhookEvent(rawBody, signature, secret)
```

## BFF Proxy (Next.js)

For frontend apps that use a Backend-for-Frontend proxy:

```typescript
const client = new AmigoClient({
apiKey: 'bff-proxy',
workspaceId: 'ws-id',
baseUrl: '/api/platform',
fetch: customFetchWithCookies,
})
```

## Pagination

The SDK now exposes first-class async auto-pagination helpers on collection resources:

```typescript
import { AmigoClient } from '@amigo-ai/platform-sdk'

for await (const agent of client.agents.listAutoPaging({ limit: 100 })) {
console.log(agent.name)
}

for await (const entity of client.world.listEntitiesAutoPaging({ limit: 100 })) {
console.log(entity.display_name)
}
```

For custom pagination flows, the lower-level `paginate(...)` utility remains available.

## Error handling

All SDK errors extend `AmigoError`. Use type guards for specific handling:

```typescript
import {
AmigoClient,
AmigoError,
isNotFoundError,
isRateLimitError,
isAuthenticationError,
} from '@amigo-ai/platform-sdk'

try {
await client.agents.get('agent-id')
} catch (err) {
if (isNotFoundError(err)) {
console.log('Agent not found')
} else if (isRateLimitError(err)) {
console.log('Rate limited, retry after:', err.retryAfter, 'seconds')
} else if (isAuthenticationError(err)) {
console.log('Invalid API key')
} else if (err instanceof AmigoError) {
console.log('API error:', err.message, err.errorCode, err.requestId)
}
}
```

Webhook verification errors are separate from API transport errors and throw `WebhookVerificationError`.

### Error classes

| Class | HTTP Status | Description |
| --------------------- | ----------- | --------------------------------------- |
| `BadRequestError` | 400 | Malformed request |
| `AuthenticationError` | 401 | Invalid or expired API key |
| `PermissionError` | 403 | Insufficient permissions |
| `NotFoundError` | 404 | Resource does not exist |
| `ConflictError` | 409 | Duplicate slug or version conflict |
| `ValidationError` | 422 | Request body validation failure |
| `RateLimitError` | 429 | Too many requests — check `.retryAfter` |
| `ServerError` | 5xx | Server-side error |
| `ConfigurationError` | — | SDK misconfiguration at init time |
| `NetworkError` | — | Fetch/network failure |
| `RequestTimeoutError` | — | Request exceeded the configured timeout |

## CommonJS (CJS) usage

```javascript
const { AmigoClient } = require('@amigo-ai/platform-sdk')
```

## License

MIT