{"id":49034309,"url":"https://github.com/amigo-ai/amigo-platform-typescript-sdk","last_synced_at":"2026-07-03T00:01:18.108Z","repository":{"id":352317725,"uuid":"1211891010","full_name":"amigo-ai/amigo-platform-typescript-sdk","owner":"amigo-ai","description":"Official TypeScript SDK for the Amigo Platform API at api.platform.amigo.ai","archived":false,"fork":false,"pushed_at":"2026-06-26T19:01:20.000Z","size":11959,"stargazers_count":0,"open_issues_count":97,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T19:06:14.949Z","etag":null,"topics":["amigo","api-client","healthcare-ai","openapi","platform-api","sdk","typescript"],"latest_commit_sha":null,"homepage":"https://docs.amigo.ai/developer-guide/platform-api/platform-sdk","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amigo-ai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-15T21:17:52.000Z","updated_at":"2026-06-26T17:14:53.000Z","dependencies_parsed_at":"2026-06-12T04:02:45.486Z","dependency_job_id":null,"html_url":"https://github.com/amigo-ai/amigo-platform-typescript-sdk","commit_stats":null,"previous_names":["amigo-ai/amigo-platform-typescript-sdk"],"tags_count":105,"template":false,"template_full_name":null,"purl":"pkg:github/amigo-ai/amigo-platform-typescript-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amigo-ai%2Famigo-platform-typescript-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amigo-ai%2Famigo-platform-typescript-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amigo-ai%2Famigo-platform-typescript-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amigo-ai%2Famigo-platform-typescript-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amigo-ai","download_url":"https://codeload.github.com/amigo-ai/amigo-platform-typescript-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amigo-ai%2Famigo-platform-typescript-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35067032,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-02T02:00:06.368Z","response_time":173,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["amigo","api-client","healthcare-ai","openapi","platform-api","sdk","typescript"],"created_at":"2026-04-19T10:31:20.483Z","updated_at":"2026-07-03T00:01:18.067Z","avatar_url":"https://github.com/amigo-ai.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./assets/readme/amigo-banner.png\" alt=\"Amigo banner\" width=\"100%\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003e@amigo-ai/platform-sdk\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003eOfficial TypeScript SDK for the \u003ca href=\"https://api.platform.amigo.ai/v1/docs\"\u003eAmigo Platform API\u003c/a\u003e.\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://docs.amigo.ai\"\u003eProduct Docs\u003c/a\u003e\n  ·\n  \u003ca href=\"https://docs.amigo.ai/developer-guide/platform-api/platform-sdk\"\u003eDeveloper Guide\u003c/a\u003e\n  ·\n  \u003ca href=\"https://docs.amigo.ai/api-reference\"\u003eAPI Reference\u003c/a\u003e\n  ·\n  \u003ca href=\"./examples/README.md\"\u003eExamples\u003c/a\u003e\n  ·\n  \u003ca href=\"./api.md\"\u003eAPI Surface\u003c/a\u003e\n  ·\n  \u003ca href=\"./CHANGELOG.md\"\u003eChangelog\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@amigo-ai/platform-sdk\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/@amigo-ai/platform-sdk.svg\" alt=\"npm version\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/amigo-ai/amigo-platform-typescript-sdk/actions/workflows/test.yml\"\u003e\u003cimg src=\"https://github.com/amigo-ai/amigo-platform-typescript-sdk/actions/workflows/test.yml/badge.svg\" alt=\"CI\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-MIT-blue.svg\" alt=\"MIT License\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nTyped 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.\n\n## Platform context\n\nThe 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.\n\n![TypeScript SDK platform context](./assets/readme/platform-architecture.svg)\n\n## Documentation\n\n| Need                                        | Best entry point                                                                   |\n| ------------------------------------------- | ---------------------------------------------------------------------------------- |\n| Product architecture and deployment context | [docs.amigo.ai](https://docs.amigo.ai/)                                            |\n| Tutorials and integration guidance          | [Developer Guide](https://docs.amigo.ai/developer-guide/platform-api/platform-sdk) |\n| Endpoint-by-endpoint REST reference         | [API Reference](https://docs.amigo.ai/api-reference)                               |\n| Repo-local SDK examples                     | [examples/README.md](./examples/README.md)                                         |\n| Generated package surface                   | [api.md](./api.md)                                                                 |\n| Published release history                   | [CHANGELOG.md](./CHANGELOG.md)                                                     |\n\n### Guides\n\n| Guide                                                        | Description                                                     |\n| ------------------------------------------------------------ | --------------------------------------------------------------- |\n| [Build a Custom Patient Form](./docs/guides/build-a-form.md) | Create, deliver, and render patient intake forms using surfaces |\n\nThe 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.\n\n## Installation\n\n```bash\nnpm install @amigo-ai/platform-sdk\n```\n\n## Quick start\n\n```typescript\nimport { AmigoClient } from '@amigo-ai/platform-sdk'\n\nconst client = new AmigoClient({\n  apiKey: 'your-api-key',\n  workspaceId: 'your-workspace-id',\n})\n\n// List agents\nconst { items: agents } = await client.agents.list({ limit: 10 })\nconsole.log(agents.map((agent) =\u003e agent.name))\n\n// Search entities in the world model\nconst entityResults = await client.world.listEntities({\n  q: 'Jane Doe',\n  entity_type: ['patient'],\n  limit: 5,\n})\nconsole.log(entityResults.entities[0]?.display_name)\n\n// Get call analytics for the last 30 days\nconst stats = await client.analytics.getCalls({ days: 30 })\nconsole.log(stats.total_calls, stats.avg_duration_seconds)\n```\n\n## Authentication\n\n### API key (server-to-server)\n\nPass `apiKey` and `workspaceId` to `AmigoClient`. Best for backend services and scripts.\n\n### Device code flow (CLI and desktop apps)\n\nFor interactive apps where users sign in via the browser, use `loginWithDeviceCode`:\n\n```typescript\nimport {\n  loginWithDeviceCode,\n  openBrowser,\n  formatDeviceCodeInstructions,\n  TokenManager,\n  FileTokenStorage,\n} from '@amigo-ai/platform-sdk'\n\nconst result = await loginWithDeviceCode({\n  onCode: async (issuance) =\u003e {\n    console.log(formatDeviceCodeInstructions(issuance))\n    await openBrowser(issuance.verification_uri_complete)\n  },\n  onWorkspaceRequired: async (workspaces) =\u003e {\n    // Prompt user to pick a workspace\n    return workspaces[0].workspace_id\n  },\n})\n\n// Persist credentials across runs\nconst tokens = new TokenManager({ storage: new FileTokenStorage() })\nawait tokens.store(result)\n\n// Use the token\nconst client = new AmigoClient({ apiKey: result.accessToken, workspaceId: result.workspaceId })\n```\n\nSee [`examples/auth/device-code-login.ts`](./examples/auth/device-code-login.ts) for a complete working example.\n\n### Exchange an API key for a JWT\n\nUse `client.tokens.exchangeApiKey()` to swap a long-lived API key for a\nshort-lived identity-issued JWT. This is useful when you want to mint a\nnarrowly scoped, time-bound token from a privileged server (for example to\nforward to a browser via a BFF proxy, or to call the platform from a runtime\nthat can't safely hold the raw API key).\n\nThe call posts to `POST /token` on the configured `baseUrl` and is **not**\nworkspace-scoped — `workspaceId` is still required on the client because it\ngoverns every other resource call on the same instance, but `POST /token`\nitself ignores it. The SDK also unconditionally strips the configured\n`Authorization` header for this one request and sends the exchange key only\nas the `api_key` form field, so the configured client key is never sent over\nthe wire on the exchange call.\n\n```typescript\nimport { AmigoClient } from '@amigo-ai/platform-sdk'\n\nconst apiKey = process.env.AMIGO_API_KEY\nconst workspaceId = process.env.AMIGO_WORKSPACE_ID\nif (!apiKey || !workspaceId) {\n  throw new Error('AMIGO_API_KEY and AMIGO_WORKSPACE_ID must be set')\n}\n\nconst client = new AmigoClient({ apiKey, workspaceId })\n\nconst { access_token, expires_in, scope } = await client.tokens.exchangeApiKey({\n  apiKey,\n  // Optional: request a narrower scope on the issued JWT. Enforcement is\n  // server-side — the SDK just forwards the value as a form field.\n  scope: 'entities:read agents:read',\n})\n\nconsole.log(`Got JWT, expires in ${expires_in}s with scope \"${scope}\"`)\n\n// Use the JWT in a second client. JWTs are passed as `apiKey` — Bearer auth.\nconst scopedClient = new AmigoClient({ apiKey: access_token, workspaceId })\n\nconst { items: agents } = await scopedClient.agents.list({ limit: 5 })\nconsole.log(agents.map((agent) =\u003e agent.name))\n```\n\nThe response is the standard OAuth-style token payload (`access_token`,\n`token_type`, `expires_in`, `scope`, plus optional `session_id` /\n`refresh_token` when applicable). The `apiKey` you pass to\n`exchangeApiKey()` can be a different key than the one configured on the\nclient — the configured key is not used to authenticate the exchange\nrequest itself.\n\n### External-user sessions for customer text chat\n\nUse external-user sessions when a customer backend needs to start or continue\ntext conversations on behalf of one of its own users without giving that user a\nworkspace membership. The customer backend first obtains a constrained parent\nJWT with `external_user_sessions:create`, then mints a short-lived\n`external_user` child JWT bound to one workspace, subject, and service.\nFor a complete setup walkthrough, see\n[`docs/guides/external-user-text-conversations.md`](./docs/guides/external-user-text-conversations.md).\n\nCreate a parent external-integration credential from an admin/owner backend.\nThe plaintext `client_secret` is returned only once, from create or rotate:\n\n```typescript\nimport { AmigoClient } from '@amigo-ai/platform-sdk'\n\nconst admin = new AmigoClient({ apiKey: process.env.AMIGO_API_KEY!, workspaceId })\n\nconst integration = await admin.externalIntegrations.create({\n  name: 'customer-portal',\n  display_name: 'Customer Portal',\n  description: 'Backend that mints external-user chat sessions',\n})\n\nconst { client_secret, credential } = await admin.externalIntegrations.createCredential(\n  integration.id,\n  {\n    name: 'production backend',\n    service_ids: [serviceId],\n  },\n)\n\nconsole.log(credential.client_id)\n// Store client_secret immediately in your secrets manager. It is shown only once.\n```\n\nAt runtime, the backend sequence is:\n\n1. Exchange `client_credentials` for the parent JWT.\n2. Mint an `external_user` session for `external_subject_key`, `subject_type`,\n   and `service_id`.\n3. Create a conversation with the child JWT.\n4. Send turns or stream turns with the child JWT.\n5. Rotate the refresh token before the access token expires.\n\n```typescript\nimport { EXTERNAL_USER_SESSION_CREATE_SCOPE } from '@amigo-ai/platform-sdk'\n\nconst backend = new AmigoClient({ apiKey: process.env.AMIGO_API_KEY!, workspaceId })\n\nconst parent = await backend.tokens.exchangeClientCredentials({\n  clientId: process.env.AMIGO_EXTERNAL_INTEGRATION_CLIENT_ID!,\n  clientSecret: process.env.AMIGO_EXTERNAL_INTEGRATION_CLIENT_SECRET!,\n  scope: EXTERNAL_USER_SESSION_CREATE_SCOPE,\n})\n\nconst session = await backend.tokens.createExternalUserSession({\n  parentAccessToken: parent.access_token,\n  externalSubjectKey: 'customer-user-123',\n  subjectType: 'user',\n  serviceId,\n  // Optional: include only when this subject is already linked to a world entity.\n  consumerEntityId,\n  ttlSeconds: 1800,\n})\n\nconst externalUser = new AmigoClient({\n  apiKey: session.access_token,\n  workspaceId,\n})\n\nconst conversation = await externalUser.conversations.create({ service_id: serviceId })\nawait externalUser.conversations.createTurn(conversation.id, {\n  message: 'Hello, I need help scheduling',\n})\n\nconst refreshed = await backend.tokens.refresh({\n  refreshToken: session.refresh_token!,\n  workspaceId,\n})\n```\n\n`external_user` tokens are intentionally conversation-scoped. They can create,\nread, close, send turns, and stream turns for their own service-bound\nconversation, but they cannot list workspace conversations or request\n`include_tool_calls`. A service or entity mismatch is rejected by the API.\nHandle `token_expired` by rotating with `client.tokens.refresh()`. Treat refresh\nreuse/theft errors as terminal and restart the session from the parent\ncredential.\n\n## Configuration\n\n| Option        | Type           | Required | Description                                                          |\n| ------------- | -------------- | -------- | -------------------------------------------------------------------- |\n| `apiKey`      | `string`       | Yes      | API key or JWT from device code flow (Bearer auth)                   |\n| `workspaceId` | `string`       | Yes      | Your workspace ID — all resource operations are scoped to this       |\n| `baseUrl`     | `string`       | No       | Override the API base URL (default: `https://api.platform.amigo.ai`) |\n| `retry`       | `RetryOptions` | No       | Retry configuration for transient failures                           |\n| `maxRetries`  | `number`       | No       | Convenience alias for retry count                                    |\n| `timeout`     | `number`       | No       | Default request timeout in milliseconds                              |\n| `headers`     | `HeadersInit`  | No       | Default headers added to every request                               |\n| `hooks`       | `ClientHooks`  | No       | Request/response lifecycle hooks for tracing or logging              |\n| `fetch`       | `typeof fetch` | No       | Custom fetch for BFF proxy, cookie forwarding, or test mocking       |\n\n### Retry options\n\n```typescript\nconst client = new AmigoClient({\n  apiKey: 'your-key',\n  workspaceId: 'your-workspace-id',\n  retry: {\n    maxAttempts: 3, // Total attempts including first. Default: 3\n    baseDelayMs: 250, // Base delay for exponential backoff. Default: 250\n    maxDelayMs: 30000, // Cap on delay. Default: 30_000\n  },\n})\n```\n\nGET 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.\n\n### Runtime requirements\n\nThe SDK is built around web-standard primitives. Use it in runtimes that provide:\n\n- `fetch`, `Request`, `Response`, `Headers`, `URL`\n- `AbortController`\n- `TextEncoder` / `TextDecoder`\n- `crypto.subtle` for webhook signature verification\n\nCI currently validates active LTS Node releases. Standards-based edge/server runtimes with the same APIs work well with the low-level request wrappers.\n\n## Generated Types\n\nThe SDK ships with generated OpenAPI types and re-exports them for direct use:\n\n```typescript\nimport type { components, operations, paths } from '@amigo-ai/platform-sdk'\n\ntype Agent = components['schemas']['AgentResponse']\ntype ListAgentsQuery = operations['list_agents_v1__workspace_id__agents_get']['parameters']['query']\n```\n\nPublic 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:\n\n```bash\nnpm run openapi:sync\n```\n\nFor a repo-local overview of the exported client surface, see the generated [api.md](./api.md).\n\n## Advanced request control\n\nThe normal resource surface supports scoped request overrides, so you can keep the ergonomic API while adding timeout, retry, and header controls:\n\n```typescript\nconst agents = await client\n  .withOptions({\n    timeout: 5_000,\n    maxRetries: 1,\n    headers: { 'X-Debug-Trace': 'true' },\n  })\n  .agents.list({ limit: 10 })\n\nconsole.log(agents._request_id)\nconsole.log(agents.lastResponse.statusCode)\nconsole.log(agents.items)\n```\n\nYou can scope options to a single resource as well:\n\n```typescript\nconst agent = await client.agents.withOptions({ timeout: 2_000 }).get('agent-id')\n```\n\nFor 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.\n\n```typescript\nconst result = await client.GET('/v1/{workspace_id}/agents', {\n  params: { query: { limit: 10 } },\n  timeout: 5_000,\n  maxRetries: 1,\n  headers: { 'X-Debug-Trace': 'true' },\n})\n\nconsole.log(result.requestId)\nconsole.log(result.data.items)\nconsole.log(result.rateLimit.remaining)\n```\n\nAvailable helpers:\n\n- `client.GET(...)`\n- `client.POST(...)`\n- `client.PUT(...)`\n- `client.PATCH(...)`\n- `client.DELETE(...)`\n- `client.HEAD(...)`\n- `client.OPTIONS(...)`\n- `client.withOptions(...)`\n- `client.\u003cresource\u003e.withOptions(...)`\n\n### Response metadata\n\nObject responses from resource methods include non-enumerable request metadata:\n\n```typescript\nconst agent = await client.agents.get('agent-id')\n\nconsole.log(agent._request_id)\nconsole.log(agent.lastResponse.statusCode)\nconsole.log(agent.lastResponse.rateLimit.remaining)\n```\n\nLow-level request helpers return the raw `Response` alongside parsed data:\n\n```typescript\nconst { data, response, requestId } = await client.GET('/v1/{workspace_id}/agents')\n\nconsole.log(requestId)\nconsole.log(response.headers.get('content-type'))\nconsole.log(data.items)\n```\n\n### Request hooks\n\nUse hooks for logging, tracing, and metrics without wrapping `fetch` yourself:\n\n```typescript\nconst client = new AmigoClient({\n  apiKey: 'your-api-key',\n  workspaceId: 'your-workspace-id',\n  hooks: {\n    onRequest({ request, schemaPath }) {\n      console.log('request', request.method, schemaPath)\n    },\n    onResponse({ response, requestId }) {\n      console.log('response', response.status, requestId)\n    },\n  },\n})\n```\n\n## Resources\n\n### Tokens\n\nExchange a long-lived API key for a short-lived identity-issued JWT, or mint\nexternal-user session tokens for customer text chat. See\n[Exchange an API key for a JWT](#exchange-an-api-key-for-a-jwt) and\n[External-user sessions for customer text chat](#external-user-sessions-for-customer-text-chat)\nunder Authentication for the full walkthroughs.\n\n```typescript\nimport { EXTERNAL_USER_SESSION_CREATE_SCOPE } from '@amigo-ai/platform-sdk'\n\nconst { access_token, expires_in } = await client.tokens.exchangeApiKey({\n  apiKey: process.env.AMIGO_API_KEY!,\n  scope: 'entities:read agents:read',\n})\n\nconst parent = await client.tokens.exchangeClientCredentials({\n  clientId: process.env.AMIGO_EXTERNAL_INTEGRATION_CLIENT_ID!,\n  clientSecret: process.env.AMIGO_EXTERNAL_INTEGRATION_CLIENT_SECRET!,\n  scope: EXTERNAL_USER_SESSION_CREATE_SCOPE,\n})\n```\n\n### Agents\n\n```typescript\nimport type { VoiceSessionProvider } from '@amigo-ai/platform-sdk'\n\n// Create an agent\nconst agent = await client.agents.create({\n  name: 'Patient Intake Agent',\n  description: 'Handles inbound scheduling calls',\n})\n\n// Create a version (the versioned config object)\nconst version = await client.agents.createVersion(agent.id, {\n  name: 'v1',\n  identity: {\n    name: 'Alex',\n    role: 'Scheduling Coordinator',\n    developed_by: 'Acme Health',\n    default_spoken_language: 'en',\n    relationship_to_developer: {\n      ownership: 'Acme Health',\n      type: 'assistant',\n      conversation_visibility: 'public',\n      thought_visibility: 'private',\n    },\n  },\n  voice_config: {\n    voice_id: 'voice-abc123',\n    session_provider: 'inhouse' satisfies VoiceSessionProvider,\n  },\n})\n\n// Get the latest version\nconst latest = await client.agents.getVersion(agent.id, 'latest')\n\nconst { items: agents } = await client.agents.list({ search: 'intake' })\n```\n\n### Actions\n\nActions are reusable agent capabilities (formerly \"skills\").\n\n```typescript\nconst action = await client.actions.create({\n  slug: 'schedule-appointment',\n  name: 'Schedule Appointment',\n  description: 'Books appointments in the scheduling system',\n  input_schema: {\n    type: 'object',\n    properties: {\n      patient_id: { type: 'string' },\n      appointment_type: { type: 'string' },\n    },\n    required: ['patient_id', 'appointment_type'],\n  },\n})\n\n// Test with a sample input\nconst result = await client.actions.test(action.id, {\n  input: { patient_id: 'ID-001', appointment_type: 'follow-up' },\n})\nconsole.log(result.result, result.duration_ms)\n```\n\n### Services\n\nServices wire together an agent + context graph + phone channel.\n\n```typescript\nimport type { VoiceSessionProvider } from '@amigo-ai/platform-sdk'\n\nconst { items: services } = await client.services.list()\nconst service = await client.services.get('service-id')\nconsole.log(service.agent_name, service.channel_type, service.version_sets)\n\nawait client.services.update(service.id, {\n  voice_config: {\n    ...(service.voice_config ?? {}),\n    session_provider: 'inhouse' satisfies VoiceSessionProvider,\n  },\n})\n```\n\n### World Model\n\nThe world model tracks entities (patients, contacts, appointments) and the events that flow through them.\n\n```typescript\n// Filter entities with simple list queries\nconst patients = await client.world.listEntities({\n  q: 'Jane Doe',\n  entity_type: ['patient'],\n  limit: 10,\n})\nconsole.log(patients.entities.length)\n\n// Get a single entity\nconst patient = await client.world.getEntity('entity-id')\nconsole.log(patient.display_name, patient.entity_type)\n\n// Query timeline\nconst timeline = await client.world.getTimeline('entity-id', { limit: 20 })\n\n// Semantic search over the world model\nconst results = await client.world.search({\n  q: 'Jane Doe',\n  entity_type: 'patient',\n  limit: 5,\n})\n\n// View sync status from connectors\nconst syncStatus = await client.world.getSyncStatusBySink()\n```\n\n### Calls\n\nCalls are read-only — they are created by the voice pipeline.\n\n```typescript\nconst { items: calls } = await client.calls.list({\n  direction: 'inbound',\n  service_id: 'service-id',\n})\n\n// Get full detail with transcript and intelligence\nconst detail = await client.calls.get(calls[0].call_sid)\nconsole.log(detail.intelligence?.summary)\nconsole.log(detail.transcript)\n\n// Analytics benchmarks\nconst benchmarks = await client.calls.getBenchmarks({ days: 30 })\n```\n\n### Text conversations\n\nUse `client.conversations.sendMessage()` for user-first synchronous text turns. Omit\n`conversation_id` to start a new durable conversation; pass the returned ID to resume it.\n\n```typescript\nconst firstTurn = await client.conversations.sendMessage({\n  service_id: 'service-id',\n  message: 'Hello, I need help scheduling',\n  entity_id: 'entity-id',\n})\n\nconst nextTurn = await client.conversations.sendMessage({\n  service_id: 'service-id',\n  conversation_id: firstTurn.conversation_id,\n  message: 'Tuesday morning works',\n})\n\nconsole.log(nextTurn.messages.map((message) =\u003e message.text))\n```\n\nFor real-time browser clients, build the text-stream URL and use WebSocket\nsubprotocol auth so the token is not placed in the URL:\n\n```typescript\nimport { textStreamAuthProtocols } from '@amigo-ai/platform-sdk'\n\nconst apiKey = process.env.AMIGO_API_KEY!\nconst url = client.conversations.textStreamUrl({ serviceId: 'service-id' })\nconst socket = new WebSocket(url, textStreamAuthProtocols(apiKey))\n\nsocket.addEventListener('open', () =\u003e {\n  socket.send(JSON.stringify({ type: 'message', text: 'Hello' }))\n})\n```\n\nIf a browser rejects your API key as a WebSocket subprotocol value, use the\nquery-token fallback only in trusted contexts. URL tokens can appear in browser\nhistory, server access logs, HTTP proxy logs, and referrer headers:\n\n```typescript\n// WARNING: query tokens can be captured by URL logs and browser history.\nconst url = client.conversations.textStreamUrl({ serviceId: 'service-id', token: apiKey })\nconst socket = new WebSocket(url)\n```\n\n### Real-time event streams\n\nFor workspace-wide events (calls, surfaces, pipeline, operators, channels), use the typed SSE consumer:\n\n```typescript\nconst handle = client.events.subscribeToWorkspace({\n  onEvent: (event) =\u003e {\n    switch (event.event_type) {\n      case 'call.started':\n        console.log('call started:', event.call_sid)\n        break\n      case 'pipeline.error':\n        console.error('pipeline error:', event)\n        break\n    }\n  },\n  onError: (err) =\u003e console.error('terminal:', err),\n  onReconnect: (attempt) =\u003e console.warn(`reconnect #${attempt}`),\n})\n\n// Later: handle.unsubscribe(); await handle.done\n```\n\nThe helper handles automatic reconnect (exp backoff with jitter, server-sent\n`retry:` honored), gapless replay via `Last-Event-ID`, and discriminated-union\ndispatch. Server emits a structured `error` frame with a stable `code` on\nterminal failures — narrow with the typed error guard:\n\n```typescript\nimport { isWorkspaceEventStreamError } from '@amigo-ai/platform-sdk'\n\nonError: (err) =\u003e {\n  if (isWorkspaceEventStreamError(err)) {\n    if (err.code === 'too_many_streams') {\n      // workspace has too many open streams; close another tab\n    } else if (err.retryable) {\n      // SDK already retried up to maxReconnects; safe to try again later\n    }\n  }\n}\n```\n\nFor per-call voice observation (`agent_transcript_delta`, `latency`, `session_*`,\n`participant_*`, etc.), use the WebSocket-based observer helper:\n\n```typescript\nconst observerHandle = client.observers.subscribe({\n  callSid: 'CAxxx',\n  token: bearerToken,\n  onEvent: (event) =\u003e {\n    switch (event.type) {\n      case 'agent_transcript_delta':\n        renderAgentDelta(event.delta)\n        break\n      case 'session_end':\n        showSummary(event)\n        break\n    }\n  },\n  onError: (err) =\u003e console.error('observer terminal:', err.reason, err.closeCode),\n})\n```\n\nBoth helpers are built on the shared `ReconnectingWebSocket` primitive, which\nmaps platform-specific WebSocket close codes (4001 client error, 4029 rate\nlimit, 4403 auth, 4100 token expired) to a typed reason taxonomy and applies an\nidle watchdog (default 45s) so a half-dead socket forces a reconnect rather\nthan hanging indefinitely. Compose it directly via `createReconnectingWebSocket`\nwhen you need a managed WebSocket outside these resources.\n\n### Analytics\n\n```typescript\n// Dashboard KPIs with period-over-period deltas\nconst dashboard = await client.analytics.getDashboard({ days: 7 })\nconsole.log(dashboard.call_volume.value, dashboard.call_volume.delta_pct)\nconsole.log(dashboard.avg_quality.value)\n\n// Call volume time series\nconst calls = await client.analytics.getCalls({ days: 30, interval: '1d' })\nconsole.log(calls.total_calls, calls.calls_by_date)\n\n// Per-agent performance\nconst { agents } = await client.analytics.getAgents({ period: '7d' })\n\n// Compare two periods\nconst comparison = await client.analytics.compareCallPeriods({\n  current_from: '2026-04-01',\n  current_to: '2026-04-15',\n  previous_from: '2026-03-15',\n  previous_to: '2026-03-31',\n})\n```\n\n### Integrations\n\n```typescript\nconst { items: integrations } = await client.integrations.list({ enabled: true })\n\n// Test a specific endpoint\nconst result = await client.integrations.testEndpoint('integration-id', 'geocode', {\n  textQuery: '123 Main St, Springfield',\n})\n```\n\n### Data Sources\n\n```typescript\nconst { items: sources } = await client.dataSources.list()\nconst source = await client.dataSources.get('source-id')\nconsole.log(source.source_type, source.health_status, source.last_sync_at)\n```\n\n### Settings\n\n```typescript\nimport type { SttProvider, TtsProvider } from '@amigo-ai/platform-sdk'\n\n// Voice\nconst voice = await client.settings.voice.get()\nawait client.settings.voice.update({\n  voice_id: 'new-voice-id',\n  speed: 1.1,\n  stt_provider: 'deepgram' satisfies SttProvider,\n  tts_provider: 'cartesia' satisfies TtsProvider,\n})\n\n// Retention\nconst retention = await client.settings.retention.get()\nawait client.settings.retention.update({ call_recordings_days: 90 })\n```\n\n### Surfaces (Patient Forms)\n\nSurfaces 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).\n\n```typescript\n// Create a patient intake form\nconst surface = await client.POST('/v1/{workspace_id}/surfaces', {\n  body: {\n    entity_id: patientId,\n    title: 'New Patient Intake',\n    channel: 'sms',\n    fields: [\n      { key: 'full_name', label: 'Full Name', field_type: 'text', required: true },\n      { key: 'date_of_birth', label: 'Date of Birth', field_type: 'date', required: true },\n      {\n        key: 'allergies',\n        label: 'Allergies',\n        field_type: 'multiselect',\n        options: ['Penicillin', 'Sulfa', 'None'],\n      },\n    ],\n  },\n})\nconsole.log(surface.data.url) // Patient-facing token URL\n\n// Deliver via SMS\nawait client.POST('/v1/{workspace_id}/surfaces/{surface_id}/deliver', {\n  params: { path: { surface_id: surface.data.id } },\n  body: { channel_address: '+15551234567' },\n})\n\n// Track completion\nconst { data: rates } = await client.GET(\n  '/v1/{workspace_id}/analytics/surfaces/completion-rates',\n  {},\n)\n```\n\nPublic 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.\n\n### Billing\n\n```typescript\nconst dashboard = await client.billing.getDashboard()\nconst usage = await client.billing.getUsage()\nconst { items: invoices } = await client.billing.listInvoices()\nconst pdf = await client.billing.getInvoicePdf('invoice-id')\n```\n\n### Operators\n\n```typescript\nconst { items: operators } = await client.operators.list()\nconst dashboard = await client.operators.getDashboard()\nconst queue = await client.operators.getQueue()\nconst escalations = await client.operators.getActiveEscalations()\n\n// Join/leave calls, switch mode, send guidance\nawait client.operators.joinCall('operator-id', { call_sid: 'call-sid' })\nawait client.operators.sendGuidance('operator-id', { text: 'Ask about allergies' })\nawait client.operators.wrapUp('operator-id', { outcome: 'resolved' })\n```\n\n### Triggers (Automations)\n\n```typescript\nconst trigger = await client.triggers.create({\n  name: 'Daily outreach',\n  schedule: '0 9 * * 1-5',\n  timezone: 'America/New_York',\n  action_id: 'skill-id',\n  event_type: 'trigger.scheduled',\n  input_template: { campaign: 'follow-up' },\n})\n\nawait client.triggers.fire(trigger.id)\nawait client.triggers.pause(trigger.id)\nawait client.triggers.resume(trigger.id)\nconst runs = await client.triggers.listRuns(trigger.id)\n```\n\n### Review Queue\n\n```typescript\nconst stats = await client.reviewQueue.getStats()\nconst dashboard = await client.reviewQueue.getDashboard()\nconst { items } = await client.reviewQueue.list({ status: 'pending' })\n\n// Claim, approve, reject, correct\nawait client.reviewQueue.claim('item-id')\nawait client.reviewQueue.approve('item-id', { notes: 'Verified correct' })\nawait client.reviewQueue.reject('item-id', { reason: 'Data mismatch' })\nawait client.reviewQueue.batchApprove({ item_ids: ['id1', 'id2'] })\n```\n\n### Compliance\n\n```typescript\nconst hipaa = await client.compliance.getHipaa()\n```\n\n### Audit\n\n```typescript\nconst { items: events } = await client.audit.list({ limit: 50 })\nconst summary = await client.audit.getSummary()\nawait client.audit.createExport({ start_date: '2026-01-01', end_date: '2026-03-31' })\n```\n\n### Recordings\n\n```typescript\nconst urls = await client.recordings.getUrls('call-sid')\nconst metadata = await client.recordings.getMetadata('call-sid')\n```\n\n### Functions (UC Functions)\n\n```typescript\nconst { items: functions } = await client.functions.list()\nconst fn = await client.functions.get('my-function')\nconst result = await client.functions.test('my-function', { input: { query: 'test' } })\n```\n\n### Workspace Data Queries\n\n```typescript\nconst query = await client.workspaceDataQueries.create({\n  name: 'recent_orders',\n  description: 'Recent orders by status',\n  sql_template: 'select * from custom.orders where status = :status',\n  parameters: [{ name: 'status', type: 'string', description: 'Order status' }],\n})\nconst result = await client.workspaceDataQueries.invoke(query.id, {\n  input: { status: 'open' },\n})\n```\n\n## Webhook Verification\n\nUse the raw request body when verifying webhook deliveries. Timestamped signatures are replay-protected by default.\n\n```typescript\nimport { parseWebhookEvent, WebhookVerificationError } from '@amigo-ai/platform-sdk'\n\nconst body = await request.text()\n\ntry {\n  const event = await parseWebhookEvent({\n    payload: body,\n    signature: request.headers.get('x-amigo-signature') ?? '',\n    timestamp: request.headers.get('x-amigo-timestamp') ?? undefined,\n    secret: process.env.AMIGO_WEBHOOK_SECRET!,\n  })\n\n  console.log(event.type, event.data)\n} catch (error) {\n  if (error instanceof WebhookVerificationError) {\n    console.error('Rejected webhook:', error.message)\n  } else {\n    throw error\n  }\n}\n```\n\nIf your delivery channel only provides a legacy HMAC without a timestamp, the original helper signature still works:\n\n```typescript\nimport { parseWebhookEvent } from '@amigo-ai/platform-sdk'\n\nconst event = await parseWebhookEvent(rawBody, signature, secret)\n```\n\n## BFF Proxy (Next.js)\n\nFor frontend apps that use a Backend-for-Frontend proxy:\n\n```typescript\nconst client = new AmigoClient({\n  apiKey: 'bff-proxy',\n  workspaceId: 'ws-id',\n  baseUrl: '/api/platform',\n  fetch: customFetchWithCookies,\n})\n```\n\n## Pagination\n\nThe SDK now exposes first-class async auto-pagination helpers on collection resources:\n\n```typescript\nimport { AmigoClient } from '@amigo-ai/platform-sdk'\n\nfor await (const agent of client.agents.listAutoPaging({ limit: 100 })) {\n  console.log(agent.name)\n}\n\nfor await (const entity of client.world.listEntitiesAutoPaging({ limit: 100 })) {\n  console.log(entity.display_name)\n}\n```\n\nFor custom pagination flows, the lower-level `paginate(...)` utility remains available.\n\n## Error handling\n\nAll SDK errors extend `AmigoError`. Use type guards for specific handling:\n\n```typescript\nimport {\n  AmigoClient,\n  AmigoError,\n  isNotFoundError,\n  isRateLimitError,\n  isAuthenticationError,\n} from '@amigo-ai/platform-sdk'\n\ntry {\n  await client.agents.get('agent-id')\n} catch (err) {\n  if (isNotFoundError(err)) {\n    console.log('Agent not found')\n  } else if (isRateLimitError(err)) {\n    console.log('Rate limited, retry after:', err.retryAfter, 'seconds')\n  } else if (isAuthenticationError(err)) {\n    console.log('Invalid API key')\n  } else if (err instanceof AmigoError) {\n    console.log('API error:', err.message, err.errorCode, err.requestId)\n  }\n}\n```\n\nWebhook verification errors are separate from API transport errors and throw `WebhookVerificationError`.\n\n### Error classes\n\n| Class                 | HTTP Status | Description                             |\n| --------------------- | ----------- | --------------------------------------- |\n| `BadRequestError`     | 400         | Malformed request                       |\n| `AuthenticationError` | 401         | Invalid or expired API key              |\n| `PermissionError`     | 403         | Insufficient permissions                |\n| `NotFoundError`       | 404         | Resource does not exist                 |\n| `ConflictError`       | 409         | Duplicate slug or version conflict      |\n| `ValidationError`     | 422         | Request body validation failure         |\n| `RateLimitError`      | 429         | Too many requests — check `.retryAfter` |\n| `ServerError`         | 5xx         | Server-side error                       |\n| `ConfigurationError`  | —           | SDK misconfiguration at init time       |\n| `NetworkError`        | —           | Fetch/network failure                   |\n| `RequestTimeoutError` | —           | Request exceeded the configured timeout |\n\n## CommonJS (CJS) usage\n\n```javascript\nconst { AmigoClient } = require('@amigo-ai/platform-sdk')\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famigo-ai%2Famigo-platform-typescript-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famigo-ai%2Famigo-platform-typescript-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famigo-ai%2Famigo-platform-typescript-sdk/lists"}