Voice API
Every Voice action (22) and data model (3): endpoints, scopes, input, preconditions, events and examples.
Your Eras answer the phone and your website by voice, book, take messages and file every call in the Inbox.
Module code voice · 22 actions · 3 data models · 13 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: voice (everything), voice:read (read only) or voice:<capability>.
Resources
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
voice_agents | voice_agent | /api/v1/voice_agents | voice.update | ||
voice_calls | voice_call | /api/v1/voice_calls | |||
voice_whatsapp_lines | voice_whatsapp_line | /api/v1/voice_whatsapp_lines |
Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.
Actions
| Action | Needs | What it does |
|---|---|---|
voice.answer_permission | voice:handle | The customer answered the call permission |
voice.await_permission | voice:handle | Wait for the customer’s permission to call |
voice.complete_call | voice:handle | File a finished call |
voice.connect_whatsapp | voice:manage | Answer WhatsApp calls |
voice.connect_whatsapp_call | voice:handle | Era picked up a WhatsApp call |
voice.decide_whatsapp_call | voice:write | Answer a ringing WhatsApp call with Era, or let it ring |
voice.enable | voice:manage | Give an Era a voice |
voice.fail_call | voice:handle | A call did not happen |
voice.mark_dialed | voice:handle | A call was placed |
voice.open_call | voice:handle | A call began |
voice.record_sync | voice:handle | Record the voice provider’s answer |
voice.record_turn | voice:handle | Record what Era did on a call |
voice.record_whatsapp | voice:handle | Record whether WhatsApp calls are on |
voice.remove | voice:delete | Take an Era’s voice away |
voice.set_enabled | voice:manage | Switch an Era’s voice on or off |
voice.set_outcome | voice:write | Correct a call’s outcome |
voice.set_whatsapp_line | voice:manage | Answer WhatsApp calls on my computer |
voice.start_call | voice:write | Have Era call someone |
voice.start_whatsapp_call | voice:write | Call someone on WhatsApp with Era |
voice.take_over_whatsapp_call | voice:write | Take the WhatsApp call over from Era |
voice.update | voice:manage | Change an Era’s voice |
voice.whatsapp_ring | voice:handle | A WhatsApp call is ringing on the computer |
voice.answer_permission
The customer answered the call permission — Allowed: the call is placed now. Declined: it is cancelled and nothing is charged. Machine-only.
Needs voice:handle · changes data
403 machine_only).https://app.getera.io/api/v1/voice_calls/{id}/actions/answer_permission- Canonical address:
POST /api/v1/actions/voice.answer_permission
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Call · id of voice_call |
granted | boolean | yes | Allowed |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_awaiting | Only a call waiting for permission takes the answer. |
Events it publishes: voice.call_missed
What it writes: voice_call (update) — awaiting → queued (allowed) or cancelled (declined, released)
Errors: 401 no or expired credential · 403 the credential does not hold voice:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_awaiting · 429 slow down
voice.await_permission
Wait for the customer’s permission to call — WhatsApp lets a business call only someone who allowed it: the request went out, the call waits for their answer. Machine-only.
Needs voice:handle · changes data
403 machine_only).https://app.getera.io/api/v1/voice_calls/{id}/actions/await_permission- Canonical address:
POST /api/v1/actions/voice.await_permission
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Call · id of voice_call |
requestId | string | Permission request message · max 200 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_queued | Only a queued WhatsApp call waits for permission. |
What it writes: voice_call (update) — queued → awaiting permission
Errors: 401 no or expired credential · 403 the credential does not hold voice:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_queued · 429 slow down
voice.complete_call
File a finished call — Writes the transcript, summary, outcome and length, meters the real minutes on your own voice key and tells workflows. Machine-only.
Needs voice:handle · reaches outside (sends, publishes or charges)
403 machine_only).https://app.getera.io/api/v1/voice_calls/{id}/actions/complete_call- Canonical address:
POST /api/v1/actions/voice.complete_call
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Call · id of voice_call |
transcript | object / array | Transcript | |
summary | string | Summary · max 4000 | |
durationSec | number | yes | Length (seconds) · min 0 · max 14400 |
endReason | string | How it ended · max 200 | |
transferred | boolean | Put through to a person | |
externalId | string | Provider call · max 120 | |
fromNumber | string | From · max 40 | |
toNumber | string | To · max 40 | |
conversationId | uuid | Inbox conversation · id of conversation | |
customerId | uuid | Customer · id of customer | |
recording | boolean | Has a recording | |
recordingFileId | uuid | Recording file | |
language | string | Language · max 8 | |
providerActions | object / array | Provider actions |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
call_not_found | The call must exist here. |
call_finished | A call is filed once. |
Events it publishes: voice.call_finished, voice.appointment_booked, voice.lead_captured, voice.message_taken, voice.transfer_requested, voice.whatsapp_call_finished
What it writes: voice_call (update) — → completed, with transcript, outcome and cost
Errors: 401 no or expired credential · 403 the credential does not hold voice:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 call_not_found, call_finished · 429 slow down
voice.connect_whatsapp
Answer WhatsApp calls — The Era answers voice calls on your WhatsApp Business number too, and files each one in that customer’s WhatsApp chat.
Needs voice:manage · reaches outside (sends, publishes or charges)
https://app.getera.io/api/v1/voice_agents/{id}/actions/connect_whatsapp- Canonical address:
POST /api/v1/actions/voice.connect_whatsapp
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Voice · id of voice_agent |
inboxId | uuid | WhatsApp number (Inbox channel) · id of inbox |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
voice_not_found | The voice must exist here. |
inbox_not_whatsapp | Calls come to an active WhatsApp Cloud API channel. |
whatsapp_taken | One Era answers one WhatsApp number. |
Events it publishes: voice.agent_changed
What it writes: voice_agent (update) — Points the WhatsApp line at the Era (pending until Meta and the voice provider take it)
curl -X POST https://app.getera.io/api/v1/voice_agents/VOICE_AGENT_ID/actions/connect_whatsapp \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"inboxId":"INBOX_ID"}'{
"ok": true,
"result": "…",
"events": [
"voice.agent_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.voiceAgents.connectWhatsapp("VOICE_AGENT_ID", {
inboxId: "INBOX_ID"
})era voice_agents connect_whatsapp VOICE_AGENT_ID \
--inbox-id "INBOX_ID"{
"name": "era__voice__connect_whatsapp",
"arguments": {
"id": "VOICE_AGENT_ID",
"inboxId": "INBOX_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold voice:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 voice_not_found, inbox_not_whatsapp, whatsapp_taken · 429 slow down
voice.connect_whatsapp_call
Era picked up a WhatsApp call — The computer answered (or placed) the WhatsApp call and the voice is connected — minutes and money checked first. Machine-only.
Needs voice:handle · reaches outside (sends, publishes or charges)
403 machine_only).https://app.getera.io/api/v1/voice_calls/{id}/actions/connect_whatsapp_call- Canonical address:
POST /api/v1/actions/voice.connect_whatsapp_call
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Call · id of voice_call |
deviceId | uuid | Computer |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_ringing | Only a waiting WhatsApp call connects, once. |
wrong_computer | The computer that has the call connects it. |
voice_not_found | The talking Era must have a voice. |
voice_off | The voice must be on. |
minutes_used_up | The month’s call minutes must not be used up. |
no_source | Something must be able to pay for the minutes. |
budget_exceeded | The AI budget must have room. |
Events it publishes: voice.call_started
What it writes: voice_call (update) — queued → in progress, with its reservation
Errors: 401 no or expired credential · 403 the credential does not hold voice:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_ringing, wrong_computer, voice_not_found, voice_off, minutes_used_up, no_source, budget_exceeded · 429 slow down
voice.decide_whatsapp_call
Answer a ringing WhatsApp call with Era, or let it ring — While a WhatsApp call rings on your computer: Era answers it, or it keeps ringing for you. A workflow on “WhatsApp call ringing” uses this too.
Needs voice:write · reaches outside (sends, publishes or charges)
https://app.getera.io/api/v1/voice_calls/{id}/actions/decide_whatsapp_call- Canonical address:
POST /api/v1/actions/voice.decide_whatsapp_call
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Call · id of voice_call |
answer | boolean | yes | Era answers |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_ringing | Only a ringing WhatsApp call is decided. |
What it writes: voice_call (update) — Writes the decision; a call left to ring is closed without cost
curl -X POST https://app.getera.io/api/v1/voice_calls/VOICE_CALL_ID/actions/decide_whatsapp_call \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"answer":true}'{
"ok": true,
"result": "…",
"events": []
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.voiceCalls.decideWhatsappCall("VOICE_CALL_ID", {
answer: true
})era voice_calls decide_whatsapp_call VOICE_CALL_ID \
--answer true{
"name": "era__voice__decide_whatsapp_call",
"arguments": {
"id": "VOICE_CALL_ID",
"answer": true
}
}Errors: 401 no or expired credential · 403 the credential does not hold voice:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_ringing · 429 slow down
voice.enable
Give an Era a voice — An Era answers the phone, the website’s voice button and you in the app — with the voice, greeting, hours and abilities you pick.
Needs voice:manage · changes data
https://app.getera.io/api/v1/voice_agents/actions/enable- Canonical address:
POST /api/v1/actions/voice.enable
Input
| Field | Type | Required | Notes |
|---|---|---|---|
agentId | uuid | yes | Era · id of ai_agent |
inboxId | uuid | yes | Phone line (Inbox channel) · id of inbox |
voiceId | string | Voice · max 120 | |
voiceName | string | Voice name · max 120 | |
language | string | Language · max 8 | |
greeting | string | Greeting · max 600 | |
persona | string | Manner · max 4000 | |
afterHours | string (enum) | Outside opening hours · Allowed values: answer, take_message, transfer, closed_message | |
afterHoursMessage | string | Closed message · max 600 | |
transferNumber | string | Transfer number · max 32 | |
tools | object / array | What it may do · check_availability, book_appointment, cancel_appointment, order_status, capture_lead, answer_question, take_message, transfer_to_human | |
approvalTools | object / array | Ask me first for | |
knowledgeBaseIds | object / array | Knowledge bases | |
maxCallMinutes | number | Longest call (minutes) · min 1 · max 60 | |
widgetEnabled | boolean | Voice button on my website | |
widgetSiteId | uuid | Website · id of site | |
widgetLabel | string | Button text · max 60 | |
appEnabled | boolean | Talk to it in the app | |
outboundEnabled | boolean | May make calls | |
disclosureEnabled | boolean | Say it is an AI and the call is recorded | |
confirmBooking | boolean | Send a WhatsApp/SMS confirmation after a booking |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
agent_not_found | The Era must exist here. |
already_has_voice | An Era has one voice. |
inbox_not_voice | Calls land on an active voice channel. |
inbox_taken | One Era answers one line. |
unknown_tool | Every ability must be one a voice Era has. |
bad_transfer_number | A transfer number is a full international phone number. |
transfer_needs_number | Putting calls through needs a transfer number. |
unknown_language | The language must be one the voice speaks. |
unknown_knowledge_base | Knowledge bases are picked by id. |
site_not_found | The website must be this workspace’s. |
Events it publishes: voice.agent_changed
What it writes: voice_agent (insert) — Writes the voice, switched off until it is ready
curl -X POST https://app.getera.io/api/v1/voice_agents/actions/enable \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"agentId":"AI_AGENT_ID","inboxId":"INBOX_ID"}'{
"ok": true,
"result": "…",
"events": [
"voice.agent_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.voiceAgents.enable({
agentId: "AI_AGENT_ID",
inboxId: "INBOX_ID"
})era voice_agents enable \
--agent-id "AI_AGENT_ID" \
--inbox-id "INBOX_ID"{
"name": "era__voice__enable",
"arguments": {
"agentId": "AI_AGENT_ID",
"inboxId": "INBOX_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold voice:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 agent_not_found, already_has_voice, inbox_not_voice, inbox_taken, unknown_tool, bad_transfer_number, transfer_needs_number, unknown_language, unknown_knowledge_base, site_not_found · 429 slow down
voice.fail_call
A call did not happen — Busy, no answer or failed: nothing is metered and a missed call can be followed up. Machine-only.
Needs voice:handle · reaches outside (sends, publishes or charges)
403 machine_only).https://app.getera.io/api/v1/voice_calls/{id}/actions/fail_call- Canonical address:
POST /api/v1/actions/voice.fail_call
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Call · id of voice_call |
status | string | yes | What happened · max 12 |
reason | string | Why · max 600 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
call_finished | A call is filed once. |
invalid_state | Missed, failed or cancelled. |
Events it publishes: voice.call_missed
What it writes: voice_call (update) — → missed / failed / cancelled, released
Errors: 401 no or expired credential · 403 the credential does not hold voice:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 call_finished, invalid_state · 429 slow down
voice.mark_dialed
A call was placed — The provider is ringing the number. Machine-only.
Needs voice:handle · changes data
403 machine_only).https://app.getera.io/api/v1/voice_calls/{id}/actions/mark_dialed- Canonical address:
POST /api/v1/actions/voice.mark_dialed
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Call · id of voice_call |
externalId | string | yes | Provider call · max 120 |
callSid | string | Telephony id · max 120 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_queued | Only a queued call can be dialled, once. |
What it writes: voice_call (update) — queued → in progress
Errors: 401 no or expired credential · 403 the credential does not hold voice:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_queued · 429 slow down
voice.open_call
A call began — Records a call the moment it starts — the phone, the website button or the app — after checking minutes and the voice key. Machine-only.
Needs voice:handle · reaches outside (sends, publishes or charges)
403 machine_only).https://app.getera.io/api/v1/voice_calls/actions/open_call- Canonical address:
POST /api/v1/actions/voice.open_call
Input
| Field | Type | Required | Notes |
|---|---|---|---|
voiceAgentId | uuid | yes | Voice · id of voice_agent |
channel | string (enum) | yes | Channel · Allowed values: phone, widget, app, whatsapp |
bridge | string | How it reached Era · max 20 | |
waCallId | string | WhatsApp call · max 200 | |
direction | string (enum) | Direction · Allowed values: inbound, outbound | |
fromNumber | string | From · max 40 | |
toNumber | string | To · max 40 | |
externalId | string | Provider call · max 120 | |
callSid | string | Telephony id · max 120 | |
principalUserId | uuid | Talking member | |
practice | boolean | Practice call |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
voice_not_found | The voice must exist here. |
voice_off | The voice must be on. |
channel_off | The website button or the in-app voice must be on for a call there. |
unknown_bridge | The way the call reached Era must be a known one. |
minutes_used_up | The month’s call minutes must not be used up. |
no_source | The workspace’s own voice key must be able to run it. |
budget_exceeded | The AI budget must have room. |
Events it publishes: voice.call_started
What it writes: voice_call (insert) — Opens the call
Errors: 401 no or expired credential · 403 the credential does not hold voice:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 voice_not_found, voice_off, channel_off, unknown_bridge, minutes_used_up, no_source, budget_exceeded · 429 slow down
voice.record_sync
Record the voice provider’s answer — The provider took (or refused) the Era’s voice and number. Machine-only.
Needs voice:handle · changes data
403 machine_only).https://app.getera.io/api/v1/voice_agents/{id}/actions/record_sync- Canonical address:
POST /api/v1/actions/voice.record_sync
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Voice · id of voice_agent |
status | string | yes | State · max 10 |
externalAgentId | string | Provider agent · max 120 | |
externalPhoneId | string | Provider number · max 120 | |
voiceId | string | Voice · max 120 | |
error | string | Why · max 600 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
voice_not_found | The voice must exist here. |
invalid_state | A sync is synced or failed. |
Events it publishes: voice.sync_failed
What it writes: voice_agent (update) — Writes the provider ids and the sync state
Errors: 401 no or expired credential · 403 the credential does not hold voice:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 voice_not_found, invalid_state · 429 slow down
voice.record_turn
Record what Era did on a call — Adds the actions of one reply to the call’s log and keeps its working memory. Machine-only.
Needs voice:handle · changes data
403 machine_only).https://app.getera.io/api/v1/voice_calls/{id}/actions/record_turn- Canonical address:
POST /api/v1/actions/voice.record_turn
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Call · id of voice_call |
actions | object / array | Actions | |
memory | object / array | Working memory | |
externalId | string | Provider call · max 120 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
call_not_open | Only a call in progress takes new actions. |
What it writes: voice_call (update) — Appends actions, replaces memory
Errors: 401 no or expired credential · 403 the credential does not hold voice:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 call_not_open · 429 slow down
voice.record_whatsapp
Record whether WhatsApp calls are on — Meta took the calling settings and the voice provider took the bridged number — or one of them refused. Machine-only.
Needs voice:handle · changes data
403 machine_only).https://app.getera.io/api/v1/voice_agents/{id}/actions/record_whatsapp- Canonical address:
POST /api/v1/actions/voice.record_whatsapp
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Voice · id of voice_agent |
status | string | yes | State · max 10 |
externalPhoneId | string | Provider number · max 120 | |
error | string | Why · max 600 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
voice_not_found | The voice must exist here. |
invalid_state | On, off or failed. |
Events it publishes: voice.sync_failed
What it writes: voice_agent (update) — Writes the WhatsApp line state and the provider number id
Errors: 401 no or expired credential · 403 the credential does not hold voice:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 voice_not_found, invalid_state · 429 slow down
voice.remove
Take an Era’s voice away — The Era stops answering calls. Its call history stays.
Needs voice:delete · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/voice_agents/{id}/actions/remove- Canonical address:
POST /api/v1/actions/voice.remove
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Voice · id of voice_agent |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
voice_not_found | The voice must exist here. |
Events it publishes: voice.agent_changed
What it writes: voice_agent (delete) — Deletes the voice; calls keep their history
curl -X POST https://app.getera.io/api/v1/voice_agents/VOICE_AGENT_ID/actions/remove \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"voice.agent_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.voiceAgents.remove("VOICE_AGENT_ID")era voice_agents remove VOICE_AGENT_ID{
"name": "era__voice__remove",
"arguments": {
"id": "VOICE_AGENT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold voice:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 voice_not_found · 429 slow down
voice.set_enabled
Switch an Era’s voice on or off — On: it answers calls. Off: calls are not answered by Era; nothing is lost.
Needs voice:manage · changes data
https://app.getera.io/api/v1/voice_agents/{id}/actions/set_enabled- Canonical address:
POST /api/v1/actions/voice.set_enabled
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Voice · id of voice_agent |
enabled | boolean | yes | On |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
voice_not_found | The voice must exist here. |
no_voice | A voice is picked before it goes on. |
inbox_not_voice | A line is picked before it goes on. |
Events it publishes: voice.agent_changed
What it writes: voice_agent (update) — Flips the switch
curl -X POST https://app.getera.io/api/v1/voice_agents/VOICE_AGENT_ID/actions/set_enabled \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"enabled":true}'{
"ok": true,
"result": "…",
"events": [
"voice.agent_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.voiceAgents.setEnabled("VOICE_AGENT_ID", {
enabled: true
})era voice_agents set_enabled VOICE_AGENT_ID \
--enabled true{
"name": "era__voice__set_enabled",
"arguments": {
"id": "VOICE_AGENT_ID",
"enabled": true
}
}Errors: 401 no or expired credential · 403 the credential does not hold voice:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 voice_not_found, no_voice, inbox_not_voice · 429 slow down
voice.set_outcome
Correct a call’s outcome — When Era read a call wrong — it was really a booking, a lead, a message.
Needs voice:write · changes data
https://app.getera.io/api/v1/voice_calls/{id}/actions/set_outcome- Canonical address:
POST /api/v1/actions/voice.set_outcome
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Call · id of voice_call |
outcome | string (enum) | yes | Outcome · Allowed values: booked, cancelled, lead, message, answered, transferred, no_action, failed |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
call_not_finished | Only a finished call is corrected. |
What it writes: voice_call (update) — Writes the outcome
curl -X POST https://app.getera.io/api/v1/voice_calls/VOICE_CALL_ID/actions/set_outcome \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"outcome":"booked"}'{
"ok": true,
"result": "…",
"events": []
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.voiceCalls.setOutcome("VOICE_CALL_ID", {
outcome: "booked"
})era voice_calls set_outcome VOICE_CALL_ID \
--outcome "booked"{
"name": "era__voice__set_outcome",
"arguments": {
"id": "VOICE_CALL_ID",
"outcome": "booked"
}
}Errors: 401 no or expired credential · 403 the credential does not hold voice:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 call_not_finished · 429 slow down
voice.set_whatsapp_line
Answer WhatsApp calls on my computer — Era answers the WhatsApp voice calls on your computer’s WhatsApp — which ones, after how many seconds, and whether it may also call people for you. No phone number needed.
Needs voice:manage · changes data
https://app.getera.io/api/v1/voice_whatsapp_lines/actions/set_whatsapp_line- Canonical address:
POST /api/v1/actions/voice.set_whatsapp_line
Input
| Field | Type | Required | Notes |
|---|---|---|---|
voiceAgentId | uuid | yes | Era that talks · id of voice_agent |
deviceId | uuid | Computer · The Era Desktop whose WhatsApp it answers; empty = any of yours. | |
enabled | boolean | On | |
answerRule | string (enum) | Which calls · Allowed values: all, unknown, contacts, in_hours, after_hours, workflow | |
ringSeconds | number | Let it ring first (seconds) · min 0 · max 40 | |
ownerName | string | Whose assistant Era says it is · max 120 | |
outboundEnabled | boolean | May call people for me | |
answerVideo | boolean | Also answer video calls (voice only) |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
voice_not_found | The talking Era must have a voice here. |
What it writes: voice_whatsapp_line (upsert) — Writes the workspace’s one WhatsApp-on-the-computer line
curl -X POST https://app.getera.io/api/v1/voice_whatsapp_lines/actions/set_whatsapp_line \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"voiceAgentId":"VOICE_AGENT_ID"}'{
"ok": true,
"result": "…",
"events": []
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.voiceWhatsappLines.setWhatsappLine({
voiceAgentId: "VOICE_AGENT_ID"
})era voice_whatsapp_lines set_whatsapp_line \
--voice-agent-id "VOICE_AGENT_ID"{
"name": "era__voice__set_whatsapp_line",
"arguments": {
"voiceAgentId": "VOICE_AGENT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold voice:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 voice_not_found · 429 slow down
voice.start_call
Have Era call someone — Era rings a customer now from your number — to confirm a booking, follow up a lead, say an order is ready. Minutes are checked first.
Needs voice:write · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/voice_calls/actions/start_call- Canonical address:
POST /api/v1/actions/voice.start_call
Input
| Field | Type | Required | Notes |
|---|---|---|---|
voiceAgentId | uuid | yes | Era · id of voice_agent |
to | string | yes | Number · Any way it is written: 0532 123 45 67 or +90… · max 40 |
purpose | string | What the call is for · max 1000 | |
customerId | uuid | Customer · id of customer | |
channel | string (enum) | Call on · Allowed values: phone, whatsapp |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
voice_not_found | The voice must exist here. |
voice_off | The voice must be on. |
outbound_off | The Era must be allowed to make calls. |
bad_number | The number must be callable. |
no_phone_line | A connected number is needed to call out. |
whatsapp_calling_off | A WhatsApp call needs WhatsApp calls switched on for this Era. |
minutes_used_up | The month’s call minutes must not be used up. |
outbound_limit | The daily outbound limit must not be reached. |
no_source | The workspace’s own voice key must be able to run it. |
budget_exceeded | The AI budget must have room. |
Events it publishes: voice.call_started
What it writes: voice_call (insert) — Queues the call
curl -X POST https://app.getera.io/api/v1/voice_calls/actions/start_call \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"voiceAgentId":"VOICE_AGENT_ID","to":"Number"}'{
"ok": true,
"result": "…",
"events": [
"voice.call_started"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.voiceCalls.startCall({
voiceAgentId: "VOICE_AGENT_ID",
to: "Number"
})era voice_calls start_call \
--voice-agent-id "VOICE_AGENT_ID" \
--to "Number"{
"name": "era__voice__start_call",
"arguments": {
"voiceAgentId": "VOICE_AGENT_ID",
"to": "Number"
}
}Errors: 401 no or expired credential · 403 the credential does not hold voice:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 voice_not_found, voice_off, outbound_off, bad_number, no_phone_line, whatsapp_calling_off, minutes_used_up, outbound_limit, no_source, budget_exceeded · 429 slow down
voice.start_whatsapp_call
Call someone on WhatsApp with Era — Era calls a person on your computer’s WhatsApp, talks to them about what you asked, hangs up and tells you how it went — summary, outcome, transcript.
Needs voice:write · reaches outside (sends, publishes or charges)
https://app.getera.io/api/v1/voice_calls/actions/start_whatsapp_call- Canonical address:
POST /api/v1/actions/voice.start_whatsapp_call
Input
| Field | Type | Required | Notes |
|---|---|---|---|
to | string | Number · Any way it is written: 0532 123 45 67 or +90… · max 40 | |
customerId | uuid | Customer · id of customer | |
name | string | Name · max 120 | |
goal | string | yes | What to talk about · e.g. “Ask if she can come on Friday at 3 instead of Thursday.” · max 1000 |
script | string | Points to cover · max 2000 | |
exact | boolean | Exactly this WhatsApp chat · The name is the chat the person picked from Era’s choices. | |
threadId | string | Era chat · Where Era tells the result when the call ends. · max 200 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
whatsapp_line_off | Era must be set up for WhatsApp on a computer. |
whatsapp_outbound_off | Era must be allowed to make WhatsApp calls. |
no_one_to_call | A WhatsApp name, a number or a customer is needed. |
bad_number | The number must be callable. |
voice_not_found | The talking Era must have a voice. |
voice_off | The voice must be on. |
minutes_used_up | The month’s call minutes must not be used up. |
outbound_limit | The daily outbound limit must not be reached. |
no_source | Something must be able to pay for the minutes. |
budget_exceeded | The AI budget must have room. |
Events it publishes: voice.whatsapp_call_requested
What it writes: voice_call (insert) — Queues the call for the computer, with its reservation
curl -X POST https://app.getera.io/api/v1/voice_calls/actions/start_whatsapp_call \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"goal":"What to talk about"}'{
"ok": true,
"result": "…",
"events": [
"voice.whatsapp_call_requested"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.voiceCalls.startWhatsappCall({
goal: "What to talk about"
})era voice_calls start_whatsapp_call \
--goal "What to talk about"{
"name": "era__voice__start_whatsapp_call",
"arguments": {
"goal": "What to talk about"
}
}Errors: 401 no or expired credential · 403 the credential does not hold voice:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 whatsapp_line_off, whatsapp_outbound_off, no_one_to_call, bad_number, voice_not_found, voice_off, minutes_used_up, outbound_limit, no_source, budget_exceeded · 429 slow down
voice.take_over_whatsapp_call
Take the WhatsApp call over from Era — “Devral”: Era stops talking and the call’s sound goes back to your own microphone and speakers on the computer.
Needs voice:write · reaches outside (sends, publishes or charges)
https://app.getera.io/api/v1/voice_calls/{id}/actions/take_over_whatsapp_call- Canonical address:
POST /api/v1/actions/voice.take_over_whatsapp_call
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Call · id of voice_call |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
call_not_live | Only a WhatsApp call going on now is taken over, once. |
Events it publishes: voice.whatsapp_takeover
What it writes: voice_call (update) — Marks the moment; the computer hands the sound back
curl -X POST https://app.getera.io/api/v1/voice_calls/VOICE_CALL_ID/actions/take_over_whatsapp_call \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"voice.whatsapp_takeover"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.voiceCalls.takeOverWhatsappCall("VOICE_CALL_ID")era voice_calls take_over_whatsapp_call VOICE_CALL_ID{
"name": "era__voice__take_over_whatsapp_call",
"arguments": {
"id": "VOICE_CALL_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold voice:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 call_not_live · 429 slow down
voice.update
Change an Era’s voice — Voice, greeting, language, opening-hours behaviour, transfer number, abilities, knowledge and where it answers.
Needs voice:manage · changes data
https://app.getera.io/api/v1/voice_agents/{id}/actions/update- REST alias:
PATCH /api/v1/voice_agents/{id} - Canonical address:
POST /api/v1/actions/voice.update
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Voice · id of voice_agent |
inboxId | uuid | Phone line (Inbox channel) · id of inbox | |
voiceId | string | Voice · max 120 | |
voiceName | string | Voice name · max 120 | |
language | string | Language · max 8 | |
greeting | string | Greeting · max 600 | |
persona | string | Manner · max 4000 | |
afterHours | string (enum) | Outside opening hours · Allowed values: answer, take_message, transfer, closed_message | |
afterHoursMessage | string | Closed message · max 600 | |
transferNumber | string | Transfer number · max 32 | |
tools | object / array | What it may do · check_availability, book_appointment, cancel_appointment, order_status, capture_lead, answer_question, take_message, transfer_to_human | |
approvalTools | object / array | Ask me first for | |
knowledgeBaseIds | object / array | Knowledge bases | |
maxCallMinutes | number | Longest call (minutes) · min 1 · max 60 | |
widgetEnabled | boolean | Voice button on my website | |
widgetSiteId | uuid | Website · id of site | |
widgetLabel | string | Button text · max 60 | |
appEnabled | boolean | Talk to it in the app | |
outboundEnabled | boolean | May make calls | |
disclosureEnabled | boolean | Say it is an AI and the call is recorded | |
confirmBooking | boolean | Send a WhatsApp/SMS confirmation after a booking |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
voice_not_found | The voice must exist here. |
inbox_not_voice | Calls land on an active voice channel. |
inbox_taken | One Era answers one line. |
unknown_tool | Every ability must be one a voice Era has. |
bad_transfer_number | A transfer number is a full international phone number. |
transfer_needs_number | Putting calls through needs a transfer number. |
unknown_language | The language must be one the voice speaks. |
unknown_knowledge_base | Knowledge bases are picked by id. |
site_not_found | The website must be this workspace’s. |
Events it publishes: voice.agent_changed
What it writes: voice_agent (update) — Writes the settings and marks the provider copy for re-sync
curl -X POST https://app.getera.io/api/v1/voice_agents/VOICE_AGENT_ID/actions/update \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"inboxId":"INBOX_ID"}'{
"ok": true,
"result": "…",
"events": [
"voice.agent_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.voiceAgents.update("VOICE_AGENT_ID", {
inboxId: "INBOX_ID"
})era voice_agents update VOICE_AGENT_ID \
--inbox-id "INBOX_ID"{
"name": "era__voice__update",
"arguments": {
"id": "VOICE_AGENT_ID",
"inboxId": "INBOX_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold voice:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 voice_not_found, inbox_not_voice, inbox_taken, unknown_tool, bad_transfer_number, transfer_needs_number, unknown_language, unknown_knowledge_base, site_not_found · 429 slow down
voice.whatsapp_ring
A WhatsApp call is ringing on the computer — Era Desktop reports a ringing WhatsApp call; the line’s rule says whether Era answers it, leaves it to you, or a workflow decides. Machine-only.
Needs voice:handle · reaches outside (sends, publishes or charges)
403 machine_only).https://app.getera.io/api/v1/voice_calls/actions/whatsapp_ring- Canonical address:
POST /api/v1/actions/voice.whatsapp_ring
Input
| Field | Type | Required | Notes |
|---|---|---|---|
deviceId | uuid | Computer | |
callerName | string | Caller · max 120 | |
callerNumber | string | Number · max 40 | |
known | boolean | A customer | |
customerId | uuid | Customer · id of customer | |
openNow | boolean | Open now | |
video | boolean | Video call |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
whatsapp_line_off | Era must be set to answer WhatsApp calls on a computer. |
wrong_computer | Only the chosen computer answers. |
voice_not_found | The talking Era must have a voice. |
voice_off | The talking Era’s voice must be on. |
Events it publishes: voice.whatsapp_ringing
What it writes: voice_call (insert) — A ringing WhatsApp call with Era’s decision
Errors: 401 no or expired credential · 403 the credential does not hold voice:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 whatsapp_line_off, wrong_computer, voice_not_found, voice_off · 429 slow down
Data models
VoiceAgent
Table voice_agent · Resource voice_agents
GET /api/v1/voice_agentsGET /api/v1/voice_agents/{id}PATCH /api/v1/voice_agents/{id}→voice.update
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
agent_id | string (uuid) · uuid | ai_agent | ||
inbox_id | string (uuid) · uuid | yes | inbox | |
is_enabled | boolean · bool | |||
provider | string · text | elevenlabs, mock | ||
payer | string · text | |||
voice_id | string · text | yes | ||
voice_name | string · text | yes | ||
language | string · text | |||
greeting | string · text | |||
persona | string · text | |||
after_hours | string · text | answer, take_message, transfer, closed_message | ||
after_hours_message | string · text | yes | ||
transfer_number | string · text | yes | ||
tools | string[] · text[] | |||
approval_tools | string[] · text[] | |||
knowledge_base_ids | string[] · uuid[] | |||
max_call_minutes | integer · int4 | |||
widget_enabled | boolean · bool | |||
widget_site_id | string (uuid) · uuid | yes | site | |
widget_label | string · text | yes | ||
app_enabled | boolean · bool | |||
outbound_enabled | boolean · bool | |||
external_agent_id | string · text | yes | ||
external_phone_id | string · text | yes | ||
sync_status | string · text | pending, synced, failed | ||
sync_error | string · text | yes | ||
synced_at | string (date-time) · timestamptz | yes | ||
created_by | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz | |||
whatsapp_inbox_id | string (uuid) · uuid | yes | inbox | |
whatsapp_phone_id | string · text | yes | ||
whatsapp_status | string · text | off, pending, on, failed | ||
whatsapp_error | string · text | yes | ||
disclosure_enabled | boolean · bool | |||
confirm_booking | boolean · bool |
Primary key: id
Checks:
CHECK (((after_hours_message IS NULL) OR (length(after_hours_message) <= 600)))CHECK ((approval_tools <@ tools))CHECK ((length(greeting) <= 600))CHECK ((language ~ '^[a-z]{2}(-[A-Z]{2})?$'::text))CHECK (((max_call_minutes >= 1) AND (max_call_minutes <= 60)))CHECK ((payer = 'own_key'::text))CHECK ((length(persona) <= 4000))CHECK (((sync_error IS NULL) OR (length(sync_error) <= 600)))CHECK ((tools <@ ARRAY['check_availability'::text, 'book_appointment'::text, 'cancel_appointment'::text, 'order_status'::text, 'capture_lead'::text, 'answer_question'::text, 'take_message'::text, 'transfer_to_human'::text]))CHECK (((transfer_number IS NULL) OR (transfer_number ~ '^\+[1-9][0-9]{6,14}$'::text)))CHECK (((whatsapp_error IS NULL) OR (length(whatsapp_error) <= 600)))CHECK (((widget_label IS NULL) OR (length(widget_label) <= 60)))
VoiceCall
Table voice_call · Resource voice_calls
GET /api/v1/voice_callsGET /api/v1/voice_calls/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
voice_agent_id | string (uuid) · uuid | yes | voice_agent | |
agent_id | string (uuid) · uuid | yes | ai_agent | |
inbox_id | string (uuid) · uuid | yes | inbox | |
conversation_id | string (uuid) · uuid | yes | conversation | |
customer_id | string (uuid) · uuid | yes | customer | |
channel | string · text | phone, widget, app, whatsapp | ||
direction | string · text | inbound, outbound | ||
status | string · text | queued, awaiting_permission, in_progress, completed, missed, failed, cancelled | ||
from_number | string · text | yes | ||
to_number | string · text | yes | ||
caller_name | string · text | yes | ||
purpose | string · text | yes | ||
external_id | string · text | yes | ||
external_call_sid | string · text | yes | ||
started_at | string (date-time) · timestamptz | yes | ||
ended_at | string (date-time) · timestamptz | yes | ||
duration_sec | integer · int4 | |||
transcript | object · jsonb | |||
summary | string · text | yes | ||
outcome | string · text | yes | ||
actions | object · jsonb | |||
memory | object · jsonb | |||
recording | boolean · bool | |||
recording_file_id | string (uuid) · uuid | yes | ||
language | string · text | yes | ||
end_reason | string · text | yes | ||
principal_user_id | string (uuid) · uuid | yes | app_user | |
payer | string · text | yes | ||
usage_id | string (uuid) · uuid | yes | ||
source_kind | string · text | yes | ||
cost_usd | string (decimal) · numeric | |||
error | string · text | yes | ||
created_by | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz | |||
bridge | string · text | yes | ||
wa_call_id | string · text | yes | ||
device_id | string (uuid) · uuid | yes | ||
ring_decision | string · text | yes | ||
ring_reason | string · text | yes | ||
handover_requested_at | string (date-time) · timestamptz | yes | ||
handover_by | string (uuid) · uuid | yes | app_user |
Primary key: id
Checks:
CHECK ((jsonb_typeof(actions) = 'array'::text))CHECK (((bridge IS NULL) OR (bridge = ANY (ARRAY['twilio'::text, 'sip'::text, 'elevenlabs'::text, 'whatsapp_sip'::text, 'desktop'::text, 'web'::text]))))CHECK ((duration_sec >= 0))CHECK (((error IS NULL) OR (length(error) <= 600)))CHECK ((jsonb_typeof(memory) = 'object'::text))CHECK (((outcome IS NULL) OR (outcome = ANY (ARRAY['booked'::text, 'cancelled'::text, 'lead'::text, 'message'::text, 'answered'::text, 'transferred'::text, 'no_action'::text, 'failed'::text]))))CHECK (((payer IS NULL) OR (payer = 'own_key'::text))) NOT VALIDCHECK (((purpose IS NULL) OR (length(purpose) <= 1000)))CHECK (((ring_decision IS NULL) OR (ring_decision = ANY (ARRAY['answer'::text, 'ignore'::text, 'wait'::text]))))CHECK (((ring_reason IS NULL) OR (length(ring_reason) <= 300)))CHECK (((source_kind IS NULL) OR (source_kind = ANY (ARRAY['native'::text, 'byos'::text, 'byok'::text])))) NOT VALIDCHECK ((jsonb_typeof(transcript) = 'array'::text))
VoiceWhatsappLine
Table voice_whatsapp_line · Resource voice_whatsapp_lines
GET /api/v1/voice_whatsapp_linesGET /api/v1/voice_whatsapp_lines/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
voice_agent_id | string (uuid) · uuid | voice_agent | ||
device_id | string (uuid) · uuid | yes | ||
is_enabled | boolean · bool | |||
answer_rule | string · text | all, unknown, contacts, in_hours, after_hours, workflow | ||
ring_seconds | integer · int4 | |||
owner_name | string · text | yes | ||
outbound_enabled | boolean · bool | |||
answer_video | boolean · bool | |||
created_by | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK (((owner_name IS NULL) OR (length(owner_name) <= 120)))CHECK (((ring_seconds >= 0) AND (ring_seconds <= 40)))
Events
Workflows and webhooks react to these events.
| Event | What happens | Payload |
|---|---|---|
voice.agent_changed | An Era got a voice, or its voice settings changed. | id, agentId, enabled |
voice.appointment_booked | Era booked an appointment on a call. | id, voiceAgentId, customerId, conversationId, appointmentId, channel, service, appointmentDate, startTime, callerName, callerNumber |
voice.call_finished | A call ended and was filed in the Inbox with its summary and outcome. | id, voiceAgentId, agentId, customerId, conversationId, outcome, channel, direction, durationSec, summary, fromNumber, recipientUserId |
voice.call_missed | A call did not connect (busy, no answer, failed). | id, voiceAgentId, customerId, number, direction, reason, recipientUserId |
voice.call_started | A call began — phone, WhatsApp, website button or app. | id, voiceAgentId, channel, direction, customerId |
voice.lead_captured | Era took a new lead on a call. | id, voiceAgentId, customerId, conversationId, leadId |
voice.message_taken | Era took a message on a call. | id, voiceAgentId, customerId, conversationId, taskId, summary, recipientUserId |
voice.sync_failed | The voice provider refused an Era’s setup. | id, error, recipientUserId |
voice.transfer_requested | A caller was put through to a person. | id, voiceAgentId, customerId, conversationId |
voice.whatsapp_call_finished | A WhatsApp call Era took on the computer ended — caller, transcript, summary, outcome and what it was about. | id, voiceAgentId, direction, callerName, callerNumber, customerId, conversationId, transcript, summary, outcome, outcomeLabel, intent, goal, durationSec, recipientUserId |
voice.whatsapp_call_requested | Someone asked Era to call a person on WhatsApp from the computer. | id, voiceAgentId, deviceId, to, name, goal, customerId, recipientUserId |
voice.whatsapp_ringing | A WhatsApp call is ringing on your computer — with who it is and whether Era takes it. | id, voiceAgentId, callerName, callerNumber, customerId, known, decision |
voice.whatsapp_takeover | A person took a WhatsApp call over from Era. | id, byUserId |