/** SDK configuration options. */ export interface AgentCallConfig { /** API key starting with `ac_live_`. */ apiKey: string; /** API base URL. Defaults to `https://api.agentcall.co`. */ baseUrl?: string; /** Request timeout in milliseconds. Defaults to 30000. */ timeout?: number; } /** * Saved outbound AI agent on a phone number. When set, the dashboard's * Place AI call dialog hydrates from this blob so the customer doesn't * retype the prompt + voice + language for every outbound call from the * same number. Independent of the inbound AI receptionist config. * * Read via `client.numbers.getOutboundDefaults(numberId)`, written via * `client.numbers.setOutboundDefaults(numberId, options)`, cleared via * `client.numbers.clearOutboundDefaults(numberId)`. Also surfaced on * `PhoneNumber.outbound` on list/get responses so a single * `client.numbers.list()` tells you which numbers have a saved agent. * * Pro plan only — outbound AI itself is Pro, so Free users can't save * a blob they can never use. */ export interface OutboundDefaults { /** Instructions for the outbound AI voice agent. Required. */ systemPrompt: string; /** Default first message when the recipient picks up. */ firstMessage?: string; /** Default OpenAI Realtime voice for outbound calls from this number. */ voice?: string; /** * Default spoken language. `'auto'` matches the recipient's language; * specific ISO-639-1 codes pin the AI to that language. */ language?: 'auto' | 'en' | 'es' | 'fr' | 'de' | 'it' | 'pt' | 'nl' | 'ja' | 'ko' | 'zh' | 'hi' | 'ar' | 'ru' | 'id' | 'tr' | 'pl' | 'uk' | 'vi' | 'ta' | 'ms' | 'ro' | 'el' | 'cs' | 'sv' | 'hu' | 'da' | 'fi' | 'no' | 'sk' | 'hr' | 'bg'; /** Default max call duration in seconds (10-3600). */ maxDurationSecs?: number; /** Default to recording outbound calls. Adds $0.01/min on top of voice rate. */ record?: boolean; /** * Optional short friendly label for the saved agent (e.g. `"appointment-booker"`). * Shown on the dashboard numbers row as the agent badge so customers can * scan the list and tell which numbers have which persona. */ templateId?: string; /** * Server-set ISO 8601 timestamp of the last save. Returned by GET and POST * responses; never set by the caller. */ updatedAt?: string; } /** Response from `client.numbers.getOutboundDefaults()` and `client.numbers.setOutboundDefaults()`. */ export interface OutboundDefaultsResponse { numberId: string; number: string; outboundDefaults: OutboundDefaults | null; } /** * Recurrence for a recurring proactive ScheduledMessage (digests). Interpreted * in the schedule's `timezone`. `dayOfWeek` (0=Sunday..6=Saturday) is required * for `weekly` and ignored for `daily`. */ export interface ScheduleRecurrence { frequency: 'daily' | 'weekly'; /** Hour of day in the schedule's timezone, 0-23. */ hour: number; /** Minute of hour, 0-59. */ minute: number; /** 0=Sunday..6=Saturday. Required for `weekly`. */ dayOfWeek?: number; } /** * A proactive scheduled message — the agent texting FIRST. One-shot reminders * (`fireAt`) or recurring digests (`recurrence`). Read via * `client.schedules.list()`, created via `client.schedules.create()`, removed * via `client.schedules.cancel()`. */ export interface Schedule { id: string; numberId: string; kind: 'reminder' | 'digest' | 'custom'; /** Absolute UTC ISO 8601 fire time for one-shot schedules; null when recurring. */ fireAt: string | null; /** Recurrence spec for recurring schedules; null when one-shot. */ recurrence: ScheduleRecurrence | null; timezone: string; /** ISO 8601 of the next time this schedule will fire. */ nextRunAt: string; /** ISO 8601 of the last time it fired, or null if it hasn't yet. */ lastFiredAt: string | null; contactPhone: string; /** Verbatim message body (placeholders filled from `payload`); null in agent mode. */ template: string | null; /** Instruction for the agent to compose the message; null in template mode. */ promptHint: string | null; payload: Record | null; status: 'active' | 'paused' | 'done' | 'canceled'; dedupeKey: string | null; createdAt: string; } /** Options for `client.schedules.create()`. */ export interface CreateScheduleOptions { /** Recipient phone number in E.164 format. */ contactPhone: string; /** Schedule kind. Defaults to `custom`. */ kind?: 'reminder' | 'digest' | 'custom'; /** One-shot fire time (ISO 8601, must be in the future). Provide this OR `recurrence`. */ fireAt?: string; /** Recurring spec. Provide this OR `fireAt`. */ recurrence?: ScheduleRecurrence; /** IANA timezone for the recurrence. Defaults to `America/Chicago`. */ timezone?: string; /** Verbatim message (with `{{placeholders}}` filled from `payload`). Provide this OR `promptHint`. */ template?: string; /** Instruction for the agent to compose the message with memory. Provide this OR `template`. */ promptHint?: string; /** Structured data for template fill + dedup (e.g. `{ service, time }`). Never sent raw. */ payload?: Record; /** Idempotency key scoped to the number — a re-create with the same key returns 409. */ dedupeKey?: string; } /** A provisioned phone number. */ export interface PhoneNumber { /** Unique number ID (e.g. `num_abc123`). */ id: string; /** Phone number in E.164 format (e.g. `+12125551234`). */ number: string; /** ISO 3166-1 alpha-2 country code. */ country: string; /** Number type: `local`, `tollfree`, or `mobile`. */ type: string; /** Optional human-readable label. */ label: string | null; /** Current status: `active` or `released`. */ status: string; /** Monthly cost in USD. */ monthlyRate: number; /** ISO 8601 timestamp of when the number was provisioned. */ provisionedAt: string; /** * Vendor-neutral carrier tier identifier. `'primary'` numbers support * inbound AI voice today; `'fallback'` numbers are routed through the * secondary carrier pool and don't yet support inbound AI. Optional on * the type because older API versions did not return it. */ carrierTier?: 'primary' | 'fallback'; /** * Saved outbound AI agent (systemPrompt + voice + language + ...), or null * when none configured. The dashboard hydrates the Place AI call dialog * from this blob. Optional on the type because older API versions did not * return it. */ outbound?: OutboundDefaults | null; } /** * Voice billing mode for a phone number. * - `'managed'` (default): AgentCall handles voice with a shared key. * Calls bill at $0.40/min on Pro. * - `'byok'`: customer brings their own OpenAI key for this number. * Calls bill at $0.10/min on Pro and OpenAI is paid directly by the * customer for the underlying voice usage. */ export type VoiceMode = 'managed' | 'byok'; /** * Response from `client.numbers.configureInboundAi()` and `client.numbers.getInboundConfig()`. * * BYOK status lives inside `config`, not at the top level: * - `config.voiceMode` is `'managed'` (default) or `'byok'` * - `config.hasByokKey` is `true` when a key is saved on this number * - `config.byokOpenaiApiKeyPreview` is a redacted preview (e.g. `'sk-...AbCd'`) when `voiceMode='byok'` * - `config.byokConfiguredAt` is the ISO 8601 timestamp the key was saved * * Earlier SDK versions declared these fields at the top level, but the API * has always returned them inside `config`. Read them there. */ export interface InboundAiConfigResponse { numberId: string; number?: string; carrierTier?: 'primary' | 'fallback'; /** * The applied inbound config. Includes the configured fields (mode, systemPrompt, * voice, firstMessage, maxDurationSecs, notify, record, contextWebhook) plus BYOK * status fields (voiceMode, hasByokKey, byokOpenaiApiKeyPreview, byokConfiguredAt). * The `contextWebhook.signingSecret` you set is write-only; responses expose * `contextWebhook.hasSigningSecret` (boolean) in its place. */ config: Record; } /** Response from `client.numbers.setByokKey()`. */ export interface SetByokKeyResult { ok: boolean; numberId: string; voiceMode: 'byok'; /** Redacted preview of the saved key (e.g. `sk-...AbCd`). The full key is never returned. */ keyPreview: string; /** ISO 8601 timestamp when the key was saved. */ configuredAt: string; } /** Response from `client.numbers.disableByok()`. */ export interface DisableByokResult { ok: boolean; numberId: string; voiceMode: 'managed'; } /** * A voice in the Premium Voice library, as returned by * `client.calls.premiumVoices()`. The `id` is the value you pass to * `client.numbers.setPremiumVoice(numberId, id)`. */ export interface PremiumVoice { /** Voice id — pass this to `setPremiumVoice`. */ id: string; /** Neutral human display name (e.g. `Sofia`). */ name: string; /** One-line description of the voice's character. */ description: string; /** `female` or `male`. */ gender: 'female' | 'male'; /** Accent descriptor (e.g. `American`, `British`). */ accent: string; /** URL to a sample MP3 of the voice. */ sampleUrl: string; } /** Response from `client.calls.premiumVoices()` — the Premium Voice catalog. */ export interface PremiumVoicesResponse { voices: PremiumVoice[]; /** Pricing strings, e.g. `{ premiumVoice: '$0.59/minute' }`. */ pricing: Record; } /** * Customer-facing view of a number's Premium Voice state. Returned inside * `SetPremiumVoiceResult`. `voiceName` is null for an unknown/legacy id. */ export interface PremiumVoiceState { /** True when Premium Voice is enabled on the number. */ enabled: boolean; /** The selected premium voice id, or null when disabled. */ voiceId: string | null; /** Display name of the selected voice, or null. */ voiceName: string | null; } /** * Response from `client.numbers.setPremiumVoice()` and * `client.numbers.disablePremiumVoice()`. */ export interface SetPremiumVoiceResult { ok: boolean; numberId: string; premiumVoice: PremiumVoiceState; } /** Options for provisioning a new phone number. */ export interface ProvisionOptions { /** Country code (e.g. `US`, `CA`). Defaults to `US`. */ country?: string; /** Number type to provision. Free plan: `local` only. Pro: all types. */ type?: 'local' | 'tollfree' | 'mobile'; /** Optional human-readable label for this number. */ label?: string; /** * Optional 3-digit NANP area code. Works for both US (e.g. `'314'` * St. Louis, `'415'` San Francisco) and Canada (e.g. `'416'` Toronto, * `'604'` Vancouver). Canada shares NANP. Only honored for `local` * and `mobile` types. Silently ignored for `tollfree` (no real * geography). If no inventory is available in the requested NPA the * API returns a `no_numbers_in_area_code` error. Retry without * `areaCode` to get any local number in the country. */ areaCode?: string; } /** An SMS message (inbound or outbound). */ export interface Message { /** Unique message ID (e.g. `msg_xyz789`). */ id: string; /** `inbound` or `outbound`. */ direction?: string; /** Sender phone number in E.164 format. */ from: string; /** Recipient phone number in E.164 format. */ to: string; /** Message text content. */ body: string; /** Extracted OTP code, if one was detected in the body. */ otp: string | null; /** * Delivery outcome of an outbound message: * `queued` (handed to the carrier, no receipt yet), `sent` (carrier accepted * it, handset delivery unconfirmed), `delivered` (confirmed on the * recipient's handset), or `failed` (carrier rejected it, see `errorCode`). * * The 201 from `sms.send()` only means the carrier accepted the message, so * re-read it with `sms.get(id)` when delivery actually matters. */ status?: string; /** * Carrier error code on a failed message, else null. `40010` means the * sending number is not registered for A2P texting yet; check that number's * `messaging.state`. */ errorCode?: string | null; /** ISO 8601 timestamp when the carrier confirmed delivery, else null. */ deliveredAt?: string | null; /** Message cost in USD. */ cost?: number; /** ISO 8601 timestamp when the message was received. */ receivedAt?: string; /** ISO 8601 timestamp when the message was created. */ createdAt?: string; } /** A two-way AI SMS conversation thread (one per number + contact). */ export interface SmsConversation { /** Conversation ID (e.g. `smsconv_abc123`). */ id: string; /** The AgentCall number this thread is on. */ numberId: string; /** The contact's phone number in E.164 format. */ contactPhone: string; /** `active`, `idle`, or `closed`. */ state: string; /** ISO 8601 timestamp of the most recent message in the thread. */ lastMessageAt: string; /** ISO 8601 timestamp when the thread was created. */ createdAt: string; } /** A single message inside a two-way AI SMS thread. */ export interface SmsConversationMessage { id: string; /** `inbound` or `outbound`. */ direction: string; from: string; to: string; body: string; /** True when this outbound message was generated by the AI. */ aiReply: boolean; createdAt: string; } /** One message inside the freshness-classified view of a thread. */ export interface SmsContextMessage { id: string; /** `inbound` or `outbound`. */ direction: string; body: string; /** ISO 8601, or null when the stored timestamp was unusable. */ createdAt: string | null; conversationId: string | null; /** Age in days relative to `freshness.referenceTime`; null if unknown. */ ageDays: number | null; /** True for a pure greeting — recent, but not substantive discussion. */ isGreeting: boolean; } /** * A thread classified by freshness. Use this instead of slicing `messages` * yourself: "the last N messages" is not "recent conversation" — on a quiet * thread those N can be months old, and an agent that conflates the two will * describe a months-old exchange as something you just talked about. */ export interface SmsConversationContext { /** The newest inbound message; never repeated in the collections below. */ currentMessage: SmsContextMessage | null; /** Prior messages inside the freshness window, oldest first. */ recentMessages: SmsContextMessage[]; /** `recentMessages` minus greetings — the only "we discussed X" material. */ recentSubstantiveMessages: SmsContextMessage[]; /** Prior messages outside the window. Background only, never "recent". */ olderMessages: SmsContextMessage[]; freshness: { windowDays: number; /** ISO 8601 reference point the classification was made against. */ referenceTime: string; /** True when referenceTime fell back to server time. */ referenceTimeFallback: boolean; recentMessageCount: number; recentSubstantiveCount: number; olderMessageCount: number; unknownTimestampCount: number; }; /** Labels for the context actually available (e.g. `sms_recent`). */ sources: string[]; } /** A conversation plus its recent messages (up to 50, oldest first). */ export interface SmsConversationDetail extends SmsConversation { messages: SmsConversationMessage[]; /** * Freshness-classified view of the same messages. Additive — `messages` is * unchanged. Optional so older API deployments still type-check. */ context?: SmsConversationContext; } /** * Result of replying into a conversation. On a fresh send it's the created * outbound message; on a duplicate idempotencyKey it's `{ status: 'duplicate' }`. */ export type SmsConversationReplyResult = { id: string; conversationId: string; direction: string; from: string; to: string; body: string; aiReply: boolean; createdAt: string; } | { status: 'duplicate'; conversationId: string; }; /** Options for sending an SMS. */ export interface SendSMSOptions { /** Your provisioned phone number in E.164 format. */ from: string; /** Destination phone number in E.164 format. */ to: string; /** Message body, max 1600 characters. */ body: string; /** * Retry-safe key, scoped per phone number (max 200 chars). A duplicate * request with the same `(from, idempotencyKey)` replays the original * response instead of re-texting the recipient. When omitted, the SDK * generates one per `send()` call so its own automatic retries can never * double-send. */ idempotencyKey?: string; } /** Options for querying the SMS inbox. */ export interface InboxOptions { /** Max number of messages to return. Defaults to 20. */ limit?: number; /** Pagination cursor from a previous response. */ cursor?: string; /** Only return messages received after this ISO 8601 timestamp. */ since?: string; /** If `true`, only return messages that contain an OTP code. */ otpOnly?: boolean; } /** A phone call record. */ export interface Call { /** Unique call ID (e.g. `call_abc123`). */ id: string; /** `inbound` or `outbound`. */ direction: string; /** Caller phone number in E.164 format. */ from: string; /** Recipient phone number in E.164 format. */ to: string; /** * Call status lifecycle: `initiated` -> `ringing` -> `in-progress` -> * `completed`. Calls that never connect end as `busy`, `no_answer`, or * `failed`. */ status: string; /** Call duration in seconds, or `null` if not yet completed. */ duration: number | null; /** Whether the call is being recorded. */ record: boolean; /** * Short-lived signed URL to the call recording, valid for ~1 hour. * `null` when the call wasn't recorded or is still in progress. * For longer-lived access, call `client.calls.getRecordingUrl(id)` to * mint a fresh URL on demand. */ recordingUrl: string | null; /** * Cheap boolean derived from the recording column — `true` when a * recording exists and a signed URL can be fetched, `false` otherwise. * Use this in list views to render a play icon without forcing every * row to consume a signed URL. * * Returned by list endpoints (GET /v1/calls). Optional on the type * because older backends pre-Job-3 may not include it. */ hasRecording?: boolean; /** * The other leg of an agent-to-agent call (both numbers on this account), * or `null`. Lets you join the outbound and inbound call records without * from/to + timestamp heuristics. Optional because older backends may not * include it. */ peerCallId?: string | null; /** * Caller-supplied string tags set on dial via * `InitiateAICallOptions.metadata`. Echoed verbatim here and in every * call webhook payload; copied to the linked agent-to-agent inbound leg. * Omitted when the call was dialed without metadata. */ metadata?: Record; /** ISO 8601 timestamp when the call was created. */ createdAt: string; } /** Response shape for `client.calls.getRecordingUrl()`. */ export interface RecordingUrlResponse { /** The call ID this URL belongs to. */ callId: string; /** Short-lived signed URL playable via HTML