Skip to content

POST /v1/decisions/should_send

Call POST /v1/decisions/should_send
Price $0.003 per call, billed only when an answer is returned
Scope decisions:write
Operation id shouldSend

Should this prepared message go out now? Returns send as p and a hold_reason.

Call it as the last gate before a prepared message goes out. Give the message, the decision ids behind it, the inbox health you measure and the ICP. Returns send as p, answer (send or hold) and a hold_reason when holding. Verdict never sends anything.

Price: $0.003 per call (x-verdict-price-usd), billed only when an answer is returned: errors (including decision_unavailable, 503 with Retry-After) and refused calls are free. Send X-Max-Cost-USD to cap spend and Idempotency-Key (required) to retry safely; a repeated key within 24 hours returns the stored response and bills nothing. Every response carries the common envelope: id, p (always present, calibrated), answer, reason_code, reason, evidence, calibration, cost and balance_usd. Report what happened next with POST /v1/outcomes.

Scope: decisions:write.

Copy it as curl. Set VERDICT_API_KEY first; uuidgen makes a fresh Idempotency-Key.

Request
curl -X POST 'https://api.askverdict.io/v1/decisions/should_send' \
-H "Authorization: Bearer $VERDICT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{
"message": {
"channel": "email",
"subject": "Trade ops after your raise",
"body": "Hi Dana, congratulations on the new fund. We help 10-person advisers automate trade reconciliation. Open to a short call next week?"
},
"decision_ids": [
"dec_01JC3Z4Y8R9K2M5N7P1Q3S5T7V"
],
"inbox_health": {
"bounce_rate_7d": 0.012,
"spam_complaint_rate_7d": 0.0005,
"sent_today": 38,
"daily_limit": 50
},
"recipient": {
"touches_30d": 1
},
"icp_id": "icp_01JC3Z4Y8R9K2M5N7P1Q3S5T7V"
}'

200: The should_send decision, in the common envelope.

200 response
{
"id": "dec_01JC3Z4Y8R9K2M5N7P1Q3S5T7V",
"version": "2026-11-01",
"question_set_version": "2026-11-01",
"model": "jev",
"calibration": {
"basis": "org",
"n_outcomes": 412,
"threshold": 0.6
},
"latency_ms": 180,
"created_at": "2026-11-07T14:03:00Z",
"decision": "should_send",
"p": 0.88,
"answer": "send",
"reason_code": "all_checks_passed",
"reason": "Upstream decisions clear their thresholds and inbox health is within bounds.",
"evidence": [
{
"source": "request",
"url": null,
"observed_at": "2026-11-07",
"pointer": "/inbox_health"
}
],
"send": 0.88,
"hold_reason": null,
"cost": {
"decision_usd": 0.003,
"vendor_usd": 0,
"total_usd": 0.003
},
"balance_usd": 18.301
}
Name In Type Required Description
idempotency-key header string (uuid) yes A UUID you generate per logical request. Required on every POST. Repeating it within 24 hours returns the stored response and bills nothing; reusing it with a different body returns 409 idempotency_conflict.
x-max-cost-usd header string no Maximum you are willing to pay for this call, in US dollars (up to 6 decimals). If the call would cost more, it is refused with 402 max_cost_exceeded before spending.
x-verdict-version header string no API version to use, a date. Defaults to the version current when the key was created. The current version is 2026-11-01.

Schema ShouldSendRequest. The should_send input.

Field Type Required Description
message object yes The prepared message.
message.channel enum: “email”, “other” yes Channel the message will go out on.
message.subject string no Subject line.
message.body string yes Plain-text body as it will be sent. At most 8192 bytes of UTF-8.
decision_ids array of string yes The decisions behind the message (for example qualify_firm and pick_person), 1 to 10.
inbox_health InboxHealth yes Inbox health you measure; Verdict never reads your inbox.
inbox_health.bounce_rate_7d number no Share of sends that bounced over the last 7 days.
inbox_health.spam_complaint_rate_7d number no Share of sends marked as spam over the last 7 days.
inbox_health.reply_rate_7d number no Share of sends replied to over the last 7 days.
inbox_health.sent_today integer no Messages sent from this inbox today.
inbox_health.daily_limit integer no Your daily send limit for this inbox.
inbox_health.domain_age_days integer no Age of the sending domain in days.
recipient object no Optional contact history of the recipient.
recipient.last_contacted_at string (date-time) no When you last wrote to this recipient.
recipient.touches_30d integer no Messages sent to this recipient in 30 days.
icp_id string yes The ICP profile to decide against.

Schema ShouldSendResponse.

Field Type Required Description
id string yes The decision id. Report outcomes against it.
decision enum: “should_send” yes The decision name.
version enum: “2026-11-01” yes API version that served the decision.
question_set_version string (date) yes Version (a date) of the question set that answered (spec 6.1).
model enum: “jev” yes Decision model that answered: always jev (ADR-0008).
p number yes Calibrated probability (spec 6.4). Always present.
answer enum: “send”, “hold” yes send or hold: p applied to your threshold.
reason_code enum: “all_checks_passed”, “upstream_decision_below_threshold”, “upstream_decision_missing”, “inbox_health_degraded”, “daily_volume_limit_reached”, “recipient_recently_contacted”, “message_quality_issue”, “recipient_opted_out” yes Why, from the fixed should_send reason-code enum.
reason string yes Short templated explanation of reason_code; never free text from a frontier model.
evidence array of Evidence yes Cited sources, with URLs and dates.
evidence[].source enum: “sec_edgar”, “gleif”, “companies_house”, “wikidata”, “request” yes The index source cited, or request when the evidence is part of your own input.
evidence[].url string or null (uri) yes Link to the cited record at the source; null when source is request.
evidence[].observed_at string (date) yes Date the source reported the fact.
evidence[].label string no Short description of what the evidence shows.
evidence[].pointer string no JSON Pointer into your request body when source is request, for example /thread/2.
calibration Calibration yes How p was calibrated (spec 6.4).
calibration.basis enum: “org”, “segment”, “global” yes org (your own outcomes), segment (your ICP’s segment) or global: how much to trust p.
calibration.n_outcomes integer yes Outcomes behind the calibration map used.
calibration.threshold number yes The threshold answer applied to p.
cost Cost yes What this call was billed, itemized. Misses and errors bill nothing.
cost.decision_usd number yes Price of the decision. US dollars, up to 6 decimals.
cost.vendor_usd number yes Vendor pass-through charges (always 0 in v1). US dollars, up to 6 decimals.
cost.total_usd number yes Total billed for this call. US dollars, up to 6 decimals.
balance_usd number yes Balance after this call. US dollars, up to 6 decimals.
latency_ms integer yes Server-side latency of the call, in milliseconds.
created_at string (date-time) yes When the decision was made. Decisions are immutable.
send number yes Probability that sending now is right; equal to p.
hold_reason enum: “upstream_decision_weak”, “upstream_decision_missing”, “inbox_health_degraded”, “daily_volume_limit”, “recently_contacted”, “message_issue”, “recipient_opted_out”, “low_confidence”, null yes Why to hold, when answer is hold; null when answer is send.

Errors are RFC 9457 problem details and are never billed. Branch on type. See Errors.

Status Meaning
400 Invalid request: malformed JSON, an unknown field, a failed constraint or a size limit (invalid_request).
401 Missing, malformed, revoked or expired API key, or a bad webhook signature (unauthorized).
402 Payment required: the balance cannot cover the call (insufficient_balance), or the call would exceed X-Max-Cost-USD (max_cost_exceeded). Nothing was spent.
403 The key lacks the scope this operation needs (forbidden_scope).
404 The resource does not exist in this organization (not_found).
409 The Idempotency-Key was reused with a different request, or the first request is still running (idempotency_conflict).
429 Rate limited (rate_limited). Wait Retry-After seconds.
500 Unexpected server error (internal). Not billed; retry with the same Idempotency-Key.
503 Temporarily unavailable: the decision model failed, timed out or is rate limited, or decisions are switched off (decision_unavailable), or a vendor the call depends on is unavailable (vendor_unavailable, phase 2). Not billed. Wait Retry-After seconds and retry with the same Idempotency-Key.

Open the interactive reference to send this request from the browser. The full document is at /openapi.json.