Bolna Inbound Calls
Inbound = map a phone number to an agent so calls to that number are answered automatically. Optionally layer IVR menus, caller identification, spam controls, and multilingual switching on top.
Two endpoints — that's it
| Endpoint | Purpose |
|---|---|
POST /inbound/setup |
Map a phone number to an agent (with optional IVR config) |
POST /inbound/unlink |
Unmap |
Both: Authorization: Bearer $BOLNA_API_KEY, Content-Type: application/json.
Where phone_number_id comes from
| Number source | How to get the ID |
|---|---|
| Bolna-hosted (purchased on dashboard) | GET /phone-numbers/all — see manage-phone-numbers |
| Connected provider (your Twilio, Plivo, Vobiz, Exotel) | Returned when you list phone numbers after connecting the provider |
| Your own SIP trunk (BYOT) | Returned from POST /sip-trunks/trunks/{id}/numbers — see setup-sip-trunk |
Whatever the source, the mapping flow is identical.
Basic mapping
curl -X POST https://api.bolna.ai/inbound/setup \
-H "Authorization: Bearer $BOLNA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"phone_number_id": "123e4567-e89b-12d3-a456-426614174000"
}'
Response:
{
"url": "https://api.bolna.ai/inbound_call?agent_id=...&user_id=...",
"phone_number": "+19876543210",
"id": "3c90c3cc0d444b5088888dd25736052a"
}
That's the whole basic setup. Now any call to +19876543210 is answered by the agent.
One number, one agent. Re-mapping a number to a different agent silently unmaps the previous link. To detach without re-mapping, use /inbound/unlink.
IVR (menu routing)
For "press 1 for sales, 2 for support" or pre-call data collection. Plivo only.
curl -X POST https://api.bolna.ai/inbound/setup \
-H "Authorization: Bearer $BOLNA_API_KEY" \
-H "Content-Type: application/json" \
-d @assets/ivr_department_routing.json
The asset file:
{
"agent_id": "default-agent-uuid",
"phone_number_id": "phone-number-uuid",
"ivr_config": {
"enabled": true,
"voice": "Polly.Aditi",
"welcome_message": "Welcome to Acme Corp.",
"timeout": 5,
"max_retries": 2,
"default_agent_id": "default-agent-uuid",
"steps": [
{
"step_id": "department",
"type": "menu",
"prompt": "Press 1 for Sales. Press 2 for Support. Press 3 for Billing.",
"field_name": "department",
"options": [
{ "digit": "1", "label": "Sales", "agent_id": "sales-agent-uuid" },
{ "digit": "2", "label": "Support", "agent_id": "support-agent-uuid" },
{ "digit": "3", "label": "Billing", "agent_id": "billing-agent-uuid" }
]
}
]
}
}
Step types
| Type | Purpose |
|---|---|
menu |
Present options, route based on digit pressed. |
collect |
Capture a multi-digit input (account number, PIN). |
Top-level IVR fields
| Field | Default | Notes |
|---|---|---|
enabled |
false |
Master toggle. |
voice |
Polly.Joanna |
Polly voice for the IVR prompts. Polly.Aditi for Hindi-English. |
welcome_message |
— | Played once on connect, before the first step. |
timeout |
5 |
Seconds to wait for caller input on a step. |
max_retries |
2 |
Retries on invalid input per step. |
invalid_input_message |
"Invalid input. Please try again." |
Played on wrong key. |
no_input_message |
"No input received. Goodbye." |
Played on timeout. |
steps[] |
required | IVR flow. |
default_agent_id |
— | Fallback agent when no option-level agent_id is set. |
Step navigation
| Field | Behaviour |
|---|---|
next_step |
Linear: go to this step after current. |
conditional_next |
Branch by digit: {"1": "menu_en", "2": "menu_hi"}. |
| Neither | End of IVR. Route to selected agent_id or default_agent_id. |
Multi-language IVR with branching
{
"step_id": "language",
"type": "menu",
"prompt": "For English press 1. Hindi ke liye 2 dabayein.",
"field_name": "language",
"options": [
{ "digit": "1", "label": "English" },
{ "digit": "2", "label": "Hindi" }
],
"conditional_next": { "1": "menu_en", "2": "menu_hi" }
}
See assets/ivr_multilingual.json for the full multilingual department-routing flow.
What the agent sees
After IVR completes, the collected fields are available as recipient_data:
{
"department": "Sales",
"account_number": "123456",
"language": "English",
"ivr_completed_at": "2026-05-19T10:30:00Z"
}
Use them in agent prompts: "Customer selected {department}. Account number: {account_number}."
Disable IVR
curl -X POST https://api.bolna.ai/inbound/setup \
... \
-d '{ "agent_id": "...", "phone_number_id": "...", "ivr_config": { "enabled": false } }'
For full IVR reference (menu + collect step examples, conditional branching), see references/ivr-config.md.
Caller identification (ingest_source_config)
Pre-fill the agent's prompt variables based on who's calling. Configured on the agent, not on /inbound/setup. Three flavours:
| Source | Best for |
|---|---|
| Internal API | Teams with an existing CRM / customer database. Real-time, fully dynamic. |
| CSV upload | Small teams, low-volume, no-code. Static — re-upload to update. |
| Google Sheet | Real-time sync without coding. Sheet must be publicly accessible. |
Internal API source
Bolna does a GET request to your endpoint with:
GET https://your-api.example.com/customers
?contact_number=+19876543210
&agent_id=<agent_uuid>
&execution_id=<execution_uuid>
Auth: Bearer token (configured per agent).
Your response JSON is merged into recipient_data. Use it in prompts:
You are speaking with {first_name} {last_name}. They are on the {plan} plan,
last contacted on {last_contacted_at}.
CSV / Google Sheet source
Both need a contact_number column (with country code). All other columns become prompt variables.
contact_number,first_name,last_name,plan,last_contacted_at
+19876543210,Priya,Sharma,Enterprise,2026-05-12
+918765432109,Rahul,Verma,Pro,2026-05-14
For full setup on each source, see references/caller-identification.md.
Spam and abuse controls
These live on the agent's task_config, not on the inbound mapping itself.
| Field | Notes |
|---|---|
inbound_limit |
Max calls per recipient phone number. -1 = unlimited. Useful to prevent a single number from running up costs. |
whitelist_phone_numbers[] |
E.164 numbers that always bypass any limits / unknown-caller rules. |
disallow_unknown_numbers |
Boolean. When true, reject callers not present in the agent's ingest_source_config lookup. Requires ingest_source_config to be set. |
{
"task_config": {
"inbound_limit": 3,
"whitelist_phone_numbers": ["+919876543210", "+19876543210"],
"disallow_unknown_numbers": true
}
}
If
disallow_unknown_numbers: trueandingest_source_configis not configured, every inbound call will be rejected. Always configure caller identification first.
Multilingual auto-switching
Bolna can auto-detect the caller's language after ~3 conversation turns and switch system messages (hangup, user-online check, tool wait) to that language. Configure language variants in the agent:
{
"check_user_online_message": {
"en": "Hey, are you still there?",
"hi": "क्या आप अभी भी वहाँ हैं?",
"ta": "வணக்கம், நீங்கள் இன்னும் இணைப்பில் இருக்கிறீர்களா?"
},
"call_hangup_message": {
"en": "Thank you for calling. Goodbye!",
"hi": "कॉल करने के लिए धन्यवाद। अलविदा!"
}
}
Fallback order: detected language → en → first available. Always configure en as a safety net. See references/auto-switch-messages.md.
Unlink
curl -X POST https://api.bolna.ai/inbound/unlink \
-H "Authorization: Bearer $BOLNA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "phone_number_id": "123e4567-e89b-12d3-a456-426614174000" }'
The number remains owned (in your provider / SIP trunk / Bolna inventory). Only the agent link is removed.
Going deeper
| File | Contents |
|---|---|
references/ivr-config.md |
All step types, navigation, voices, advanced patterns. |
references/caller-identification.md |
API, CSV, Google Sheet — setup walkthroughs and prompt-variable wiring. |
references/auto-switch-messages.md |
Multilingual message bundles, fallback order, supported codes. |
assets/ivr_department_routing.json |
Plain department-routing IVR. |
assets/ivr_multilingual.json |
Language selection → department menu, with separate agents per language. |
assets/ivr_account_verification.json |
Two-step collect flow (account + PIN) before agent. |
scripts/setup_inbound.py |
Wraps POST /inbound/setup with flags for plain and IVR setups. |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Call rings but isn't answered | phone_number_id not yet mapped |
Re-run POST /inbound/setup; verify with GET /phone-numbers/all |
IVR returns 400 |
Using IVR on non-Plivo number | IVR is Plivo-only; route via Plivo or skip IVR |
recipient_data empty in prompt |
ingest_source_config not set, or contact_number didn't match |
Verify the source has the calling number; check Bolna logs for the lookup HTTP status |
| All callers rejected | disallow_unknown_numbers: true without ingest_source_config |
Add the source first, or set disallow_unknown_numbers: false |
| Auto-switch hangup plays in wrong language | Detection needed 3 turns, call was shorter | Configure en fallback; expect the first turn or two to still be primary language |
| Agent picks up but audio quality is bad on inbound SIP | ulaw codec mismatch |
Bolna sets ulaw on SIP-routed inbound; verify your trunk allows ulaw |
See also
manage-phone-numbers— buying or listing Bolna numbers (wherephone_number_idcomes from).setup-sip-trunk— wiring BYOT numbers into Bolna.create-agent—task_config(spam controls) andingest_source_configlive in the agent body.setup-tools/references/dtmf.md— keypad input inside a call (different from IVR menu routing).../references/india-compliance.md— DLT / 140 / 160-series rules if you're using regulated Indian numbers.