Reply in-thread (recipients + threading derived)
POST /v1/inboxes/{inbox_id}/reply
Reply in-thread. Recipients, subject and threading headers are derived from the parent at SUBMIT time, so a queued reply shows the human the real envelope and the pre-flight screens the real recipients.
The resolved account/inbox review policy is authoritative on every send. Omitting fields does not bypass it. Read effective_review_policy on GET /v1/inboxes/{inbox_id} to know which branch you are on:
require_review(the default for every account): a request WITHOUT anintentis rejected with422 intent_required. Nothing is sent or queued. A request WITH an intent returns202 queued_for_review. Then monitor the review until you receive asentorsend_failedreview event.allow_direct: a bare request (no mode/intent/category_id) still sends immediately and returns the legacy202body, now plusreview_id. Supplying an intent ormode: reviewqueues it for a human.auto_send_graduated: a categorized message that clears the graduation gates auto-sends; everything else is queued.
Contact lists, list-unsubscribe suppression and the billing quota are all enforced at SUBMIT time, so a rejected recipient fails fast rather than after a human has already approved the draft.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”Opaque inbox id (the canonical path key). The inbox’s email address is also accepted here as a within-project alias. Treat the id as opaque.
Request Body required
Section titled “Request Body required ”Thread-aware reply. Exactly one of thread_id / message_id selects the parent; recipients, subject, and threading headers are derived server-side.
object
object
Include all original recipients (reply-all) when true.
Optional stale-context guard; returns 409 if the thread head changed before request materialization.
An outbound attachment on send/reply/forward.
object
Standard base64-encoded attachment bytes.
Review Loop per-send assertion (see SendRequest.mode).
The agent’s “for the human reviewer” summary. summary is REQUIRED when the resolved mode is review (else the submit is 422).
object
Free-text intent summary (who/what/why).
Structured intent payload.
object
Opaque category id (cat_…) matched from the registry.
Agent-supplied confidence (0..1) in the category match (see SendRequest.category_confidence). Feeds the min_confidence auto-send gate only; the server never scores.
Opaque token from a fresh, unfiltered GET /v1/rules for this agent, project, and category.
DEPRECATED body-level alias for the Idempotency-Key header (see SendRequest.idempotency_key). Send the header instead.
Thread-aware reply. Exactly one of thread_id / message_id selects the parent; recipients, subject, and threading headers are derived server-side.
object
object
Include all original recipients (reply-all) when true.
Optional stale-context guard; returns 409 if the thread head changed before request materialization.
An outbound attachment on send/reply/forward.
object
Standard base64-encoded attachment bytes.
Review Loop per-send assertion (see SendRequest.mode).
The agent’s “for the human reviewer” summary. summary is REQUIRED when the resolved mode is review (else the submit is 422).
object
Free-text intent summary (who/what/why).
Structured intent payload.
object
Opaque category id (cat_…) matched from the registry.
Agent-supplied confidence (0..1) in the category match (see SendRequest.category_confidence). Feeds the min_confidence auto-send gate only; the server never scores.
Opaque token from a fresh, unfiltered GET /v1/rules for this agent, project, and category.
DEPRECATED body-level alias for the Idempotency-Key header (see SendRequest.idempotency_key). Send the header instead.
Responses
Section titled “ Responses ”Sent immediately (policy-permitted direct or graduated path).
A Review Loop submit that was sent immediately (policy-permitted direct or graduated path). kind is always “sent”.
object
object
The review row that governed this send (ADDITIVE).
object
Opaque review id (rr_…).
Terminal state (sent | auto_sent).
Accepted; either queued for review or accepted for delivery.
A Review Loop submit that was parked for human review.
object
object
Opaque review id (rr_…).
Current review state (e.g. needs_review).
The mode after the policy resolved the agent’s assertion.
The outcome of a reply or forward.
object
Opaque review id (rr_…) for the review row that governed this send. ADDITIVE. Every agent-plane send now creates one, so an agent that crashes after issuing the request can still call GET /v1/reviews/{id} and read closed / sent_message_id instead of guessing whether the message went out.
The send/reply/forward is malformed or fails validation. Decode and validation failures are problem+json (code: bad_request) and NAME the offending field; an unknown key lists the accepted set, and supplying both text and the deprecated body with DIFFERENT content is errors[0].code = conflicting_alias (there is no safe guess, so the request is refused rather than relayed with the wrong bytes). A few pre-existing domain rejections reached through this path (e.g. domain_not_allowed) still carry the legacy {error, message} envelope.
RFC-9457 problem+json error body. code is a closed machine enum clients switch on; type is a dereferenceable URI under https://extrovert.dev/problems/. Served as application/problem+json.
object
The CLOSED machine code. The Review Loop members split what used to be a single opaque conflict, because an agent must take a DIFFERENT action on each: stale (the revision/version you named is no longer current; nothing was mutated; re-read, re-apply, resubmit; retryable, bounded) and born_stale (built against an older rule high-water; re-read the rules or restamp_review; at most one retry per high-water) are the ONLY retryable 409s. wrong_state means this VERB is illegal from the current state while the draft is still live; never retry the same verb, read the state and the repeated allowed_action hints in errors[] and pick a legal one. terminal means the review is already sent/auto_sent/cancelled and nothing will EVER succeed; stop, and drain your review events for the outcome. send_needs_reconciliation means a prior send is unconfirmed and parked. Do not resend. Poll instead. unavailable (503) is the retryable fail-closed answer when a dependency could not be read; it carries Retry-After and is distinct from not_configured, which is permanent for this deployment.
object
The canonical error envelope. error is a stable machine code.
object
Stable error code (e.g. unauthorized, forbidden, not_found, invalid, quota_exceeded, rate_limited).
Example
forbiddenHuman-readable detail (never leaks internals).
Example
missing required scopeMissing or invalid credential.
The canonical error envelope. error is a stable machine code.
object
Stable error code (e.g. unauthorized, forbidden, not_found, invalid, quota_exceeded, rate_limited).
Example
forbiddenHuman-readable detail (never leaks internals).
Example
missing required scopeAuthenticated but lacking the required scope, or out of quota.
The canonical error envelope. error is a stable machine code.
object
Stable error code (e.g. unauthorized, forbidden, not_found, invalid, quota_exceeded, rate_limited).
Example
forbiddenHuman-readable detail (never leaks internals).
Example
missing required scopeA Review Loop conflict, as problem+json. Branch on code, NOT on the 409 status; the four codes demand opposite behavior. stale: the revision/version you named is no longer current (a human moved the draft) and NOTHING was mutated; errors[] carries the current state, revision and version, so re-apply your change on top and resubmit with the new parent_revision (retry, bounded to ~3). born_stale: the redraft was built against an older rule high-water; re-read the rules and resubmit, or restamp_review if nothing genuinely changed (at most one retry per high-water). wrong_state: this VERB is illegal from the current state but the draft is still live; NEVER retry the same verb; errors[] repeats an allowed_action entry per verb that IS legal right now. terminal: the review is already sent/auto_sent/cancelled; nothing will ever succeed, stop retrying, and a front_run_next review event carries the outcome. send_needs_reconciliation: a prior attempt is unconfirmed and parked for recover-by-Message-ID; do NOT resend, poll the review.
RFC-9457 problem+json error body. code is a closed machine enum clients switch on; type is a dereferenceable URI under https://extrovert.dev/problems/. Served as application/problem+json.
object
The CLOSED machine code. The Review Loop members split what used to be a single opaque conflict, because an agent must take a DIFFERENT action on each: stale (the revision/version you named is no longer current; nothing was mutated; re-read, re-apply, resubmit; retryable, bounded) and born_stale (built against an older rule high-water; re-read the rules or restamp_review; at most one retry per high-water) are the ONLY retryable 409s. wrong_state means this VERB is illegal from the current state while the draft is still live; never retry the same verb, read the state and the repeated allowed_action hints in errors[] and pick a legal one. terminal means the review is already sent/auto_sent/cancelled and nothing will EVER succeed; stop, and drain your review events for the outcome. send_needs_reconciliation means a prior send is unconfirmed and parked. Do not resend. Poll instead. unavailable (503) is the retryable fail-closed answer when a dependency could not be read; it carries Retry-After and is distinct from not_configured, which is permanent for this deployment.
object
The send/reply/forward is well-formed but cannot be processed. Always problem+json; branch on code. intent_required (D3): the resolved review mode is review and no intent summary was supplied; nothing was sent and nothing was queued, and detail names the field to add. recipient_suppressed: one or more recipients have a list-unsubscribe opt-out, and errors[] names ONLY the suppressed addresses; never the suppression scope or origin; so the agent can retry without them.
RFC-9457 problem+json error body. code is a closed machine enum clients switch on; type is a dereferenceable URI under https://extrovert.dev/problems/. Served as application/problem+json.
object
The CLOSED machine code. The Review Loop members split what used to be a single opaque conflict, because an agent must take a DIFFERENT action on each: stale (the revision/version you named is no longer current; nothing was mutated; re-read, re-apply, resubmit; retryable, bounded) and born_stale (built against an older rule high-water; re-read the rules or restamp_review; at most one retry per high-water) are the ONLY retryable 409s. wrong_state means this VERB is illegal from the current state while the draft is still live; never retry the same verb, read the state and the repeated allowed_action hints in errors[] and pick a legal one. terminal means the review is already sent/auto_sent/cancelled and nothing will EVER succeed; stop, and drain your review events for the outcome. send_needs_reconciliation means a prior send is unconfirmed and parked. Do not resend. Poll instead. unavailable (503) is the retryable fail-closed answer when a dependency could not be read; it carries Retry-After and is distinct from not_configured, which is permanent for this deployment.
object
A dependency could not be read, so the request was failed CLOSED rather than served on a guess (Problem code = unavailable). Retryable; see Retry-After. Distinct from not_configured, which is permanent for this deployment.
RFC-9457 problem+json error body. code is a closed machine enum clients switch on; type is a dereferenceable URI under https://extrovert.dev/problems/. Served as application/problem+json.
object
The CLOSED machine code. The Review Loop members split what used to be a single opaque conflict, because an agent must take a DIFFERENT action on each: stale (the revision/version you named is no longer current; nothing was mutated; re-read, re-apply, resubmit; retryable, bounded) and born_stale (built against an older rule high-water; re-read the rules or restamp_review; at most one retry per high-water) are the ONLY retryable 409s. wrong_state means this VERB is illegal from the current state while the draft is still live; never retry the same verb, read the state and the repeated allowed_action hints in errors[] and pick a legal one. terminal means the review is already sent/auto_sent/cancelled and nothing will EVER succeed; stop, and drain your review events for the outcome. send_needs_reconciliation means a prior send is unconfirmed and parked. Do not resend. Poll instead. unavailable (503) is the retryable fail-closed answer when a dependency could not be read; it carries Retry-After and is distinct from not_configured, which is permanent for this deployment.
object
Headers
Section titled “Headers ”Seconds to wait before retrying.