Skip to content

Inboxes

List Inboxes

Inbox list screen: name, channels, 24h volume/verdict split, status.

The two target_* filters are what the agent page’s Inboxes card reads — an agent no longer owns an inbox, so “which addresses reach this agent” is a query over the inboxes pointed at it, not a field on the agent.

Parameters

NameInTypeRequiredDescription
limitqueryintegernoMax rows to return (1-100). Omit to return every row.
offsetqueryintegernoRows to skip — pass the previous response’s next_offset.
org_idpathstring (uuid)yes
project_idquerystringnoFilter by project id, or ‘none’ for uncategorized inboxes.
target_idquerystring (uuid)noOnly inboxes whose default handler is this agent or process.
target_kindquery"agent" | "process"noOnly inboxes whose default handler is of this kind.

Responses

StatusDescriptionBody
200Successful ResponsePage_InboxResponse_
422Validation ErrorHTTPValidationError

Create Inbox

Create an inbox. Requires admin/owner.

With no target_* and no channels this is the bare shell it always was — an inbox that receives nothing and routes nowhere until the Channels and Settings screens fill it in. Passing them stands the whole thing up in one transaction instead; no revision either way, which is an inbox that accepts everything and hands it to its default handler.

Parameters

NameInTypeRequiredDescription
org_idpathstring (uuid)yes

Request body (required)

FieldTypeRequiredDescription
channels"email" | "webhook"[]no
namestringyes
slugstringyes
target_idstring (uuid)no
target_kind"agent" | "process"no

Responses

StatusDescriptionBody
201Successful Responseagentdepot_api__routers__inboxes__InboxResponse
422Validation ErrorHTTPValidationError

Get Inbox

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful Responseagentdepot_api__routers__inboxes__InboxResponse
422Validation ErrorHTTPValidationError

PATCH /api/orgs/{org_id}/inboxes/{inbox_id}

Section titled “PATCH /api/orgs/{org_id}/inboxes/{inbox_id}”

Update Inbox

Settings screen: retention, rate limits, default handler, name/status.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes

Request body (required)

FieldTypeRequiredDescription
namestringno
rate_limit_classifier_calls_per_dayintegerno
rate_limit_messages_per_dayintegerno
rate_limit_per_sender_per_hourintegerno
rate_limit_per_sender_per_minuteintegerno
retention_daysintegerno
settingsobjectno
spike_protection_enabledbooleanno
statusstringno
target_idstring (uuid)no
target_kindstringno

Responses

StatusDescriptionBody
200Successful Responseagentdepot_api__routers__inboxes__InboxResponse
422Validation ErrorHTTPValidationError

DELETE /api/orgs/{org_id}/inboxes/{inbox_id}

Section titled “DELETE /api/orgs/{org_id}/inboxes/{inbox_id}”

Delete Inbox

Soft-delete an inbox and disable its channels. Requires admin/owner.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
204Successful Response
422Validation ErrorHTTPValidationError

GET /api/orgs/{org_id}/inboxes/{inbox_id}/channels

Section titled “GET /api/orgs/{org_id}/inboxes/{inbox_id}/channels”

List Channels

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseInboxChannelResponse[]
422Validation ErrorHTTPValidationError

POST /api/orgs/{org_id}/inboxes/{inbox_id}/channels

Section titled “POST /api/orgs/{org_id}/inboxes/{inbox_id}/channels”

Create Channel

Create a channel. An inbox may hold more than one of each kind.

Email: always gets a real 6-char address_token, whether or not it is rendered as part of the address — has_code only sets config["public"] (False = the code shows in the address; the default True is the codeless, public configuration). Webhook: gets an opaque address_token and nothing else — that token is the whole credential, so the response’s webhook_url is itself sensitive.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes

Request body (required)

FieldTypeRequiredDescription
configobjectno
has_codebooleanno
kindstringyes’email’ or ‘webhook’.

Responses

StatusDescriptionBody
201Successful ResponseInboxChannelResponse
422Validation ErrorHTTPValidationError

PATCH /api/orgs/{org_id}/inboxes/{inbox_id}/channels/{channel_id}

Section titled “PATCH /api/orgs/{org_id}/inboxes/{inbox_id}/channels/{channel_id}”

Update Channel

Parameters

NameInTypeRequiredDescription
channel_idpathstring (uuid)yes
inbox_idpathstringyes
org_idpathstring (uuid)yes

Request body (required)

FieldTypeRequiredDescription
configobjectno
enabledbooleanno
has_codebooleanno

Responses

StatusDescriptionBody
200Successful ResponseInboxChannelResponse
422Validation ErrorHTTPValidationError

DELETE /api/orgs/{org_id}/inboxes/{inbox_id}/channels/{channel_id}

Section titled “DELETE /api/orgs/{org_id}/inboxes/{inbox_id}/channels/{channel_id}”

Delete Channel

Hard-delete — InboxChannel carries no soft-delete column.

Parameters

NameInTypeRequiredDescription
channel_idpathstring (uuid)yes
inbox_idpathstringyes
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
204Successful Response
422Validation ErrorHTTPValidationError

POST /api/orgs/{org_id}/inboxes/{inbox_id}/channels/{channel_id}/rotate-token

Section titled “POST /api/orgs/{org_id}/inboxes/{inbox_id}/channels/{channel_id}/rotate-token”

Rotate Channel Token

Mint a fresh address token for a webhook channel. Requires admin/owner.

This is revocation: the token in the URL is what authenticates an inbound webhook, so rotating it kills the old URL the instant this commits. Every sender configured with it starts getting 404s and has to be given the new one — which is the point, and why it is not something an update can do by accident. The response carries the new webhook_url; the old one is not recoverable.

Parameters

NameInTypeRequiredDescription
channel_idpathstring (uuid)yes
inbox_idpathstringyes
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseInboxChannelResponse
422Validation ErrorHTTPValidationError

GET /api/orgs/{org_id}/inboxes/{inbox_id}/messages

Section titled “GET /api/orgs/{org_id}/inboxes/{inbox_id}/messages”

List Messages

Inbox -> Messages screen: every message, whatever happened to it.

Parameters

NameInTypeRequiredDescription
channelquerystringnoFilter by channel kind: email|webhook.
evaluation_statequerystringnoFilter by pipeline state: evaluating|settled. ‘evaluating’ is the in-progress view — messages that have arrived but whose rules are still running.
inbox_idpathstringyes
lanequerystringnoFilter to one of the screen’s lanes: needs_attention (held, evaluating and failed work) | all | accepted | dropped.
limitqueryintegernoMax rows to return (1-100). Omit to return every row.
offsetqueryintegernoRows to skip — pass the previous response’s next_offset.
org_idpathstring (uuid)yes
searchquerystringnoSubstring match over sender/subject.
statequerystring[]noFilter by message state — repeatable, OR’d together: accepted|failed|held|evaluating|dropped. One value per row (they partition the log), which verdict alone cannot do: it reports an in-flight message as held and an accepted one whose agent crashed as accepted. Passing an empty list is a real filter and returns nothing.
verdictquerystringnoFilter by verdict: accepted|dropped|quarantined. ‘quarantined’ returns settled messages only — a message still being evaluated carries that verdict provisionally and is not held for anyone.
within_hoursqueryintegernoOnly messages received in the last N hours. Omit for the whole log. A window rather than an absolute instant so the cutoff is computed once, here, from one clock — a client that sends its own timestamp sends a different one on every render.

Responses

StatusDescriptionBody
200Successful ResponsePage_InboxMessageIndexResponse_
422Validation ErrorHTTPValidationError

GET /api/orgs/{org_id}/inboxes/{inbox_id}/messages/{message_id}

Section titled “GET /api/orgs/{org_id}/inboxes/{inbox_id}/messages/{message_id}”

Get Message

Message detail: envelope, the per-stage trace in stage order (rendered as-is — the evaluator already writes it in execution order), raw-expiry.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
message_idpathstring (uuid)yes
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseInboxMessageDetailResponse
422Validation ErrorHTTPValidationError

POST /api/orgs/{org_id}/inboxes/{inbox_id}/messages/{message_id}/replay

Section titled “POST /api/orgs/{org_id}/inboxes/{inbox_id}/messages/{message_id}/replay”

Replay Message

Run any message through the pipeline again, whatever it was decided.

Retry only ever rescued mail nobody had decided. This is the other half: an accepted message whose chat went nowhere, or a dropped one a since-fixed rule should have let in. Both were previously dead ends — the only way to re-run either was to make the sender send it again.

Still re-evaluates rather than forcing: a replayed message that fails the gates is held or dropped again. A replay of accepted mail dispatches a second chat and leaves the first alone; the message row points at the new chat and keeps the old id in its trace.

Any member, not admin-only like retry: a replay spends the same classifier call a retry does, and gating the fix behind a role while leaving the inbound flood ungated protects nothing.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
message_idpathstring (uuid)yes
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseInboxMessageDetailResponse
422Validation ErrorHTTPValidationError

POST /api/orgs/{org_id}/inboxes/{inbox_id}/messages/{message_id}/retry

Section titled “POST /api/orgs/{org_id}/inboxes/{inbox_id}/messages/{message_id}/retry”

Retry Message

Run a held message again against the rule set that is live now.

The recovery path quarantine never had. Every uncertain outcome in this pipeline holds a message — a classifier timeout, a provider 5xx, an unroutable model — and all of those are fixable, but fixing one never rescued the mail it had already stopped.

Re-evaluates; it does not force. A message that still fails the gates stays held, because the gates are the point. Admin or owner, because it can create a chat and spend a classifier call.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
message_idpathstring (uuid)yes
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseInboxMessageDetailResponse
422Validation ErrorHTTPValidationError

POST /api/orgs/{org_id}/inboxes/{inbox_id}/messages/{message_id}/terminate

Section titled “POST /api/orgs/{org_id}/inboxes/{inbox_id}/messages/{message_id}/terminate”

Terminate Message

Stop whatever this message is still doing, and let it be restarted.

The lever the log was missing. Retry and replay both start something, and neither answers a message that is stuck: a replay of accepted mail leaves the original chat running on purpose, so a hung import replied to a replay by spending on two chats instead of none, and a row the pipeline died mid-evaluation on could not be touched at all for ten minutes.

Terminating stops and closes the dispatched chat and its whole subtree (which is where the spend usually is), releasing any question it left a human holding, cancels a dispatched process run, stands down an armed reply so the sender is not answered about work that was abandoned, and settles an evaluating row.

It does not re-decide the message: the verdict is left as the rules wrote it, and nothing is dispatched. Restarting afterwards is a replay, which now goes through immediately because the row is settled — subject to the usual limit that a payload past its retention window cannot be re-run at all.

Any member, like replay: this only ever stops work and never spends.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
message_idpathstring (uuid)yes
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseInboxMessageDetailResponse
422Validation ErrorHTTPValidationError

GET /api/orgs/{org_id}/inboxes/{inbox_id}/messages/counts

Section titled “GET /api/orgs/{org_id}/inboxes/{inbox_id}/messages/counts”

Message Counts

Counts for the messages screen’s lanes and filter chips.

Declared above /{message_id} on purpose: that route’s path parameter is a UUID, so a request for this one would otherwise be matched by it and rejected as a malformed id.

Takes the same channel/search/within_hours the list takes, and deliberately not state/lane: those are what the counts are for. Narrowing them by the chip the operator already clicked would leave every other chip reading zero.

Parameters

NameInTypeRequiredDescription
channelquerystringnoFilter by channel kind: email|webhook.
inbox_idpathstringyes
org_idpathstring (uuid)yes
searchquerystringnoSubstring match over sender/subject/rule.
within_hoursqueryintegernoOnly messages received in the last N hours.

Responses

StatusDescriptionBody
200Successful ResponseInboxMessageCounts
422Validation ErrorHTTPValidationError

GET /api/orgs/{org_id}/inboxes/{inbox_id}/revisions

Section titled “GET /api/orgs/{org_id}/inboxes/{inbox_id}/revisions”

List Revisions

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseInboxRevisionResponse[]
422Validation ErrorHTTPValidationError

POST /api/orgs/{org_id}/inboxes/{inbox_id}/revisions

Section titled “POST /api/orgs/{org_id}/inboxes/{inbox_id}/revisions”

Push Revision

Save the inbox’s draft rule set. Requires admin/owner.

Parses definition against IntakeRuleSet (422 on shape errors).

There is at most one open draft per inbox: this cuts a new version only when the newest revision is no longer editable (deployed, archived, or a superseded draft) — otherwise it overwrites the open draft in place and answers 200 rather than 201, because nothing was created. A definition whose hash matches the latest revision writes nothing at all (also 200, the existing revision returned). See inbox_service.push_revision for why saving is not a version-cutting operation.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes

Request body (required)

FieldTypeRequiredDescription
definitionobjectyes

Responses

StatusDescriptionBody
201Successful ResponseInboxRevisionResponse
422Validation ErrorHTTPValidationError

GET /api/orgs/{org_id}/inboxes/{inbox_id}/revisions/{revision_id}

Section titled “GET /api/orgs/{org_id}/inboxes/{inbox_id}/revisions/{revision_id}”

Get Revision

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes
revision_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseInboxRevisionResponse
422Validation ErrorHTTPValidationError

DELETE /api/orgs/{org_id}/inboxes/{inbox_id}/revisions/{revision_id}

Section titled “DELETE /api/orgs/{org_id}/inboxes/{inbox_id}/revisions/{revision_id}”

Discard Revision

Discard a draft revision — the open draft, or a superseded one the Rules screen surfaces as leftover debris. A deployed or archived revision is refused (409): those are history, and rolling back is how you return to one.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes
revision_idpathstring (uuid)yes

Responses

StatusDescriptionBody
204Successful Response
422Validation ErrorHTTPValidationError

POST /api/orgs/{org_id}/inboxes/{inbox_id}/revisions/{revision_id}/deploy

Section titled “POST /api/orgs/{org_id}/inboxes/{inbox_id}/revisions/{revision_id}/deploy”

Deploy Revision

Validate + deploy a draft revision. Requires admin/owner.

Swaps Inbox.current_revision_id and archives the previously-deployed revision in one commit — atomic from any reader’s point of view, and never touches an already-written inbox_messages.revision_id, so a message mid-evaluation when this runs finishes on the revision it started with.

Also relabels any other still-draft revision older than this one as superseded (see inbox_service.deploy_revision), so the Rules screen can tell a stale, never-deployed draft apart from one that’s genuinely ahead of what’s live.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes
revision_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseDeployInboxRevisionResponse
422Validation ErrorHTTPValidationError

POST /api/orgs/{org_id}/inboxes/{inbox_id}/revisions/{revision_id}/rollback

Section titled “POST /api/orgs/{org_id}/inboxes/{inbox_id}/revisions/{revision_id}/rollback”

Rollback Revision

Restore a previously-deployed (now archived) revision as current.

Not a re-deploy: no new version is created and no re-validation is forced — the revision was validated when it first went live, and version numbers are never renumbered (contract “Versioned revisions” decision). Requires admin/owner.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes
revision_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseDeployInboxRevisionResponse
422Validation ErrorHTTPValidationError

POST /api/orgs/{org_id}/inboxes/{inbox_id}/revisions/{revision_id}/validate

Section titled “POST /api/orgs/{org_id}/inboxes/{inbox_id}/revisions/{revision_id}/validate”

Validate Revision

Deep-validate a draft: every route/prompt reference resolves.

Runs PR 3’s typed rule vocabulary over the draft definition without dispatching anything — no envelope is evaluated, no chat or process run is created. Never mutates the revision; deploy re-runs this and refuses on the same errors.

Parameters

NameInTypeRequiredDescription
inbox_idpathstringyes
org_idpathstring (uuid)yes
revision_idpathstring (uuid)yes

Responses

StatusDescriptionBody
200Successful ResponseValidateRevisionResponse
422Validation ErrorHTTPValidationError