Calling into a Flow
A Flow is a call script you build in the admin center (Voice Features → Flows): steps that speak text, collect keypad presses, call your own URL and route the call. Normally a Flow answers calls that come in. With one API request you can also start a call out: we ring a number, and when it is answered, the Flow runs — with values you sent (a name, a date, an amount) spoken in its steps.
your system ──POST /flows/{flowId}/calls──▶ Telebroad rings "to"
│ answered
▼
Flow runs: "Hi Dana, your appointment with
Dr. Patel is Tuesday at 10:30. Press 1 to
confirm, 2 to cancel."
│ caller presses 1
▼
answer saved on the call + your URL is called
:::caution This endpoint is not part of the public REST API
It lives at https://api.account.telebroad.com/api/v1/flows/{flowId}/calls —
under /api/v1, not /api/public/v1. It takes only an API key, its
responses use a different envelope from the one in
Envelope, and some of its errors are plain text.
Everything you need is on this page and in the
reference entry. It is not
in the playground.
:::
1. Build the Flow
In the admin center go to Voice Features → Flows, then Add Flow (or edit
an existing one) and Save. The flow's id is the number at the end of the
editor's address, …/flows/742 — that is the flowId you will call.
Write the words you want to change per call as {placeholders} in a step's
text-to-speech text:
Hi {name}, this is a reminder of your appointment with {doctor} on {date} at {time}.
Each {name} is filled from the params you send in the request. The rules:
- Use text-to-speech for any step with placeholders. A step whose audio is an uploaded, recorded or library sound plays that sound, and nothing is filled in.
- Names must match exactly.
{appointment_date}is filled only by aparamskeyappointment_date. Use letters, digits,_and-. - A placeholder with no value is read out as written. If you forget
name, the caller hears the word inside the braces. Send every placeholder the flow uses. - Values are spoken exactly as sent.
"4719"is read as "four thousand seven hundred nineteen"; send"4 7 1 9"to have it read digit by digit. Write dates and times the way they should sound:"Tuesday, November 4","10:30 AM".
The editor shows "Contains {values} filled in from your API call" on a step that uses them, and lets you type preview values to hear it. The Test call button on the flow rings a phone of your choice with values you type — the quickest way to hear the real thing before writing code.
Where else params are filled
The same values also fill {placeholders} in a step's URL and request
Body, a Smartsheet lookup's Look up value, and text your step URL
returns. In a URL they are inserted as sent, not URL-encoded — a space or &
breaks it. If a flow variable has the same name as a params key, the
params value wins for that call. The full rules are in
Placeholders & values.
2. Create an API key
Integrations → API Keys → Create API key. You need the API Keys permission on your role.
| Field | Set it to |
|---|---|
| Name | Anything — e.g. "Appointment bot". |
| What this key can do | Tick flows:call. Nothing else is needed for this endpoint, and nothing else makes it work. |
| Limit it to specific objects | Appears once flows:call is ticked. Pick the flow (or flows) this key may call. Leave it empty and the key can call every flow on the account. |
| Allowed IPs (optional) | Your servers' public addresses or CIDR blocks. Requests from anywhere else are refused. |
| Expires | Required — 7 days to 1 year. You get reminder emails before it lapses. |
The key (tbk_live_…) is shown once. Store it in your secret manager.
Recommended: one key per integration, pinned to the one flow it calls, with an IP allowlist. A leaked key can then only ring numbers through that flow, from your servers.
:::note The key's role does not apply here
The key's Runs as user's role is not consulted by this endpoint — a key with
flows:call can call any flow it is allowed by its own flow pins, whatever that
user's role says. Narrow the key itself.
:::
The Test call dialog has a Create button that opens this same form with
the flow pinned and flows:call already ticked.
3. Place the call
curl -X POST 'https://api.account.telebroad.com/api/v1/flows/742/calls' \
-H "Authorization: Bearer $TB_KEY" \
-H 'Content-Type: application/json' \
-d '{
"to": "12125550188",
"from": "12125550100",
"params": {
"name": "Dana",
"doctor": "Dr. Patel",
"date": "Tuesday, November 4",
"time": "10:30 AM",
"appointment_id": "A-20931"
},
"ttlSec": 1800
}'
| Field | Required | Meaning |
|---|---|---|
to | yes | The number to ring. Send US numbers in 11-digit form, 12125550188. It is passed to the phone system as given. |
from | no | The caller ID shown. Must be a number or Virtual Caller ID on your account, written exactly as the admin center lists it (12125550100 — no +, spaces or dashes), or the request is refused with 403. Leave it out to use the account's normal outbound caller ID. |
params | no | {"key": "value"} — every value a string. A number such as "amount": 42.5 fails the whole request with 400; send "42.50". A null value is accepted as an empty string. |
ttlSec | no | How long the values stay readable, in seconds from now. Default 900. See below. |
The key goes in Authorization: Bearer … (this endpoint also accepts it as
X-API-Key).
Response — 200:
{
"code": 200,
"message": "success",
"data": { "call": { "id": "48113", "status": "queued", "flowId": 742 } }
}
The call is already on its way. Three things to know about this response:
call.idis not a call id. It identifies the storedparams. It is not in Call History or in webhooks, and no endpoint looks a call up by it. The one place it reappears is a step URL or body that uses the{params_id}placeholder — see Step URLs.statusis alwaysqueuedand never changes. There is no status endpoint. You learn what happened from the flow itself — see Get the results back.- The body also has
remoteIP,urlandappVersionfields. Ignore them.
ttlSec: make it cover the whole call
The flow reads your params again at every step. Once ttlSec has passed
since your request, later steps no longer see them — and a placeholder with no
value is read out as written. The default, 15 minutes, is enough for a short
call that is answered promptly; raise it for long flows. There is no upper limit.
4. What happens on the call
- We ring
to. Its own call forwarding is not followed. - The flow starts when the call is answered — by anyone or anything. There is no answering-machine detection: if voicemail picks up, the first step plays into the mailbox. Write the first step so it still makes sense as a voicemail, and expect "no input" on the keypad step in that case.
- If nobody answers, the flow never runs and nothing is saved.
- Each call is billed to your account like any other outbound call.
5. Get the results back
Your request returns before anyone has answered, so the outcome reaches you from the flow itself:
- A step URL — during the call. Give the step that follows a keypad choice a
Lookup → URL that includes your own reference from
params, e.g.https://example.com/hooks/appointment?id={appointment_id}&answer=confirmed. We call it the moment the caller reaches that step. It must answer2xxwithin 8 seconds; if it fails, the step takes its On error route, which hangs up by default. See Step URLs. - Flow Data — after the flow. Turn on Enable CSAT on a keypad step and
the answer is saved on the call: a Call History column and filter, and a
flowDataarray in Call History alert webhooks and SFTP files. Yourparamsare not saved on the call. See Results. - Call Logs — for debugging. The flow's Call Logs show every run: the text spoken with your values filled in, the keys pressed, your URL's reply and any error.
flowData is not in the public API's call endpoints or webhooks.
Examples
Worked examples builds an appointment reminder end to end — the flow, the request, your endpoint and the results — plus a payment reminder and a post-call survey.
Errors
Authentication failures are answered before the request is read, as
plain-text bodies with no detail. Everything else is JSON:
{"code": 422, "message": "error code (2.4055)", "info": "missing 'to' number"}.
| Status | Body | Cause | Fix |
|---|---|---|---|
401 | Unauthorized (text; unauthorized when no key was sent at all) | No key, or not a valid key | Send Authorization: Bearer tbk_live_… with the full key |
403 | Forbidden (text) | The key is revoked or expired; lacks flows:call; the request IP is outside its Allowed IPs; the flow is not among its flow pins (this includes a flow id that does not exist, for a key with flow pins); or the flow belongs to another account | Check each in the admin center — the response does not say which |
404 | Not Found (text) | No flow has this id (a key with no flow pins only) | Check the id in the editor's address |
400 | 2.4054 | Body is not valid JSON, or a params value is not a string | Quote every value |
422 | 2.4055 | to missing or blank | Send to |
403 | 2.4051 | from is not a number or Virtual Caller ID on your account | Use one of your numbers exactly as listed, or leave from out |
409 | 2.4048 | The flow has not been registered with the phone system | Open the flow and Save it again |
400 | 3.96 | The phone system refused to | Check the number's format |
500 | 3.961 | The phone system did not accept the call request | Usually no call was placed — but a timeout talking to the phone system is reported the same way. See Retries below |
Retries. A 4xx placed no call. On a timeout or 5xx you cannot always know
whether the call went out, and every successful request places a new call —
there is no idempotency key. Before retrying, decide whether a second call to the
same person is acceptable.
Limits
- No rate limits are enforced on this endpoint — not per key, not per
account. A key's
maxCallsPerMinute/maxCallsPerDayvalues are stored but not enforced. Throttle your own sending: each request rings a real phone and costs money. - No destination restrictions beyond what your account can dial. A key that leaks can ring any number your account can reach — pin it to a flow and set Allowed IPs.
- No scheduling, no cancel, no status. Send the request when you want the call; once accepted it cannot be withdrawn.
- One number per request. Loop on your side for a list.