https://api.callerapi.com/api/voicewss://api.callerapi.com/api/voiceclean, suspicious, likely_scam, or scam.none.report=true, a final score of scam files the caller's number and every callback number as a complaint. That block applies across CallerAPI: the SIP firewall declines the number and the SDK block lists include it.scam means a score of 0.85 or more.{
"status": "success",
"capabilities": {
"scan": true,
"live_filter": true,
"honeypot": true,
"llm": "openai:gpt-4.1-mini",
"analysis_llm": "openai:gpt-5.4-mini",
"stt": "grok:stt",
"tts": "openai:tts-1",
"personas": 8,
"prices": {
"credit_usd": 0.0039,
"filter_per_minute": 3,
"honeypot_per_minute": 8,
"scan": 2,
"scan_audio_minute": 1,
"managed_telephony_per_minute": 4
}
}
}live_filter must be true for the socket. When it is false, the socket answers 503.session.ended event is still sent.X-Auth header. The socket and the TwiML and TeXML endpoints also accept ?key=YOUR_KEY in the URL, because telephony vendors cannot set headers on a media socket."status": "success" or "status": "error" with a message.| You have | Use | Section |
|---|---|---|
| A switch, PBX, SBC, or media server that can open a WebSocket | Plain WebSocket protocol | 5 |
| A Twilio number | TwiML endpoint | 6 |
| A Telnyx number | TeXML endpoint | 7 |
| Your own speech recognition and no audio to send | Plain WebSocket protocol, text messages | 8 |
| A recording or a transcript after the call | POST /scan | 9 |
| A SIP trunk and you want the call answered by a persona | SIP INVITE to the honeypot | 13 |
wss://api.callerapi.com/api/voice/filter/stream?key=YOUR_KEY| Parameter | Values | Default | Meaning |
|---|---|---|---|
webhook | HTTPS URL | none | Receives events. HTTP URLs are ignored. |
language | BCP-47 code, for example en | auto | Transcription hint. Also turns spoken numbers into digits, which improves extraction. Set it when you know the language. |
report | true or false | false | Files the caller when the final score is scam. |
params object of the start message instead. A value in the start message wins over the URL.503. Retry on a new connection. The load balancer sends it to another node.{
"type": "start",
"call_id": "your-call-id",
"from": "+15559990000",
"to": "+15551234567",
"sample_rate": 8000,
"encoding": "pcm16",
"params": {
"webhook": "https://example.com/callerapi-voice",
"language": "en",
"report": "true"
}
}| Field | Required | Notes |
|---|---|---|
type | yes | Always start. |
call_id | no | Your identifier for the call. It is echoed in every event and stored with the session. |
from | for report | Caller number in E.164 with a leading +. The block action uses it. Without it, only callback numbers can be filed. |
to | no | Called number in E.164. |
sample_rate | no | 8000 to 48000. Default 8000. |
encoding | no | pcm16 (16-bit little-endian, default) or mulaw (G.711 mu-law). |
params | no | Same keys as the URL parameters. Values are strings. |
pcm16 at 8 kHz and 64 kbit/s for mulaw.| Message | Meaning |
|---|---|
{"type":"dtmf","digit":"1"} | The caller pressed a key. Digits are collected into intel.dtmf. |
{"type":"transcript","text":"...","track":"inbound","final":true,"end_of_turn":false} | Words you transcribed yourself. See section 8. |
{"type":"stop"} | The call ended. Closing the socket has the same effect. |
type field. Section 10 lists them. On this protocol you receive every event on the socket, including transcript.line.{"type":"clear"} message there.{"type":"stop"} or close the socket. The server then:report=true and the final score is scam.session.ended on the socket and to the webhook.session.ended, or read it from the webhook or from GET /sessions/{id}.mod_audio_fork opens a WebSocket, sends one text frame with the metadata you give it, then streams 16-bit PCM. Pass our start message as the metadata. Check the argument order against your module version.uuid_audio_fork <uuid> start wss://api.callerapi.com/api/voice/filter/stream?key=YOUR_KEY mono 8k {"type":"start","call_id":"<uuid>","from":"+15559990000","to":"+15551234567","sample_rate":8000,"encoding":"pcm16","params":{"webhook":"https://example.com/hook","language":"en"}}mono, so only the caller's speech is sent. Events arrive on the socket. Most forks do not expose them to the dialplan, so use the webhook parameter to act on them.mulaw, and speaks the protocol above.https://api.callerapi.com/api/voice/filter/twiml?key=YOUR_KEY&forward=%2B15551234567&webhook=https://example.com/hook&language=en&report=true| Parameter | Required | Meaning |
|---|---|---|
forward | yes | The E.164 number the call rings, URL-encoded. |
webhook | recommended | Twilio drops the socket on unknown messages, so no events reach you on the stream. The webhook is the only live channel. |
tracks=both | no | Also transcribes the called party, labelled callee. Transcription cost doubles. |
language, report | no | As in section 5.1. |
forward. The caller's number and your options ride on the stream as <Parameter> elements.<Stream url> at wss://api.callerapi.com/api/voice/filter/stream?key=YOUR_KEY, use track="inbound_track", and add <Parameter name="from" value="+15559990000"/> with the From value from the Twilio voice webhook. Twilio does not send the caller's number in the start frame, so without the parameter from is empty and the block action has no caller number.https://api.callerapi.com/api/voice/filter/texml?key=YOUR_KEY&forward=%2B15551234567&webhook=https://example.com/hookfrom and to in the start frame, so they are always present. The stream uses codec="PCMU". PCMA is also accepted.{"type":"transcript","text":"This is the IRS. There is a warrant for your arrest.","track":"inbound","final":true}| Field | Default | Meaning |
|---|---|---|
text | required | The words. Empty text is ignored. |
track | inbound | inbound is the remote caller. outbound is the local party, labelled callee. |
final | true | false marks an interim result. Interims are not scored and not stored. |
end_of_turn | false | The honeypot uses it to answer sooner. The filter ignores it. |
| Field | Type | Notes |
|---|---|---|
transcript | string | Text of the call. This or audio is required. |
audio_base64 | string | Base64 audio. |
encoding | string | wav (default), pcm16, or mulaw. |
sample_rate | int | Required for pcm16. Default 8000 for mulaw. |
language | string | BCP-47 hint. |
from | string | Caller number in E.164. Required for report. |
to | string | Called number in E.164. |
report | bool | File the caller when the score is scam. Default false. |
audio plus the same fields as form values. The body limit is 25 MB. The request times out after 90 seconds.{
"status": "success",
"session_id": "3f0c1a2e-...",
"verdict": {
"score": 0.93,
"level": "scam",
"category": "Calls pretending to be government, businesses, or family and friends",
"tactics": ["authority_impersonation", "threat", "untraceable_payment", "urgency"],
"signals": ["invokes Social Security", "asks for gift cards", "threatens arrest or legal action"],
"summary": "Caller impersonates Social Security and demands gift cards under threat of arrest.",
"source": "combined"
},
"intel": {},
"transcript": "...",
"reported": true,
"reported_numbers": 1
}| Field | Meaning |
|---|---|
type | Event type. See 10.2. |
session_id | The session. Use it with GET /sessions/{id}. |
kind | filter or honeypot. |
call_id | Your call_id, the Twilio CallSid, the Telnyx call control id, or the SIP Call-ID. |
from, to | E.164 numbers when known. |
at | RFC 3339 time. |
type | When | Extra fields | Webhook |
|---|---|---|---|
session.started | The start message was accepted. | persona (honeypot only) | yes |
transcript.line | One utterance is final. | speaker, text | no |
verdict | The score or category changed enough to matter. | verdict | yes |
session.error | A provider refused a request. | error, text | yes |
session.ended | The call ended and the final analysis is done. | verdict, intel, transcript, duration_seconds, reported, reported_numbers | yes |
speaker is caller, callee, or the persona's name.session.error ends a honeypot session when the greeting could not be spoken. In every other case it is advisory and the session continues.verdict event is sent when:verdict:{
"type": "verdict",
"session_id": "7463336c-c965-450f-b188-e6908e969395",
"kind": "filter",
"call_id": "your-call-id",
"from": "+15559990000",
"to": "+15551234567",
"at": "2026-09-13T15:04:11Z",
"verdict": {
"score": 0.72,
"level": "likely_scam",
"category": "Advance Fee Loan",
"tactics": ["untraceable_payment", "urgency"],
"signals": ["pitches a loan", "asks for money before a loan"],
"source": "heuristic"
}
}session.ended:{
"type": "session.ended",
"session_id": "7463336c-c965-450f-b188-e6908e969395",
"kind": "filter",
"call_id": "your-call-id",
"from": "+15559990000",
"to": "+15551234567",
"at": "2026-09-13T15:07:52Z",
"verdict": {
"score": 0.94,
"level": "scam",
"category": "Advance Fee Loan",
"tactics": ["untraceable_payment", "urgency", "callback_pressure"],
"signals": ["pay us up front, and then we would give you the loan", "send us a payment right now to +19998887766"],
"summary": "Caller offers a $10,000 loan and demands an upfront payment to a phone number within the hour.",
"source": "combined"
},
"intel": {
"names": ["Dmitry"],
"organizations": ["Bankloans.com"],
"callback_numbers": ["+19998887766"],
"amounts": ["$10,000"]
},
"transcript": "caller: Hi, this is Dmitry from Bankloans.com...\n",
"duration_seconds": 221,
"reported": true,
"reported_numbers": 2
}webhook to an HTTPS URL. The server posts every event except transcript.line.| Header | Value |
|---|---|
Content-Type | application/json |
X-Voice-Event | The event type. |
X-Voice-Signature | sha256=<hex>. HMAC-SHA256 of the raw request body. The key is your API key. |
session_id and read GET /sessions/{id} when a post is missing.at to order them.| Field | Meaning |
|---|---|
score | 0 to 1. Probability that the caller is a scammer. |
level | Bucket of the score. See 11.2. |
category | One exact CallerAPI complaint subject, or none. See 11.3. |
tactics | Manipulation techniques observed. See 11.4. |
signals | Short pieces of evidence. Above a score of 0.6 they quote the caller's words. |
summary | Two sentences at most. Present when a model produced the verdict. |
source | heuristic (rules only), llm (model only), combined (both), or stored (read back from the session record). |
| Level | Score | Suggested action |
|---|---|---|
clean | below 0.3 | Nothing. |
suspicious | 0.3 to 0.59 | Tag the CDR. Show a soft warning if you have a UI. |
likely_scam | 0.6 to 0.84 | Warn the called party. Consider recording. |
scam | 0.85 and above | Warn or drop the call. This is the level that files a complaint with report=true. |
category is one of these exact strings. They are the CallerAPI complaint subjects, so a filed complaint carries the same value.Advance Fee Loan
Bank/Credit Card Company Imposter
Business Email Compromise
Calls pretending to be government, businesses, or family and friends
Charity
Computer & Technical Support
Counterfeit Product
COVID-19
Credit Cards
Credit Repair/Debt Relief
Cryptocurrency
Debt Collection
Lotteries, Prizes & Sweepstakes
Medical & Prescriptions
Moving
No Subject Provided
Phishing
Reducing Your Debt (Credit Cards, Mortgage, Student Loans)
Rental
Romance
Tax Collection
Travel/Vacation/Timeshare
Utility
Warranties & Protection Plans
Work From Home & Other Ways To Make Money
Yellow Pages/Directories
Hang Up
Scammers
Advertising
Surveys
Financial Services
Store
Company
Othernone means the call is clean. It is not a complaint subject and is never filed.GET /categories returns the list with a description of each, as JSON.urgency
secrecy
authority_impersonation
remote_access
untraceable_payment
pii_harvest
otp_request
threat
prize_lure
family_fear
refund_bait
callback_pressurenames, organizations, callback_numbers (E.164), emails, urls, crypto_wallets, bank_accounts, card_numbers, gift_card_brands, payment_apps, remote_tools, remote_codes, case_numbers, amounts, addresses, scripts (verbatim lines of the pitch), dtmf (every digit pressed, in order).report=true the server files a complaint at the end of the session when all of these are true:none, Advertising, Surveys, Hang Up, and No Subject Provided are never filed. Other needs a score of 0.85 or more on both products.from in E.164 with a leading +, or a callback number the caller gave.category. The comment holds the summary and the intel, so an analyst sees why the number is there.reported and reported_numbers in session.ended tell you what was filed.| Path | How |
|---|---|
| Plain WebSocket | Open wss://api.callerapi.com/api/voice/honeypot/stream?key=YOUR_KEY. Same start message as section 5.2. Send caller audio as binary frames. The server sends persona audio as binary frames in your encoding and rate, and {"type":"clear"} when you must drop queued playback. Do not send the persona's own voice back. |
| SIP | Send an INVITE to sip:honeypot@HONEYPOT_HOST:5080 with G.711 (PCMU or PCMA) in the SDP offer. Add X-CallerAPI-Key: YOUR_KEY. No SRTP. RFC 4733 DTMF is understood. The caller's number is read from P-Asserted-Identity, then From. The honeypot ends the call with BYE. |
| SIP firewall | If your domain is on the CallerAPI SIP firewall, set spam_action=honeypot with PUT /api/domains/{id}. Spam calls are answered by a persona instead of a 603. No INVITE from you is needed. |
| Twilio | Voice URL https://api.callerapi.com/api/voice/honeypot/twiml?key=YOUR_KEY. |
| Telnyx | Voice URL https://api.callerapi.com/api/voice/honeypot/texml?key=YOUR_KEY. |
params in the start message, or SIP headers:| Option | SIP header | Default | Meaning |
|---|---|---|---|
persona | X-Persona | random | A persona id from GET /personas. |
webhook | X-Webhook | none | HTTPS URL for events. |
report | X-Report | true | File the caller when the final score clears 0.8. |
GET /sessions?limit=50
GET /sessions/{id}limit is 1 to 200. Any other value becomes 50. Sessions belong to the key that created them.| Field | Meaning |
|---|---|
id | Session id. |
kind | filter, honeypot, or scan. |
protocol | raw, twilio, telnyx, sip, or http. |
call_id, from, to | As given or read from the vendor. |
persona_id | Honeypot only. |
started_at, ended_at, duration_seconds | Timing. |
turns | Number of transcript lines. |
transcript | speaker: text per line. |
score, level, category, summary | The final verdict. |
intel_count | Number of distinct intel facts. |
reported | true when at least one complaint was filed. |
GET /sessions/{id} also returns verdict and intel as objects.| Product | Credits | Per minute |
|---|---|---|
| Filter | 3 per started minute | $0.0117 |
Filter with tracks=both | transcription cost doubles | |
| Honeypot | 8 per started minute | $0.0312 |
| Scan, transcript | 2 per request | |
| Scan, audio | 2 per request plus 1 per started minute of audio | |
| Number provided by CallerAPI | 4 more per minute on that number | $0.0156 |
GET /capabilities.| Limit | Value |
|---|---|
| Live sessions per node | 200. Above it the socket answers 503. Retry on a new connection. |
| Start message timeout | 15 seconds after the connection. |
| Filter session | 60 minutes. |
| Honeypot session | 15 minutes. |
| Sample rate | 8000 to 48000 Hz. |
| Scan body | 25 MB. |
| Scan request | 90 seconds. |
| Webhook post | 5 seconds, no retry. |
| Status | Meaning |
|---|---|
400 | Bad input. The message says which field. |
401 | Missing or wrong key. |
402 | Insufficient credits. |
404 | Session not found, or it belongs to another key. |
502 | Transcription failed on /scan. |
503 | The feature is off on this deployment, or the node is at its session limit. |
| Behaviour | Cause |
|---|---|
Upgrade refused with 401 | Missing or wrong key. |
Upgrade refused with 503 | live_filter is false, or the node is full. |
| Socket closed with no events | No start message within 15 seconds, a binary frame before the start message, or an unsupported encoding or sample_rate. |
Session ended early with session.ended | Credits ran out, or the session hit its time limit. |
session.error | A speech or model provider refused. The error field carries the provider's message. |
GET /capabilities returns "live_filter": true.from is E.164 with a leading + on every call. Without it nothing is filed.language is set when you know it.X-Voice-Signature.GET /sessions/{id}.503 on connect is retried on a new connection.report=true is off until you have reviewed verdicts on your own traffic.