Getting started
Create your first agent
From an empty workspace to an agent answering your number. The console does all of this without a line of code; every step below is the API underneath it, for when you want to do it from your own system instead.
Paths, field names, enum values, headers and error codes in these samples are the real ones, checked against the API's own specification when this page is built. The ids, amounts and names in them are illustrative. `$WETALK_API_KEY` and `$AGENT` are shell variables you set yourself.
Before you start
You need one document and one number. The document depends on the template: a menu for delivery and ordering, a question list for a survey. The number is either one of your own, through your provider's SIP trunk, which you connect yourself on the console's Numbers & SIP page (“Connect a trunk”), or one WeTalk sets up for you: ask with `POST /v1/numbers/requests` (or “Request a number” on the same page) and we connect it to your agent.
You also need an API key, and the first one cannot come from the API — a request has to authenticate with something. Mint it in the console, under Settings, in the Developers block. It is shown exactly once: only a SHA-256 digest is kept, so there is no route that could show it to you again, and losing one means revoking it and minting another.
- A key is judged by scope and never by role. Give it the narrowest set that works.
- The nine scopes are agent:read, agent:write, conversation:read, recording:read, campaign:write, credit:spend, order:read, order:write and conversation:dial.
- rate_limit_per_minute defaults to 60 and may be set anywhere from 1 to 600.
curl https://api.wetalk.io/v1/agents \
-H "Authorization: Bearer $WETALK_API_KEY" {
"data": [
{
"voice_agent_id": "0199f1c2-6b40-7a11-9d3e-6c1b5f0a2e77",
"name": "Roma orders",
"slug": "roma-orders",
"state": "live"
}
]
} Pick a template
A template already knows its job: how a menu is read and an address confirmed, or how to ask one question at a time and stop when somebody says no. The catalogue is data, and `code` is what you pass when you create the agent. Two templates are available in the first release — `delivery` and `survey`.
The same catalogue serves the languages and the voices. A voice must speak every language you ask for, and every language pack must be complete: an incomplete pack makes that language unavailable and there is no fallback to English anywhere, which is why this is checked before the build starts rather than discovered during a call.
curl https://api.wetalk.io/v1/templates \
-H "Authorization: Bearer $WETALK_API_KEY"
curl https://api.wetalk.io/v1/languages \
-H "Authorization: Bearer $WETALK_API_KEY"
curl https://api.wetalk.io/v1/voices \
-H "Authorization: Bearer $WETALK_API_KEY" Create the agent
Creating does not build. One request writes the VoiceAgent, its first version, the build job and its steps, and enqueues the build — all in one database transaction — and then answers. The factory is a state machine run by a worker, so the reply comes back in milliseconds and the work happens behind it.
Send an `Idempotency-Key` on this and on every POST that spends money or starts something. It is stored for 24 hours against the account, the route and the key. Replaying the same key with a different body is refused rather than answered from the store, which is the failure you want: it means you have a bug, not a duplicate agent.
curl https://api.wetalk.io/v1/agents \
-X POST \
-H "Authorization: Bearer $WETALK_API_KEY"
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0199f1c2-6b40-7a11-9d3e-6c1b5f0a2e77" \
-d '{
"agent_template_code": "delivery",
"name": "Roma orders",
"language_code": ["el", "en"],
"voice_id": "0199f1c2-7000-7000-8000-00000000000a",
"greeting": "Roma Pizzeria, good evening. What can I get for you?",
"number_choice": "new_number",
"medium": ["voice"],
"channel_unit_count": 4,
"field_value": {
"delivery_fee": "2.50",
"opening_hours": "Tue to Sun, 17:00 to 23:30"
}
}' {
"data": {
"voice_agent_id": "0199f1c2-6b40-7a11-9d3e-6c1b5f0a2e77",
"agent_version_id": "0199f1c2-6b41-7c02-b8aa-1d9f4e2c7b10",
"build_job_id": "0199f1c2-6b41-7c02-b8aa-2f0e5a3d8c21",
"slug": "roma-orders"
}
} Watch it build
The build job streams. Open a server-sent event stream on the id you were just handed and you get the same steps the console draws. It is SSE rather than a WebSocket because the traffic is one-way, it survives proxies, and `Last-Event-ID` resumes it for free after a dropped connection.
A comment line arrives every fifteen seconds so an idle proxy does not close the stream. If you would rather poll, read the agent instead; nothing about the build requires the stream.
- build.step and build.progress while it runs.
- build.succeeded when the version is ready to publish.
- build.failed with the reason, which is also on the version.
curl -N https://api.wetalk.io/v1/stream/build/0199f1c2-6b41-7c02-b8aa-2f0e5a3d8c21 \
-H "Authorization: Bearer $WETALK_API_KEY"
-H "Accept: text/event-stream" Publish it, then point traffic at it
A built version is not a live one. Publishing takes the sequence number you last read and refuses with `409 live_version_seq_stale` if somebody published while you were deciding — it will not quietly overwrite their decision. Rolling back is the same operation against an older version, which is why it has the same in-flight guarantee: conversations already running stay on the version they started on, and only new ones resolve to the new live version.
Then connect the line. A number on your own SIP trunk is connected to the agent on the agent's Channels tab; a number WeTalk set up for you is bound to the agent when it is connected, and there is nothing more to do. Numbers and SIP covers both.
curl https://api.wetalk.io/v1/agents/$AGENT/versions/$VERSION/publish \
-X POST \
-H "Authorization: Bearer $WETALK_API_KEY"
-H "Content-Type: application/json" \
-d '{ "expected_live_version_seq": 0 }' When something is refused
Every failing response carries the same envelope, and `code` is the only part of it you should branch on. The human `message` may be reworded between releases; the code never is, because a consumer depends on it. `field` names the offending input when there is one, and `request_id` is the thing to quote when you ask us what happened.
{
"error": {
"code": "language_pack_incomplete",
"message": "That language is not available yet.",
"field": "language_code",
"request_id": "0199f1c2-6b42-7d14-9e55-3a1b6c4d9e32"
}
}