API reference
Every voicast API endpoint (/v1) and the voice connections: authentication, format, inputs, responses and errors.
Basics#
- URL:
https://api.voicast.jp/v1 - Auth:
Authorization: Bearer vk_live_…orvk_test_…. A key reads and writes only inside its environment (live or test) - Format: JSON. Times are Unix milliseconds; IDs are prefixed strings (
agt_,ten_,call_,sess_,con_) - Errors:
{ error, message, details?, request_id }. See Errors and limits - Request ID: every response has
x-request-id(req_…), the same asrequest_idin error bodies. Quote it when contacting support - Rate limit: 600 requests per minute per API key.
x-ratelimit-limit,x-ratelimit-remainingandx-ratelimit-reset(end of the window, Unix seconds) show what is left; over the limit you get 429 withRetry-After(seconds)
Create API keys under API keys in the dashboard (admin or above). Keep them on your server; never ship them to browsers or apps.
Endpoints#
| Method | Path | Description |
|---|---|---|
| POST | /v1/agents |
Create an agent |
| GET | /v1/agents |
List agents |
| GET | /v1/agents/{id} |
Get an agent |
| PUT | /v1/agents/{id} |
Update an agent (new version) |
| GET | /v1/agents/{id}/versions |
List versions |
| DELETE | /v1/agents/{id} |
Delete an agent |
| POST | /v1/tenants |
Create a tenant |
| GET | /v1/tenants |
List tenants |
| GET | /v1/tenants/{id} |
Get a tenant |
| PATCH | /v1/tenants/{id} |
Update a tenant |
| POST | /v1/tenants/{id}/secret |
Rotate the connection secret |
| DELETE | /v1/tenants/{id} |
Delete a tenant |
| GET | /v1/calls |
List calls |
| GET | /v1/calls/{id} |
Get a call |
| GET | /v1/calls/by-provider/{providerCallId} |
Get a call by your provider's ID |
| GET | /v1/calls/{id}/transcript |
Get a transcript |
| POST | /v1/sessions |
Create a Web/app session |
| GET | /v1/usage |
Usage breakdown |
| POST | /v1/webhooks/portal |
Webhook portal link |
| GET | /v1/billing |
Billing (balance, auto-recharge, per-minute price, payment method) |
| POST | /v1/billing/topups |
Top-up URL |
| GET | /v1/tools/secret |
In-call tools signing secret |
| POST | /v1/tools/secret/rotate |
Rotate the signing secret |
| GET | /v1/connectors |
List connectors |
| GET | /v1/connections |
List connections |
| POST | /v1/connections |
Create a connection (API key or URL) |
| GET | /v1/connections/{id} |
Get a connection |
| PATCH | /v1/connections/{id} |
Configure a connection |
| GET | /v1/connections/{id}/options |
Options for configuring a connection |
| DELETE | /v1/connections/{id} |
Delete a connection |
| GET | /v1/tenants/{id}/connections |
List a tenant's connections |
| POST | /v1/tenants/{id}/connect-links |
Connect-page link for a business |
Voice connections (no API key):
| Kind | URL | Description |
|---|---|---|
| WebSocket | wss://voice.voicast.jp/ws |
Phone (Twilio Media Streams) |
| POST | https://voice.voicast.jp/v1/webrtc/offer |
Web and apps (WebRTC offer) |
| PATCH | https://voice.voicast.jp/v1/webrtc/offer |
Add ICE candidates |
For MCP (https://api.voicast.jp/mcp), see MCP.
Create an agent#
POST /v1/agents| Input | Type | Description |
|---|---|---|
name |
string | Name (1–100 chars) |
config |
AgentConfig | The agent config (Agents) |
Returns 201 Agent (with config, version: 1). Invalid config returns 422 invalid_config with per-item details.
type Agent = { id: string; name: string; version: number; config?: AgentConfig; created_at: number; updated_at: number };
type AgentConfig = {
greeting: string; // first thing the AI says (up to 300 chars)
persona: string; // role and manner (up to 1,000 chars)
fields: { key: string; label: string; required?: boolean; type?: 'text' | 'phone' }[]; // 1–20
handoff_when: string[]; // handoff rules (up to 20)
closing: string; // closing when everything is collected (up to 300 chars)
closing_answered?: string; // closing when an FAQ answer settled the call
faq?: { q: string; a: string }[]; // up to 50
domain?: string; // line of business (recognition hint)
vocabulary?: string[]; // terms (recognition hints, up to 200)
voice?: string; // voice name (default Aoede)
speaking_rate?: number; // speaking rate (default 1.15)
tools?: AgentTool[]; // tools (up to 10)
connectors?: ConnectorEntry[]; // connections (up to 10)
};
type AgentTool = {
name: string; description: string; url: string; confirm?: boolean;
parameters?: { properties: Record<string, { type: 'string' | 'number' | 'integer' | 'boolean'; description?: string; enum?: string[] }>; required?: string[] };
};
type ConnectorEntry =
| { connector: string; actions: string[]; confirm?: string[] } // a preset
| { connection: string; actions: string[]; confirm?: string[] }; // a specific connection (con_…)List agents#
GET /v1/agentsReturns { data: Agent[] } (without config).
Get an agent#
GET /v1/agents/{id}?version=Omit version (an integer ≥ 1) for the current version. Returns Agent with config; 404 if not found.
Update an agent#
PUT /v1/agents/{id}| Input | Type | Description |
|---|---|---|
name |
string | Optional. Unchanged if omitted |
config |
AgentConfig | The whole config |
Returns Agent (the new version). Calls in progress keep the version they started with.
List agent versions#
GET /v1/agents/{id}/versionsReturns { data: [{ version, created_at }] }, newest first.
Delete an agent#
DELETE /v1/agents/{id}Returns 204. 409 in_use if a tenant uses it. Call records are kept.
Create a tenant#
POST /v1/tenants| Input | Type | Description |
|---|---|---|
external_id |
string | Your customer ID. 1–100 chars of letters, digits and . _ : -; unique within the environment |
name |
string | Optional. Display name (up to 100 chars) |
agent_id |
string | Optional. Default agent |
Returns 201 Tenant plus secret (the connection secret vts_…, returned only here). 409 conflict if the external_id exists.
type Tenant = { id: string; external_id: string; name: string; agent_id: string | null; status: 'active' | 'disabled'; created_at: number };List tenants#
GET /v1/tenantsReturns { data: Tenant[] }.
Get a tenant#
GET /v1/tenants/{id}Returns Tenant (the connection secret is never returned here).
Update a tenant#
PATCH /v1/tenants/{id}| Input | Type | Description |
|---|---|---|
name |
string | Optional |
agent_id |
string | null | Optional. null removes it |
status |
active, disabled |
Optional. disabled stops phone and Web/app connections |
Returns Tenant. Only the fields you pass change.
Rotate a tenant secret#
POST /v1/tenants/{id}/secretReturns { secret, previous_secret_expires_at }. The old secret keeps working for 24 hours.
Delete a tenant#
DELETE /v1/tenants/{id}Returns 204. The tenant can no longer connect and its connections are revoked. Call records are kept. You can create a tenant with the same external_id again.
List calls#
GET /v1/calls?tenant_id=&cursor=&limit=| Query | Description |
|---|---|
tenant_id |
Optional. Filter by tenant |
cursor |
Optional. Where to continue (pass the previous response's next_cursor as is) |
limit |
Optional. 1–100 (default 50) |
before |
Optional. The older way to page (calls that started before this started_at). It can skip calls that started in the same millisecond, so use cursor. Cannot be combined with cursor (422) |
Returns { data: Call[], has_more, next_cursor }, newest first. While has_more is true, pass next_cursor as cursor to read on (next_cursor is null when has_more is false).
type Call = {
id: string; tenant_id: string; agent: { id: string; version: number };
provider_call_id: string | null; // Twilio CallSid, or the session ID for Web and apps
from: string | null; // caller's number (phone, when passed)
started_at: number; ended_at: number | null; duration_ms: number | null;
outcome: 'completed' | 'answered' | 'handoff' | 'hangup' | 'error' | null; reason: string | null;
fields: Record<string, string> | null;
summary: string | null; category: string | null; has_transcript: boolean;
channel: 'phone' | 'web'; output: 'audio' | 'text';
billed_yen: number | null; // what this call costs you (yen before tax, up to 3 decimals); null while in progress
};billed_yen is the amount taken from the balance for prepaid organizations (seconds × per-minute price ÷ 60), and seconds × the contract per-minute price ÷ 60 for invoiced organizations. Invoices round the month's total seconds up to whole minutes, so the sum of the calls can differ slightly (Pricing and billing).
Get a call#
GET /v1/calls/{id}Returns Call.
Get a call by provider ID#
GET /v1/calls/by-provider/{providerCallId}Look up a call by Twilio CallSid (CA…) or session ID (sess_…), for example from Twilio's <Connect> action. Returns Call; 404 if there is no record.
Get a transcript#
GET /v1/calls/{id}/transcriptReturns { call_id, turns: [{ role: 'assistant' | 'user', text }] }. Created after the call ends; 404 if none.
Create a session#
POST /v1/sessions| Input | Type | Description |
|---|---|---|
tenant_id |
string | Tenant ID |
agent_id |
string | Optional. Defaults to the tenant's agent |
output |
audio, text |
Optional. Default audio |
Returns 201 { id, tenant_id, agent_id, output, client_token, expires_at, webrtc_url, ice_servers }. client_token (vct_…) lasts 2 minutes and works once. Pass ice_servers ([{ urls, username?, credential? }]) to RTCPeerConnection's iceServers as is (STUN, plus short-lived TURN when available). Give only client_token, webrtc_url and ice_servers to the browser or app.
Errors: 402 (balance_depleted, payment_overdue, spending_cap_reached), 409 tenant_disabled, 422 no_agent or invalid_request.
Get usage#
GET /v1/usage?from=&to=&group_by=| Query | Description |
|---|---|
from, to |
A date (YYYY-MM-DD, Japan time; to includes that day) or Unix milliseconds (to exclusive). Defaults to the start of this month until now. Up to 366 days |
group_by |
Optional. tenant, day or channel |
Returns:
type Usage = {
environment: 'live' | 'test'; from: number; to: number;
group_by: 'tenant' | 'day' | 'channel' | null; unit_price_yen: number;
total: { calls: number; seconds: number; minutes: number; amount_yen: number };
data: (({ tenant: { id: string; external_id: string; name: string } } | { day: string } | { channel: 'phone' | 'web' })
& { calls: number; seconds: number; minutes: number; amount_yen: number })[]; // [] without group_by
note: string;
};Calls that ended within the period are counted. amount_yen is an estimate excluding tax (Pricing and billing).
Create a webhook portal link#
POST /v1/webhooks/portal| Input | Type | Description |
|---|---|---|
frame_origin |
string | Optional. Parent origin when embedding in an iframe |
locale |
ja, en |
Optional. Page language (default ja) |
Returns 201 { url, expires_at } (valid for 15 minutes): a page to register endpoint URLs, choose events and see signing secrets (Results). 503 unavailable if webhook delivery is not available.
Get billing#
GET /v1/billingReturns the billing state of the API key's organization (shared by the live and test environments), the same as Billing in the dashboard.
type Billing = {
organization_id: string; month: string; // this month (Japan time, YYYY-MM)
contract: { payment_method: 'card' | 'invoice'; per_minute_yen: number; invoice_days_until_due: number };
has_card: boolean; // a card is saved for auto-recharge
usage: { calls: number; seconds: number; minutes: number }; // this month's calls (live and test)
estimate_yen: number; // prepaid: taken this month; invoice: the estimate
balance: { yen: number; milli_yen: number; depleted: boolean; next_expiry: { yen: number; at: number } | null } | null; // prepaid only
auto_recharge: { enabled: boolean; threshold_yen: number; amount_yen: number; monthly_cap_yen: number | null;
recharged_this_month_yen: number; failures: number; max_failures: number } | null; // prepaid only
topup: { min_yen: number; max_yen: number; quick_yen: number[]; credit_months: number };
spending_cap_yen: number | null; cap_reached: boolean;
payment: { failed_at: number; grace_ends_at: number; suspended: boolean } | null; // late invoice payment
tax: 'excluded'; tax_rate: number;
// also price, stripe_enabled, subscription_status, threshold
};Create a top-up#
POST /v1/billing/topups| Input | Type | Description |
|---|---|---|
amount_yen |
integer | Amount in yen before tax, 500–300,000 |
Returns 201 { url, amount_yen, tax_yen }. url is a Stripe payment page: open it in a browser and pay, and the amount is added to the balance. 422 outside the range, 409 invoice_billing for invoiced organizations. Auto-recharge, the monthly cap and the card are changed in the dashboard (Pricing and billing).
Get the tools signing secret#
GET /v1/tools/secretReturns { secret, previous_secret_expires_at }. secret (whsec_…) signs the environment's in-call tool requests. previous_secret_expires_at is when the previous secret stops being used after a rotation (null if none).
Rotate the tools signing secret#
POST /v1/tools/secret/rotateReturns { secret, previous_secret_expires_at }. For 24 hours after a rotation, requests are signed with both the new and the previous secret; switch your server to the new one within that time.
List connectors#
GET /v1/connectorsReturns { data: Connector[] }.
type Connector = {
id: string; name: string; description: string;
category: 'scheduling' | 'crm' | 'notify' | 'custom'; auth: 'oauth2' | 'api_key' | 'webhook_url';
available: boolean; // usable on this voicast
needs_setup: boolean; // needs configuration after connecting
notify: boolean; // posts a notification after each call
env_only: boolean; // environment connections only (not on the embeddable connect page)
inputs: { name: string; type: 'text' | 'password' | 'url' | 'number' }[];
actions: { name: string; tool: string; description: string; confirm: boolean }[]; // tool is the name shown to the AI
};List connections#
GET /v1/connections?tenant_id=Without tenant_id: all connections. With an empty value (?tenant_id=): environment connections only. With an ID: that tenant's connections. Deleted connections are not listed. Returns { data: Connection[] }.
type Connection = {
id: string; connector: string; tenant_id: string | null;
label: string; // the provider account (clinic@example.com, workspace #channel and so on)
status: 'active' | 'needs_setup' | 'needs_reauth';
config: object; created_by: string | null; created_at: number; updated_at: number;
};Create a connection (API key or URL)#
POST /v1/connections| Input | Type | Description |
|---|---|---|
connector |
teams, shannon_booking, http |
Connectors that take an API key or URL |
secret |
string | The API key or webhook URL (1–2,000 chars) |
input |
object | Optional. { base_url } for shannon_booking, { base_url, header } for http |
tenant_id |
string | Optional. Makes it a tenant connection. shannon_booking and http are environment-only: passing it returns 422 invalid_request |
Returns 201 Connection. voicast makes one test call before saving; failure returns 422 verify_failed. OAuth connectors are connected through a connect-page link or the dashboard.
Get a connection#
GET /v1/connections/{id}Returns Connection.
Configure a connection#
PATCH /v1/connections/{id}| Input | Type | Description |
|---|---|---|
config |
object | Connector-specific settings. Only the fields you pass change |
Returns Connection. A needs_setup connection becomes active. Invalid settings return 422 invalid_config. See Connections for each connector's settings.
Connection options#
GET /v1/connections/{id}/optionsAsks the provider for the choices available when configuring.
| Connector | Query | Returns |
|---|---|---|
| Google Calendar, Microsoft 365 | { calendars: [{ id, name, primary }] } |
|
| Google Sheets | spreadsheet, sheet |
{ spreadsheets, sheets, headers } |
| kintone | app |
{ apps, fields: [{ code, label }] } |
| Chatwork | { rooms } |
|
| LINE WORKS | { users } |
|
| MCP server | { tools } |
409 needs_reauth if the connection is broken.
Delete a connection#
DELETE /v1/connections/{id}Returns 204 and revokes the provider's token.
List a tenant's connections#
GET /v1/tenants/{id}/connectionsReturns { data: Connection[] }.
Create a connect-page link#
POST /v1/tenants/{id}/connect-links| Input | Type | Description |
|---|---|---|
connectors |
string[] | Optional. Connectors to show (google_calendar, m365_calendar, google_sheets, kintone, hubspot, slack, chatwork, lineworks, teams) |
frame_origin |
string | Optional. https parent origin when embedding in an iframe |
Returns 201 { url, expires_at } (valid for 15 minutes). See Connections.
Phone (Twilio Media Streams)#
wss://voice.voicast.jp/wsThe target of Twilio's <Connect><Stream>. Pass these as <Parameter>s:
| Parameter | Description |
|---|---|
tenant |
Tenant ID |
secret |
The tenant's connection secret (vts_…) |
agent |
Optional. Agent ID |
from |
Optional. The caller's number |
The stream is closed immediately when the secret is wrong, the tenant is disabled or calls are refused for billing reasons. See Phone.
WebRTC offer#
POST https://voice.voicast.jp/v1/webrtc/offer| Input | Type | Description |
|---|---|---|
sdp, type |
string | The WebRTC offer |
token |
string | The session's client_token (Pipecat clients may send it as requestData.token) |
pc_id, restart_pc |
string, boolean | Renegotiation only (no token needed) |
Returns { sdp, type, pc_id }. 403 when the token is wrong, expired or used, or when calls are refused for billing reasons (billing refusals will later return 402, like POST /v1/sessions).
Once connected, send RTVI client-ready on the data channel, then a ping string every second (Web and apps).
ICE candidates#
PATCH https://voice.voicast.jp/v1/webrtc/offer| Input | Type | Description |
|---|---|---|
pc_id |
string | pc_id from the offer response |
candidates |
array | [{ candidate, sdp_mid, sdp_mline_index }] |
Returns { ok: true }. 404 for an unknown pc_id.