API reference

Scoring live phone conversations for scams

Fraud detection scores a phone call while it's happening. You send what each side says, as text or as the call's audio, and you get back a live score from 0 to 100 for how likely it is that the other party is running a scam. The score comes with the kind of scam, who the caller claims to be, and what to tell the person on the line.

Everything goes through one endpoint, POST /v1/fraud. What you put in the body decides what happens, so this guide walks through a whole call, one request at a time. Each step links to the full reference.

Note

You need an API key. If you don't have one yet, see Authenticating with an API key. Every request below sends it as Authorization: Bearer sk_live_.... Make these requests from your server, never from a web page or app, so the key stays secret.

Who's who on the call

Every conversation has two sides:

Speaker Who it is
remote The party being assessed. Usually whoever called the person you're protecting.
user The person you're protecting. What they say counts much less toward the score.
unknown Either side. Use it when you can't tell who said a line.

caller and callee are accepted too, and mean remote and user.

A whole call in four requests

Step Request What it's for
1 POST /v1/fraud with number and text Start a conversation with the first thing said, and get its id
2 POST /v1/fraud with id and text Send each line as it's said. The response is the updated score.
3 GET /v1/fraud/:id/events Optional: be told the moment the score changes
4 POST /v1/fraud with id and end: true End it and get the final score

Have the call's audio instead of a transcript? Start with start: true and stream the audio instead of step 2. Want to know who owns the caller's number? That's a number check: the same endpoint with just a number.

Step 1: start a conversation

Start one as soon as you have the first thing the caller said. Add their number if you have it.

bash
curl https://api.spoofey.com/v1/fraud \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"number": "+14155550199", "text": "Hi, this is the fraud department at Chase. We saw suspicious activity on your account."}'

The response (201) is the conversation, already scored. Keep its id: every other request uses it.

Tip

Add "classify": true to check who the number really belongs to, at the number check price. A caller who claims to be a bank from a number that isn't tied to anyone scores higher. See Using number classification.

Step 2: send what's said

Send each line as soon as you have it, with the conversation's id. text counts as the caller (remote) unless you set speaker. To send several lines at once, use utterances, up to 50 at a time.

bash
curl https://api.spoofey.com/v1/fraud \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"id": "CONVERSATION_ID", "utterances": [{"speaker": "user", "text": "Oh no, what do I do?"}, {"speaker": "remote", "text": "Read me the verification code we just sent, right now, and do not tell anyone at the branch."}]}'

Every response is the updated score. Clear scam signs are picked up at once.

You have the call's audio (a phone system, Twilio Media Streams, a SIP recorder): start the conversation with {"start": true, "number": "..."}, then open the audio stream and send the raw audio as binary frames. We transcribe it and score it. Stereo is best: put the caller on channel 0 and the person you're protecting on channel 1, and we know who said what. Audio costs more per minute than text, because we transcribe it.

Step 3: act on the score

Each response tells you what to do in recommended_action, and gives you a sentence to show in advice:

recommended_action band What to do
continue low Nothing.
caution elevated Keep listening.
warn_user high Warn the person on the line, with the advice.
end_call critical Warn them that this looks like a scam and suggest they hang up.
javascript
// after any request to POST /v1/fraud, or any "score" event
function onScore(conversation) {
  if (conversation.recommended_action === "warn_user" || conversation.recommended_action === "end_call") {
    showWarning(conversation.advice || "This call shows signs of a scam.");
  }
}

The score updates in two ways:

  • At once on each line, from a check for clear scam signs: asking for a one-time code, gift cards, crypto, remote access, a courier to collect cash, threats of arrest, "don't tell anyone".
  • A few seconds later, when our AI has read the recent conversation and judged it as a whole. This happens about every 30 new words, and sooner when something serious is said. It also fills in scam_type, claimed_identity, summary and advice.

The AI's read arrives after your request has returned, so to see it as it lands, use one of these:

  • Server-sent events: one long-lived GET for the length of the call, the simplest from a server.
  • The audio stream WebSocket, which sends score updates back on the same socket.
  • The next line you send: its response carries the latest score, AI read included.
  • Polling every few seconds, if none of those suit you.

Step 4: end it

When the call ends, send its id with "end": true. This stops billing, runs one last read of anything not yet assessed, and returns the final score with its history. A conversation nobody sends anything to for 15 minutes ends on its own.

bash
curl https://api.spoofey.com/v1/fraud \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"id": "CONVERSATION_ID", "end": true}'

To look back over past calls, list your conversations.

Reading the score

Field What it means
risk_score 0–100. How likely it is that remote is running a scam right now. Rises at once, falls slowly.
peak_score The highest it has been on this call.
trust_score 100 - risk_score, if that's easier to display.
band low (under 30), elevated (30–59), high (60–79), critical (80 and up).
recommended_action continue, caution, warn_user or end_call, one for each band.
scam_type bank_impersonation, tech_support, government_impersonation, family_emergency, none…
claimed_identity Who the caller says they are: { "organization": "Chase", "kind": "financial" }.
cues The scam signs found, with a short quote as evidence.
advice One plain sentence to show the person on the line.
components What the score is made of: model_score, cue_score and classification_adjustment, with the reasons.
Warning

The score is a signal to act on, not proof. Show the warning and the advice, and let the person decide. A high score is a reason to stop and check, for example by hanging up and calling the bank on the number on their card.

Using number classification

Telling us what the caller's number is makes the score sharper. You can do it when you start the conversation or at any time during it (attach it by sending its id with classification or classify). Pass one of:

  • classify: true, which runs a number check on the conversation's number now, charged at its price.
  • The id of a number check you already ran.
  • The whole number check response.
  • Just the classification word: emergency, financial, carrier, business or ordinary.

How it moves the score:

What we see Effect
The caller claims to be a bank, government office, carrier or big company, but the number isn't tied to anyone (ordinary) Up 20
The caller claims to be one organization, but the number belongs to another Up 15
The number has been reported for spoofing or scams Up 15
The number's own risk score is high Up to 17
The number really is the organization the caller claims to be Down 12

The step down doesn't apply if the caller asked for something a real bank never asks for, like a one-time code, a PIN or gift cards. Caller ID can be faked, so a matching number isn't proof.

Billing

Conversations are billed per minute from your credit balance, one minute ahead, starting with the first thing you send. A minute counts whichever is longest of:

  • the time the call has been active (silences over a minute don't count),
  • the words sent, at 150 a minute (so pasting in a long transcript all at once is billed as the talk it is),
  • the audio streamed.

If your balance runs out mid-call, scoring pauses and requests return 402 insufficient_credits. Top up and send again, and it carries on where it stopped.

Limits

Limit Value
Live conversations per account 50
Longest conversation 4 hours
Ends on its own after 15 minutes with nothing sent
One utterance 2,000 characters
Utterances per request 50
Requests about conversations 1,200 a minute per API key (a number check counts toward the API's usual 120)
Transcripts kept 30 days

For errors that every endpoint shares, see API errors and rate limits.

Was this helpful?Your answer helps us improve this page.
Still stuck?Ask Spoofey AI about your exact case, or a person on our team will reply by email.
Last modified on October 23, 2026 · 8 min read
Edit