BE VOICEFor developers
BE Voice · Developer guide

Agent-ready APIs

A BE Voice AI agent can read from your system and write to it while it is on a phone call, by calling your REST API. This guide says what your API should look like so that works well: the agent finds the right record, understands the reply, says the right thing, and writes safely.

Any JSON API can be connected. Each rule below is marked Must (without it the call to your API fails or is unsafe) or Should (it works, but calls are slower, vaguer or need more setting up).

Your app can also place calls: give BE Voice a number, a goal and a brief, and its AI holds the conversation, takes your instructions while the call is on, and reports how it went. See Placing calls, or connect an AI assistant.

What BE Voice does

How it works

A business connects your API to its AI agent once, with no code:

  1. A connection: your API's base address and one sign-in header.
  2. Tools: one per endpoint the agent may use. Each has a path, the values the agent must collect from the caller (an order number, a date), and a line saying when to use it.
  3. What the agent gets: the fields of your reply worth saying, and the business's notes on how to read them ("BO means back-ordered: offer a call back").

On a call, the agent collects the values, BE Voice sends the request, and the agent words its answer from your reply. A business can also give a fixed sentence filled from your reply, for wording that must be exact.

Caller:  "Where's my order? It's A1001."
Agent    → GET https://api.yourshop.example/v2/orders/A1001
Your API → {"order": {"number": "A1001", "status": "SHP", "eta": "2026-10-07", "carrier": "Australia Post"}}
Agent:   "It shipped with Australia Post and should arrive on Tuesday the 7th."

Address and sign-in

Rule
HTTPS on a public address. Private and internal addresses are refused.Must
One base address per system; every tool is a path under it.Must
Sign-in is one fixed header: an API key or a long-lived token (Authorization: Bearer … or X-Api-Key: …). It is stored encrypted and sent on every request. Tokens that must be refreshed (OAuth flows) aren't supported.Must
No redirects: answer on the address called.Must
Give BE Voice its own key, limited to what the agent needs, so it can be revoked alone.Should
Check the signature on writes (below).Should

Checking a request came from BE Voice

Every request carries X-BE-Voice-Timestamp (Unix seconds) and X-BE-Voice-Signature: sha256= followed by the hex HMAC-SHA256 of <timestamp>.<raw body>, keyed with the business's webhook secret. The body is empty for a GET. Compute it over the raw bytes and reject timestamps more than 5 minutes old. BE Voice has no fixed source addresses to allow-list.

Requests

What BE Voice sends
MethodsGET, POST, PUT, PATCH, DELETE
PathThe tool's path with {name} filled from what the agent collected, each value escaped as one path segment: /orders/{order_number} becomes /orders/A1001
Query stringGET and DELETE: every collected value not used in the path, as ?name=value
BodyPOST, PUT, PATCH: JSON, from a template the business sets ({"text": "{note}", "source": "phone"}), or the collected values as one JSON object
HeadersAccept: application/json, Content-Type: application/json (with a body), User-Agent: be-voice-tools/1, your sign-in header, the signature headers, and the call headers below

Which call is this?

HeaderValue
X-BE-Voice-Call-IdThe phone call's ID (cal_…). Absent on a test from the console.
X-BE-Voice-ToolThe tool's name, e.g. get_order
Idempotency-Key<call id>:<tool>:<hash of the request>. The same when the same tool sends the same request again on the same call; different otherwise. Tests get a fresh key each time (test-…).

Rules for your API

Rule
Look records up by something a caller knows or the business already holds: an order or invoice number, a phone number, a name with a postcode. Not an internal database ID, unless your system gave it to BE Voice with the call.Must
Ignore fields and headers you don't know.Must
Take values as people say them. They arrive as text the agent heard: accept inv 881, INV-881 and inv881; ignore case and spaces in identifiers.Should
Dates and amounts: accept ISO dates (2026-10-10) and plain amounts (500, 500.00). Tell the business which format you need; it goes in the tool's "when to use" line, so the agent is told to send it that way.Should
One request does the whole job. Don't need two calls where one could answer.Should

Replies

The agent reads your reply, together with the business's notes on what the values mean, and words the answer itself.

Rule
JSON, UTF-8, up to 64 KB. More is a failure.Must
Statuses from a fixed, documented list of codes or words (SHP, BO, CAN). The business writes one line per value for the agent, so the list must be stable and complete.Must
Nothing the caller mustn't hear. A business can drop fields before the agent sees them, but the safest secret is one the reply never had: no card numbers, passwords or other customers' data.Must
Small. What reaches the agent is cut at 2,000 characters. Return one record, or a short list (10 items or fewer) with a way to narrow it.Should
One record under a named key, with plain field names.Should
Answer the next question in the same reply: the delivery date with the status, the balance with the amount, the payment link with the invoice.Should
Unknown is null, not "", 0 or "N/A".Should
Dates as ISO 8601 with an offset, or as plain words; not Unix timestamps. Money as a number in a known currency, or as text ("$1,240.50").Should
No HTML or markup in text fields.Should
{
  "order": {
    "number": "A1002",
    "status": "BO",
    "eta": null,
    "restock_date": "2026-10-13",
    "items": [{"name": "Tap washer kit", "qty": 3}]
  }
}

Optional: word the answer yourself

If your system can say it better than a general assistant would, return a top-level "answer" sentence with the record under "data". The agent is given your sentence first.

{"answer": "Order A1002 is waiting on stock, due back in on Monday 13 October.", "data": {"status": "BO"}}

Status codes

Your answerWhat the agent is told
200, 201, 202 with JSONYour reply
204, or 2xx with no body"Done."
404"Nothing was found", with your body. Use it for "no such record", never 200 with an empty body.
400, 409, 422 with a sentence"The system refused that: your sentence". The agent fixes the value and tries again, or tells the caller.
401, 403A failure. The connection's key is wrong.
429, 5xx, a timeout, not JSONA failure. The agent says it couldn't check and offers a message or a call back. Nothing is retried.

Errors the agent can act on

Send refusals as a sentence a person could act on. It is read from error (a string, or an object with message), or else message or detail, up to 300 characters, and it may be said to the caller, so keep it plain and free of internals. A refusal with no sentence is treated as a failure.

HTTP 409
{"error": "That time has just been taken. The next free times are 2:30 pm and 4 pm.", "code": "slot_taken"}

Timing and limits

Rule
Answer within the tool's wait: 10 seconds by default; a business can set 3 to 20. After that it is a failure.Must
Aim for under 2 seconds. The caller is on the line while you work.Should
At most 8 tool requests per phone call, across all tools, and one attempt each.

Writes

Rule
Safe to receive twice. The agent may repeat a write if the caller repeats themselves; the same promise, note or booking must not double up. Check the Idempotency-Key.Must
Check the values yourself. They are what the agent heard; refuse what makes no sense with 422 and a sentence.Must
Return what was created, with its ID and the values as stored. The agent reads them back to the caller.Should
Make the agent's writes undoable by staff, and mark them as coming from the phone agent (the body can carry "source": "phone").Should
POST /invoices/INV-881/promises
Idempotency-Key: cal_8f2…:record_promise_to_pay:3b1c9a0e5d7f2a44
{"amount": "500", "date": "2026-10-10", "source": "phone"}

HTTP 201
{"promise": {"id": "pr_1042", "invoice": "INV-881", "amount": "500.00", "date": "2026-10-10"}}

Who is on the line

BE Voice can't prove that a caller is who they say. An API that returns personal or account details makes that decision itself.

Rule
For details only the customer should hear, require a second value the caller gives (a postcode, a date of birth, the amount of the last invoice) with the lookup, and answer 404 unless both match.Must for personal data
On calls the business places, the business chose who to ring and the agent confirms the person first. Still return only that contact's records.Should

Describing your API

A business sets up each tool from your documentation. Give it, for every endpoint the agent may use:

You documentIt becomes
A one-line purpose, and the formats you expectThe tool's "when to use" line
The values the endpoint needs, named as in the path, query or bodyWhat the agent collects from the caller
Which fields are safe and useful to sayThe fields the agent is given
Every status value, what it means, and what should happen nextThe agent's notes on how to read the reply
An example replyA quick test when connecting

Starting the AI from your system

The same connection works the other way: your system can have the AI place calls and hear how they went.

ToYour system
Have the AI call one personStarts a call through the BE Voice API, naming the agent and giving the facts for this call. The result (outcome, details, a note, the transcript) arrives at your webhook when the call ends.
Have it work through a listProvides two endpoints: one returns the people to call with the facts for each; the other receives each person's result, and a final summary when the run is finished.
Trust what arrivesChecks the signature, as above, and treats the call's ID as the key for repeats.

Put the IDs your endpoints need (an invoice number, an appointment ID) in the facts you send with each call, so the agent never has to ask the person for them. The full API reference is available to customers.

Checklist

  • HTTPS, public, one base address, one fixed sign-in header
  • Lookups by something a caller knows; loose input accepted
  • Small JSON replies with one named record and a fixed list of statuses
  • 404 for not found; refusals as {"error": "a sentence"} with 400, 409 or 422
  • Answers in under 2 seconds, always within 10
  • Writes check the Idempotency-Key and return what was created
  • Nothing in a reply that a caller mustn't hear; personal data needs a second value
  • Each endpoint documented with its purpose, status values, safe fields and an example

To check your API, the business adds a connection and a tool in its BE Voice console and presses Test with real values. It shows your reply as it came, and exactly what the agent would be told.

Place a call

Send the number, the goal and your brief. Nothing needs setting up in BE Voice first: the AI works from what you send, opens the call in its own words (or with your greeting), and finishes when the goal is met or clearly can't be. Calls come from one of your account's numbers.

curl https://voice.binaryelements.com/v1/calls \
  -H "Authorization: Bearer $BE_VOICE_KEY" -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "to": "+61400000000",
    "task": {
      "goal": "Confirm Sam can take delivery of order A1002 on Friday morning",
      "prompt": "You are Alex from Acme Parts dispatch. Offer Friday 8-12 or 12-5; if neither works, find a day that does.",
      "context": {"customer_name": "Sam", "order": "A1002"},
      "questions": [{"key": "window", "ask": "Which window suits you?", "type": "choice",
                     "choices": ["Friday 8-12", "Friday 12-5", "Other"], "required": true}],
      "human": {"target": "ext:101"}
    },
    "callback_url": "https://app.example.com/hooks/be-voice",
    "metadata": {"ref": "ord_A1002"}
  }'
FieldWhat it does
task.goalWhat counts as done. Required.
task.promptYour brief for the AI: who it is, who it calls for, what it may say or offer.
task.contextFacts for this call (names, order numbers), so the AI never has to ask for them.
task.questionsQuestions to ask in order. Each answer is checked against its type (text, number, yes_no, choice, date) and returned under its key.
task.humanWhere the call goes when the person asks for a human: an extension (ext:101) or a phone number, with an optional then if nobody answers.
task.outcomesHow a call can end. Default: achieved, not_achieved.
task.greetingWhat the AI says first: after their hello, or first if nobody speaks.
task.closing, voice, voicemail_messageOptional wording and voice.
fromOptional: one of your numbers. Default: your first.
callback_urlOptional: HTTPS address that receives the result when the call is done.

The reply is the call, with its id. If the person asks not to be called again, their number goes on your account's do-not-call list, and calls to numbers on that list are refused with do_not_call.

When the person answers with "hello", the AI waits until they finish, then says your greeting. If nobody speaks, it speaks first. Without a greeting, it opens in its own words.

Steer a call

While the call is on, you can give the AI a new instruction. The person doesn't hear it; the AI acts on it at the next natural moment, and it shows in the transcript as an instruction line.

curl https://voice.binaryelements.com/v1/calls/$CALL_ID/steer \
  -H "Authorization: Bearer $BE_VOICE_KEY" \
  -d '{"instruction": "Also ask whether Saturday works."}'

409 means there is no conversation to steer right now: the phone is still ringing, the call is being transferred, or it has ended.

Get the result

Ask for it, optionally waiting until the call is done (wait in seconds), or let it come to your callback_url as a call.result webhook, signed as described above.

curl "https://voice.binaryelements.com/v1/calls/$CALL_ID/result?wait=60" -H "Authorization: Bearer $BE_VOICE_KEY"

{
  "call_id": "cal_…", "status": "completed", "final": true, "duration_sec": 74,
  "outcome": "completed",
  "result": {"outcome": "completed", "answers": {"window": "Friday 8-12"}, "note": "Gate code 4411."},
  "summary": "Sam will take delivery Friday morning.",
  "transcript": [{"speaker": "agent", "text": "…"}, {"speaker": "caller", "text": "…"}],
  "metadata": {"ref": "ord_A1002"}
}
FieldMeaning
finalfalse while the call is on (the transcript so far) and for a few seconds after, while the summary is written.
outcomeThe AI's outcome; or, if nobody answered, no-answer, busy, failed or canceled.
resultAnswers by question key, details, the AI's note, opt_out if they asked not to be called.

Connect an AI assistant

BE Voice is also an MCP server, so an AI assistant can place calls, follow them, steer them and read the results itself. Add it to any MCP client that supports remote servers:

{
  "mcpServers": {
    "be-voice": {
      "type": "http",
      "url": "https://voice.binaryelements.com/mcp",
      "headers": { "Authorization": "Bearer YOUR_BE_VOICE_KEY" }
    }
  }
}
ToolDoes
place_callCalls a number with a goal and a brief; returns the call id
wait_for_callWaits for the call to finish and returns the result
get_callThe call now: status, live transcript, result
steer_callA new instruction for the AI on the call
hang_upEnds the call
list_caller_numbers, list_voices, list_recent_callsWhat calls can come from, the voices, recent calls

Then just ask: "Call Sam on +61 400 000 000 and confirm Friday morning works for the delivery; tell me what they say." Placing calls through the API and MCP is switched on per account: ask Binary Elements.