Telephony
Giving an AI agent a phone number
Buy a number inside the platform or point your existing trunk at it. Either way the phone network becomes just another transport into the same agent.
Two ways in
Buy a number in the dashboard. The platform orders it from a carrier, runs the regulatory checks, provisions it and bills it through your subscription. You pick a country, submit an identity and address, and wait for the status to reach ACTIVE.
Or bring your own. Register a number you already control and point it at sip.bitpull.ai:5060 - a redirect from your PBX, or a SIP trunk configured to route there. Nothing is ordered, nothing is verified, and the number stays yours. For anyone with an existing phone system, this is usually the faster and less disruptive route.
- Caller dialsyour published number
- Carrier / your PBX
- SIP to bitpullsip.bitpull.ai:5060
- Agent answerssame agent as the web widget
Carriers and what that means for you
Numbers carry a provider - Zadarma, Twilio, DIDWW, or EXTERNAL for a number you brought yourself. You do not choose it; the platform does, based on the country. It matters for two reasons: which regulatory checks apply, and how the caller's number arrives in the SIP fields.
The number lifecycle
A purchased number moves through a state machine, and "why is my number not working" is nearly always answered by reading where it is in it.
| Status | What it means | What to do |
|---|---|---|
PENDING_SUBSCRIPTION | Ordered, waiting on the billing side. | Check the subscription and payment method. |
PENDING_PROVIDER_ORDER | The carrier order is being placed. | Wait. |
PENDING_VERIFICATION | Regulatory identity and address check in progress. | This is the slow one. Days, not minutes. |
PENDING_PROVISIONING | Approved, being routed. | Wait - usually short. |
ACTIVE | Live. Calls reach the agent. | Test it by calling it. |
CANCELLATION_SCHEDULED | Ends at the period end. | Reversible until then. |
CANCELLED | Gone. The number is released. | Assume you cannot get the same number back. |
FAILED | Order or verification failed. | Read the error and resubmit the verification with corrected details. |
Most European countries require a verified identity and a local address before a number is issued. The platform collects both - business or personal, with a VAT id where relevant - submits them, and reports back a status with reject reasons and a comment when the carrier refuses. A rejection can be resubmitted with corrected details. Plan days, not minutes, and do not promise a customer a go-live date that depends on it.
Inbound
Nothing to build. Once the number is active, calls reach the agent that owns it, and each call becomes a conversation in the same list as web and chat conversations - with the SIP fields filled in.
The caller's number arrives in whatever format the carrier sends, and there are several. A bare E.164 number, a full sip: URI, a display name wrapping one, or a literal anonymous when the caller withheld it. Any code that matches callers against a CRM has to normalise first:
/**
* Caller numbers arrive from carriers in at least four shapes:
* +4312345678
* sip:+4312345678@sip.provider.tld
* "Max Muster" <sip:4312345678@10.0.0.1;user=phone>
* anonymous@anonymous.invalid
* Anything matching a CRM has to survive all of them.
*/
const ANONYMOUS = /^(anonymous|unknown|unavailable|restricted|private)$/i
export function callerNumber(raw, defaultCountry = '+43') {
if (!raw) return null
// "Display Name" <sip:…> → the part inside the angle brackets
let value = String(raw).trim()
const angle = value.match(/<([^>]+)>/)
if (angle) value = angle[1].trim()
value = value.replace(/^(sips?|tel):/i, '') // drop the scheme
value = value.split(';')[0].trim() // drop SIP parameters
const user = value.split('@')[0].trim()
if (!user || ANONYMOUS.test(user)) return null // withheld - do not guess
const digits = user.replace(/[\s\-().]/g, '')
if (!/^\+?\d{3,}$/.test(digits)) return null // an extension, not a number
if (digits.startsWith('+')) return digits
if (digits.startsWith('00')) return '+' + digits.slice(2)
if (digits.startsWith('0')) return defaultCountry + digits.slice(1)
return '+' + digits
}Outbound
The platform can also place calls. This is an account-level operation - it runs with a user token rather than an agent API key, so it belongs in the dashboard or in an operator tool, not in an unattended integration.
| Field | Meaning |
|---|---|
fromPhoneNumberId | The id of an active number you own - not the number itself. |
to | The destination. |
language, greeting, instructions | Per-call overrides of what the agent says and how. |
disableSystemPrompt | Runs the call on the per-call instructions alone. |
consentConfirmed | Must be true. A required assertion, not a flag to default. |
Idempotency-Key | Header. A retried request must carry the same key or you will call twice. |
The API will not place a call without that assertion, and it is there because unsolicited automated calls are illegal in most of Europe. It is a statement that you have a lawful basis for calling this person - not a checkbox to hard-code. An AI agent calling a list you bought is a fine, not a growth channel.
The response comes back with a session id and a status that moves through PENDING, ACTIVE, ENDED, or FAILED / REJECTED with a SIP status code and a failure reason. That SIP code is the useful diagnostic: a rejection by the destination carrier and an unreachable number look identical until you read it.
Transfer to a human
A number carries an optional redirect target - an E.164 number or a sip: URI. That is the mechanism for handing a caller to a person, and it is a telephony operation: the call leaves the agent and lands on a real phone.
What does not transfer is context. The person who picks up gets a call, not a transcript. If your team needs to know what was already said, the conversation summary reaches them through the outbound webhook - after the fact. Design the hand-off around that gap rather than assuming it does not exist.
Existing phone systems
The common deployment is not "replace the phone system". It is a redirect rule in the PBX you already run: overflow after N seconds, out-of-hours, or a dedicated extension. Everything with a redirect or SIP trunk configuration can do it - the target is sip.bitpull.ai:5060.
It is the lowest-risk rollout there is. Nobody is answering those calls today, so the agent cannot make anything worse - and a week of real out-of-hours transcripts tells you more about the prompt than any amount of internal testing.