Events
Tell us what happened. A call starting is scored on the spot; a call ending settles its record; the turns in between feed the live monitor. All on one endpoint, keyed on your call id.
One endpoint, six types
Every event is a JSON object with a type and a call_id. The call_id is yours (the carrier's SID, the platform's id, anything stable) and it is what ties the events of one call together. Post the same start twice and you get the first answer back with duplicate: true; nothing is counted twice.
| type | When | Answer |
|---|---|---|
call.started | The moment the call arrives, before your agent speaks. | A score, an action, reasons. |
call.ended | When it ends, from either side. | The duration and the call's settled spam score. |
turn.started | Somebody begins speaking. | The live picture so far. |
turn.ended | They stop. Carries how much they said. | The live picture so far. |
interruption | The caller cut the agent off. | The live picture so far. |
transcript | Final text for a turn, if it arrives separately. | The live picture so far. |
call.ended for a call we never saw start is a 404; turns for one are too. Everything else can arrive out of order and late, and is placed by its at.A call starts
The call is recorded under your call_id, the protected number is created if this is the first time we have seen it, and the call is scored against everything we know. The answer is the same shape as a score, with the call now on record.
Request body
- type"call.started"required
- call_idstringrequired
- Your id for the call. Stable, unique within your account.
- tostringrequired
- The number that was dialled, E.164. On an inbound call, your protected number.
- fromstringrequired
- The number that dialled, E.164. On an inbound call, the caller.
- direction"inbound" | "outbound"default "inbound"
- On an outbound call the roles flip:
fromis your number andtois who you dialled. - atstringdefault now
- When it started, ISO 8601. Set it when you are reporting late.
- registerbooleandefault true
- Add
toas a protected number if it is new.falserefuses unknown numbers with409 not_protectedinstead, which is right when the list of numbers is managed elsewhere. - quietbooleandefault false
- History, not news: score and record the call, but raise no alerts and place no blocks. For importing what happened before you connected.
- sourcestringdefault "api"
- Where the call came from, for your own filtering. Free text.
- costnumber | null
- What the call cost in USD, if you know it now. Usually you know it at the end.
- cost_breakdownobject | null
- The cost by part:
llm,stt,tts,carrier,platform, in USD. - attrsobject
- What the carrier said about the caller at ring time. See attrs.
- provider_call_idstring | null
- The carrier's id beneath a platform call, when a platform and its carrier both report the same call. The two are folded into one record.
Response
- call_idstring
- As you sent it.
- scorenumber
- 0 to 1.
- action"allow" | "review" | "reject"
- Where we would draw the line.
- thresholdsobject
- Where
actionchanges:reviewandreject. Draw your own line on the score with these beside it. - reasonsstring[]
- Why.
- blockobject | null
- The active block on this caller, if any.
- checksobject
- The counts behind the score. Same shape as on a score.
- priorobject
- What this number scored before, on your account and on the network:
points,calls,good, andlast, the score and age in days of its most recent call, ornull. - duplicatetrue
- Present when this call_id had already started. The score, action and reasons are the ones decided the first time.
- cappedtrue
- Present when your plan's daily scoring allowance is spent. The call is on record, allowed, and not scored;
capcarries the allowance.
Errors
- missing_call_id400
- No call_id.
- invalid_to / invalid_from400
- A number that could not be parsed. On an outbound call the names follow the roles, so a bad caller is invalid_to.
- invalid_direction400
- Something other than
inboundoroutbound. - not_protected409
- With
register: false, a number you have not added. - number_limit402
- A new number past your plan's cap. The cap is in the body.
A call ends
Closes the record, settles the call's spam score from its full length and everything the turns showed, and folds in whatever you learned at the end, usually the cost. A second end for the same call is a 200 with duplicate: true and no change.
Request body
- type"call.ended"required
- call_idstringrequired
- The id the call started with. The carrier's id is accepted too, when one was given as provider_call_id.
- atstringdefault now
- When it ended, ISO 8601.
- reasonstringdefault "ended"
- Why, in your words or the carrier's: completed, busy, no-answer, hangup. The gate writes gate_fail or gate_timeout.
- costnumber | null
- What the call cost in USD. When two sources report a cost, the larger is kept: a platform's total includes its carrier's.
- cost_breakdownobject | null
- The cost by part. A platform-side breakdown replaces what was there so the parts add up to its total; a carrier's price only fills a gap.
- attrsobject
- Anything the carrier learned by the end: machine detection, ring time. Merged into what was already there.
Response
- call_idstring
- The id the call is stored under.
- duration_sinteger
- Start to end, in seconds.
- spam_scoreinteger | null
- 0 to 100, the call's settled score. See the two scores.
nullwhen there was nothing to score.
Errors
- missing_call_id400
- No call_id.
- unknown_call404
- We never saw this call start.
Turns, while the call runs
The live monitor. Four event types describe the shape of a conversation as it happens (who is speaking, for how long, how much they said, whether they were cut off) and each answers with the call's live picture: the flags it has tripped and a score. We keep lengths, counts and a hash. We never keep the words.
Part of Guard. On Watch these events are accepted and dropped unread, with ignored: true.
Request body
- type"turn.started" | "turn.ended" | "interruption" | "transcript"required
- call_idstringrequired
- The call this belongs to.
- role"user" | "agent"required
- Who spoke. Not needed on
interruption, which is always the caller cutting the agent off. - atstringdefault now
- When, ISO 8601.
- turninteger
- Your own index for the turn, if you keep one.
- textstring
- On
turn.endedandtranscript: what was said. It is measured (characters, words, a short hash for spotting repeats) and dropped before anything is written. - chars / wordsinteger
- Instead of
text: send the counts yourself and no words ever leave your system. - interruptedboolean
- On
turn.ended: this turn was cut off.
Response
- flagsstring[]
- What the call has tripped so far. See the table below.
- scorenumber
- 0 to 1, the live score. It only rises; a new flag at alert level raises a call.suspicious alert once.
- spam_scoreinteger | null
- 0 to 100, recomputed on every turn.
- user_turns / agent_turnsinteger
- Counts so far.
- interruptionsinteger
- Times the caller cut the agent off.
- ignoredtrue
- Present when the event was dropped:
whyislive_monitor_is_guardon Watch, orcall_turn_caponce one call has written 3,000 turns.
Errors
- invalid_role400
- Something other than
useroragent. - unknown_call404
- We never saw this call start.
Live flags
| Flag | What tripped it |
|---|---|
recording_likely | One caller turn far longer than a person speaks without a pause. |
monologue | The caller's turns, averaged, are far longer than conversation. |
barge_storm | The caller keeps cutting the agent off, well past a lively human's rate. |
rapid_fire | Many caller turns in a short window. |
repeated_turns | The same caller turn, several times over. |
silent_listener | A minute in and the caller has said nothing. |
one_sided | The caller has said a great deal and the agent almost nothing. |
A flag also appears on the call's reasons as live_<flag>, so the log tells the whole story afterwards.
With an ingest token
The same endpoint, with the credential in the path instead of a header. For a pipeline that cannot hold a key, or a platform that will only post to a bare URL. Issue the token under Settings → Live events, Your own server; it can post events and do nothing else.
curl -X POST https://api.murusai.com/v1/events/lv_4Qx9pLm2aXr8sT \
-H "Content-Type: application/json" \
-d '{ "type": "call.ended", "call_id": "CA7f3e2a91c04b" }'attrs
What the carrier knew about the other party when the call rang. All optional; what you send is kept on the call and some of it feeds the score. Keys we do not know are kept and ignored.
- geoobject
city,region,zip,country: where the number is registered, per the carrier's prefix database. The zip is what lets us notice many fresh callers from one place.- attestation"A" | "B" | "C" | "failed" | "none"
- STIR/SHAKEN as the terminating carrier reported it. A means the originating carrier vouches the caller may use that number; failed means a signature arrived and did not verify; none means nothing was signed.
- caller_namestring
- CNAM, when your number has it switched on.
- answered_bystring
- Machine detection, as the carrier names it: human, machine_start, unknown.
- carrierstring
- Which carrier delivered it.
- forwarded_fromstring
- The number that forwarded the call here, if one did.
- perfobject
- Your platform's timings, averages in ms (
turn_ms,model_ms,voice_ms,stt_ms), andinterrupted, its own count of the caller cutting in. - tokensobject
in,out,cached. The number that runs away when a caller keeps the agent talking.
FromCity, FromZip, StirVerstat, CallerName. The gate reads them for you.