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, a call screener, or nobody at all. A person goes to your agent as soon as they finish their hello. A machine is let go at 2 seconds and never later than 5: no sooner, because a shorter call saves nothing on 6-second billing and very short calls hurt your traffic; no later than the AMD you have today.
- 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. - Never in a hurry to hang up: a call screener ("If you record your name and reason for calling…") is held to the end of the window, because a person may still pick up.
- 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": 2000, "sure_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 |
Timing. A person is passed on the moment ByeBeep is sure. A machine is let go no sooner than min_ms (2000) and a call screener at max_ms (5000); both live in /etc/byebeep/connector.conf, per dialer. Set min_ms = 0 to hang up the moment it is sure. A call still unsure at max_ms goes to an agent, as NOTSURE always has.
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.
Late correction (Plus). Install with --late event or --late hangup. After a "person" verdict the call goes on at once, and the relay keeps listening for 6 seconds. If what follows turns out to be a voicemail (the greeting carries on, a known greeting, a beep), it sets BYEBEEP_CORRECTION (machine), BYEBEEP_CORRECTION_REASON and BYEBEEP_CORRECTION_MS on the channel and writes a line to the connector's log; with hangup it also hangs the call up, but only when the reason is fingerprint (a greeting we know). BYEBEEP_LATE says whether it is watching (watching, or unavailable without the relay). Add webhook = yes to /etc/byebeep/connector.conf to get every event, corrections included, at your webhook as well. How far to trust each reason, measured on real calls: fingerprint flagged no people; greeting_continues and beep also fire on about 1 in 25 people (background noise, a tone on the line), so they are reported and never hung up on. Treat them as a hint.
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, min_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, min_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 | 5000 |
min_ms | 0–max_ms: the earliest a machine is reported. ByeBeep keeps listening until then; a call screener waits until max_ms. People are never held. 0 = report a machine the moment it is sure | 2000 |
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; sure_ms when a machine was held for min_ms (the moment it was already sure); 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. reason: fingerprint (a greeting we know; reliable), greeting_continues or beep (hints: people trigger them too) | class, at_ms, reason |
end | the stream is finished |
Subtypes: person, voicemail, screener (a call screener or a phone's own assistant: "If you record your name and reason for calling…", "I'm a call assistant…"), 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, min_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 and 2 calls at once per account. 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.