Skip to content

amigo-ai/amigo-platform-typescript-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

288 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

Documentation

Need Best entry point
Product architecture and deployment context docs.amigo.ai
Tutorials and integration guidance Developer Guide
Endpoint-by-endpoint REST reference API Reference
Repo-local SDK examples examples/README.md
Generated package surface api.md
Published release history CHANGELOG.md

Guides

Guide Description
Build a Custom Patient Form 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

npm install @amigo-ai/platform-sdk

Quick start

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:

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 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.

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.

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

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.
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

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:

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 snapshot in this repo so type output stays deterministic across machines and CI runs. When you need to refresh that snapshot, run:

npm run openapi:sync

For a repo-local overview of the exported client surface, see the generated 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:

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:

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.

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.<resource>.withOptions(...)

Response metadata

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

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:

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:

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 and External-user sessions for customer text chat under Authentication for the full walkthroughs.

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

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").

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.

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.

// 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.

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.

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:

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:

// 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:

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:

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:

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

// 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

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

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

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.

// 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

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

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)

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

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

const hipaa = await client.compliance.getHipaa()

Audit

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

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

Functions (UC Functions)

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

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.

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:

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:

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:

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:

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

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

License

MIT

About

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

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors