API reference

All API requests go to https://YOUR-REFLEX-HOST/v1/... and authenticate with a secret key:

authorization: Bearer rl_live_...

Requests and responses are JSON. Bodies can be up to 64 KB.

POST /v1/check

Checks one agent turn. Each new turn counts as one check against your plan. Follow-up checks of the same reply sent with a continuationToken (see Sentence-by-sentence checks) are free for the first 12 follow-ups of a reply; each one after that counts as one check.

Request

Field Type Required Notes
agentId string yes The agent whose rule pack to use (agt_...).
callId string yes Your call or session id, up to 128 characters. Turns with the same callId are grouped in the call log.
turnIndex integer no Position of the turn in the call, starting at 0.
callerText string one of the two What the caller just said, up to 4,000 characters.
draftReply string one of the two What the agent is about to say, up to 4,000 characters. Without it, only caller signals are returned.
history array no Up to 50 earlier turns, each { "role": "caller" or "agent", "text": "..." }. The last few are used.
context string or object no Facts from tools for this call, such as an account balance or a booked slot, up to 8,000 characters as JSON. Dollar amounts here are allowed in the reply.
options object no See below.

options:

Field Default Notes
timeoutMs 400 Overall budget for the model call, 50 to 2,000.
hedgeAfterMs 140 If the first model request hasn't answered by then, a second identical one is sent and the first answer back wins. null turns this off.
onJevError "deterministic" What to do if the model times out or errors: "deterministic", "block" or "pass". See below.
signals true Set to false to skip intent, handoff, mood and end-of-turn. This makes the request a little smaller.

Response

Field Notes
turnId Id of the logged turn.
decision "pass", "review" or "block".
say The text to speak: the draft on pass or review (when reviewAction is pass), otherwise the fallback line. null if no draft was sent.
fallbackUsed true when say is a fallback line.
fired Rules that fired or landed in the review band: ruleId, title, severity, band ("fire" or "review"), probability, source ("jev", "pattern", "price_list" or "hours"), and an optional detail.
signals.intent { id, confidence, probabilities } over the pack's intents.
signals.handoff { probability, recommended }. recommended uses the pack's handoff threshold.
signals.sentiment { score, label, probabilities }. Score runs from 0 (calm) to 3 (furious). Labels are calm, mildly annoyed, upset and furious.
signals.endOfTurn { probability, complete }: whether the caller seems to have finished.
signals.callerManipulation { probability, detected }: the caller is trying to make the agent break its rules.
signals.backchannel { probability, detected }: the caller only said "mm-hm" or "okay" and isn't taking the turn.
latencyMs { total, jev } in milliseconds, measured inside Reflex Layer. A server-timing header carries the same values.
usage { inputTokens, jevCalls }.
degraded null, or { reason, policy } when the model didn't answer in time.
packId, packVersion Which rules were applied.

How the decision is made

Each rule produces a probability that it's broken. For the rule's thresholds (or the pack's defaultThresholds, by default block 0.65 and review 0.35):

The strictest outcome wins. Pattern rules return 1 or 0. Price and hours rules are code, not the model, and score like this:

Timeouts and failures

If the model hasn't answered within timeoutMs, the response comes back with degraded set:

The same policy applies if the account's monthly model budget is reached (the reason then mentions "budget"). Each paid plan includes model use well above its check count, so on paid plans this only happens with unusually long turns. On the Free plan it can also happen late in a busy month, because free accounts share a monthly model budget.

Your own code should also set a network timeout (we suggest 600 ms) and speak the draft or a fallback if Reflex Layer can't be reached at all. The official integrations do this.

Sentence-by-sentence checks

When you check a streamed reply one sentence at a time, send the first check as usual, with turnIndex set. The response includes continuationToken (it is only issued when turnIndex is present). For each later sentence, send the whole reply so far as draftReply and add:

Option Type Meaning
options.continuationToken string The token from the first check of this reply.
options.alreadySpoken number How many characters of draftReply were already spoken.
options.signals boolean Set false to skip intent and handoff signals on later sentences.

The token is signed for your account, agent, call, turn index and the first sentence's text, and expires 2 minutes after the first check (follow-ups return the same token, they don't extend it). Each follow-up's draftReply must start with the text of that first check. Checks with a valid token update the same turn in the call log (the strictest decision wins) and are not billed as new checks, up to 12 follow-ups per reply; each follow-up after the 12th is billed as one check. A missing, expired, altered or mismatched token starts a new, billed turn. The Python and Node clients handle this for you.

POST /v1/endpoint

A light check on a partial transcript, to help decide whether the caller has finished speaking. Send agentId, callId, callerText and optionally history. Returns endOfTurn, backchannel, latencyMs, usage and model. Counts as one check.

POST /v1/vapi/{agentId}/chat/completions

An OpenAI-compatible endpoint for Vapi's Custom LLM. See Integrations.

GET /v1/health

Returns { "ok": true }. No key needed.

Errors

Errors look like { "error": { "code": "...", "message": "...", "details": { ... } } }.

Status Code Meaning
400 bad_json The body isn't valid JSON.
401 unauthenticated Missing, invalid or revoked key.
402 quota_exceeded This month's plan checks are used up. Checks resume on the 1st (UTC) or after an upgrade. There is no overage billing. Treat it like a timeout: let the agent speak.
403 email_unverified The account email is not confirmed yet. Open the link in the confirmation email or resend it from the dashboard.
404 agent_not_found, not_found Unknown agent or route.
409 no_upstream Vapi proxy used before an upstream model was set.
413 too_large Body over 64 KB.
422 invalid Invalid request. details names each bad field.
502 upstream_error Your upstream model (Vapi proxy) returned an error.
503 not_configured The service is missing configuration. Contact support.

Webhooks

Set one webhook URL under Connect. It must be an https URL on a public host name. Events:

Each delivery is a POST with a JSON body { id, type, created, data } and a header:

x-reflex-signature: t=1731300000,v1=5f2c...

v1 is the hex HMAC-SHA256 of t + "." + rawBody, using your webhook secret. Reject deliveries where the signature doesn't match or t is more than 5 minutes old. See the verifier in Integrations.

Deliveries time out after 5 seconds and aren't retried yet. Use them for alerts and follow-ups, not as your only record. The call log keeps everything.