Answering-machine detection, as an API
ByeBeep tells you, while the call is still young, whether a person or a machine answered: a voicemail, a fax, a network message, or nobody at all. A voicemail greeting it has heard before is recognised in about half a second, others within a few seconds; a person as soon as they finish their hello.
- Drop in: a one-line install for VICIdial and Asterisk, a media-stream URL for Twilio, SignalWire and Telnyx,
mod_audio_forkfor FreeSWITCH, or a WebSocket from anything else. - Safe: on VICIdial and Asterisk, if ByeBeep cannot answer in time the call carries on with your existing AMD. It never sits in the SIP path.
- Clear results:
human,machineorunsure, a subtype, a confidence, how long it took and why, and the voicemail beep when you want to leave a message. - Pay per detection: from $0.06 per 1,000, 100,000 free, and a free 30-day shadow trial.
Quickstart (30 seconds)
- Create an account. You get a free test key (
bb_test_…) at once, no card needed. - Send a recording of the called party, from the moment of answer. No recording? Use our sample voicemail (Asterisk prompt by Allison Smith, CC BY-SA 3.0).
curl https://api.byebeep.com/v1/detect \
-H "Authorization: Bearer bb_test_YOUR_KEY" \
-F audio=@voicemail.wav
{
"verdict": {"class": "machine", "subtype": "voicemail", "confidence": 0.92,
"decided_ms": 320, "decided_by": "fingerprint"},
"audio_ms": 5654, "session_id": "895f47f8b70ebf4978c0744d", "billable": false, "test": true
}
For live calls, stream the audio instead (below): the verdict then arrives in the first seconds of the call, not after it.
Rather click than type? The portal's Playground streams the sample, your microphone or a recording to the live API and marks the verdict on the waveform, with the exact messages your code would send and receive.
VICIdial
On each dialer, as root:
curl -fsSL https://api.byebeep.com/install.sh | sudo bash -s -- --key bb_live_YOUR_KEY
The installer checks Asterisk, installs the connector, and puts ByeBeep in front of AMD() on VICIdial's AMD routes (8369, 8373, 8375), keeping AMD() behind it as the fallback. It also runs a small relay that keeps connections to the API open, so a call never waits for a handshake. Then it runs a self-test. Your campaigns and AMD Status Map work as before: ByeBeep sets the same variables.
| Variable | Example |
|---|---|
AMDSTATUS | HUMAN, MACHINE, NOTSURE |
AMDCAUSE | VOICEMAIL-480-0.97-BYEBEEP (subtype, ms, confidence) |
BYEBEEP_SESSION, BYEBEEP_BEEP_MS | for logs and voicemail drop |
Leaving voicemail? Install with --beep: on a voicemail the connector waits for the beep (20 s at most) before VICIdial plays your message. Undo: --uninstall restores your dialplan from the backup it made.
Asterisk
Any Asterisk 16+ can use the same connector as an EAGI program. Install it with the line above, then call it where you want detection:
exten => s,n,EAGI(byebeep) ; sets AMDSTATUS / AMDCAUSE
exten => s,n,GotoIf($["${AMDSTATUS}" != ""]?done)
exten => s,n,AMD() ; your fallback
exten => s,n(done),NoOp(${AMDSTATUS} ${AMDCAUSE})
Arguments: EAGI(byebeep,beep), mode=fast|balanced|accurate, max_ms=N.
FreeSWITCH
With mod_audio_fork, fork the answered leg with the start message as metadata. Verdicts come back as mod_audio_fork::json events.
uuid_audio_fork <uuid> start wss://api.byebeep.com/v1/stream mono 8k {"type":"start","key":"bb_live_YOUR_KEY","encoding":"pcm16","sample_rate":8000}
Twilio and SignalWire
Start a media stream of the called party's audio when the call is answered. Verdicts go to your webhook with Twilio's CallSid as call_id.
<Response>
<Start>
<Stream url="wss://api.byebeep.com/v1/twilio" track="inbound_track">
<Parameter name="key" value="bb_live_YOUR_KEY"/>
</Stream>
</Start>
<Pause length="10"/>
</Response>
Optional parameters: mode, max_ms, beep (true). Anything else you add comes back in the webhook's metadata. SignalWire speaks the same protocol at the same address.
Telnyx
Start media streaming on the answered call with stream_track: inbound_track and
stream_url: wss://api.byebeep.com/v1/telnyx?key=bb_live_YOUR_KEY
Verdicts go to your webhook with the call_control_id as call_id.
Any platform (WebSocket)
Open a WebSocket when the call is answered, send a start message, then the called party's audio as binary frames of 20–100 ms. Verdicts come back on the same socket.
// Node 22+, no dependencies
const ws = new WebSocket('wss://api.byebeep.com/v1/stream');
ws.onopen = () => ws.send(JSON.stringify({ type: 'start', key: 'bb_live_YOUR_KEY', encoding: 'mulaw', sample_rate: 8000 }));
ws.onmessage = (m) => { const ev = JSON.parse(m.data); if (ev.type === 'verdict') console.log(ev.class, ev.subtype, ev.decided_ms); };
// then, for each 20 ms of audio from the call: ws.send(frame); and at hang-up: ws.send('{"type":"stop"}')
Streaming API
wss://api.byebeep.com/v1/stream. Authenticate with the header X-Api-Key: bb_live_… (or Authorization: Bearer …); clients that cannot send headers put "key" in the start message.
| Start field | Values | Default |
|---|---|---|
encoding | mulaw, alaw, pcm16 | mulaw |
sample_rate | 8000, 16000 | 8000 |
mode | fast, balanced, accurate | balanced |
max_ms | 1000–10000: the latest a verdict may come | 4000 |
options.beep | report the voicemail beep after a machine verdict | false |
options.late_correction | report a voicemail heard after a person verdict | false |
options.webhook | also send events to your webhook | false |
call_id, metadata | your references, echoed back |
Send {"type":"stop"} or close when the call ends; ByeBeep decides on what it heard. A stream with no audio for 3 s is reported as no_audio and not billed.
Events
| Type | When | Fields |
|---|---|---|
ready | after the start message | session_id |
verdict | as soon as one is reached | class (human, machine, unsure), subtype, confidence, decided_ms, decided_by, and announcement_ms when a network announcement played first |
beep | the voicemail beep, if asked for | beep_ms |
correction | a person verdict turned out to be a voicemail, if asked for | class, at_ms, reason |
end | the stream is finished |
Subtypes: person, voicemail, fax, sit (a network message such as "number disconnected"), silence, ringback, announcement, no_audio. Treat unsure as a person, as VICIdial does.
Network announcements. Some carriers play a notice before the call connects onward, such as "Your call has been forwarded", which is followed either by voicemail or by a person picking up. ByeBeep recognises known announcements and judges the call from where they end, so a person who answers after one is not taken for a machine. The verdict then carries announcement_ms; a call that ends (or reaches max_ms) during one is unsure / announcement.
POST /v1/detect
One recording in, one verdict out: for trying ByeBeep and for recordings. Send a form field audio (WAV: 16-bit PCM at 8 or 16 kHz, or G.711) or a raw body with ?encoding=mulaw&sample_rate=8000. Optional: mode, max_ms, beep=true, call_id. Up to 4 MB. Billed like a streamed call.
Webhooks
Set an https address in the portal (Keys page). We POST each event as JSON with session_id, call_id, platform and metadata, signed:
X-ByeBeep-Signature: t=1794600000,v1=5f2c…
expected = hex(HMAC_SHA256(your_secret, t + "." + raw_body))
# compare v1 with expected in constant time; reject if t is more than 5 minutes old
Answer 2xx within 5 seconds. We retry twice (after 1 s and 5 s) on errors and timeouts, not on 4xx. The portal can send a test event.
SDKs
Small clients for Python (asyncio), Node 22+ (no dependencies) and Go: open a stream for a call, send audio, receive events, detect a WAV file in one call, and verify webhook signatures.
# Python
import asyncio, byebeep
print(asyncio.run(byebeep.detect_wav("bb_test_YOUR_KEY", "voicemail.wav")))
// Node
import { detectWav } from './byebeep.mjs';
console.log(await detectWav('bb_test_YOUR_KEY', 'voicemail.wav'));
They are being published; until then write to team@vicifast.com and we send them.
Errors and limits
| What | Answer |
|---|---|
| Wrong or missing key | HTTP 401, or the socket closes with invalid or missing API key |
| Over your account's calls at once | closes with this account is at its concurrent-call limit (the VICIdial connector falls back to AMD()) |
| Test key over 200 a day or 2 at once | HTTP 429 or a close saying so |
| A bad start message | an error event naming the field, then a close |
Keys and logs
Test keys (bb_test_…) come at sign-up and work at once: free, 200 detections a day, 2 calls at once. Live keys (bb_live_…) come once your email is confirmed and a card is saved. Every key is shown once; we keep only a fingerprint of it. Revoked keys stop working within 15 seconds.
Logs. The portal's Logs page lists every detection for 30 days (time, key, call ID, verdict, when and how it was decided), filterable by key, verdict, or call or session ID. New calls appear within about 15 seconds.
Pricing
| Monthly volume | Core | Plus |
|---|---|---|
| First 250k | $0.30 per 1,000 | $1.50 per 1,000 |
| 250k–1M | $0.12 | $1.50 |
| 1M–10M | $0.06 | $1.10 |
| Over 10M | $0.06 | $0.75 |
Core: verdict, subtype, timings, webhooks. Plus adds beep timing and late correction. 100,000 detections free once; the first 30 days after you go live are a free shadow trial; a month with any charge is at least $29. Monthly invoices, charged to your card. Calls with no audio are free.
Data and privacy
The API does not store call audio. For each call we keep a usage record (time, key, call ID, verdict, timings, session ID) for billing and support; your Logs page shows it for 30 days. Audio reaches us only as the stream of the called party's side, over TLS, and is processed in memory.