Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.crewship.ai/llms.txt

Use this file to discover all available pages before exploring further.

Inbox

The inbox is the unified human-in-the-loop surface for the workspace. Rows are written by source-of-truth handlers (waitpoint create, escalation create, run-failure terminal, generic agent messages); these three endpoints are the read + state-flip surface the UI consumes — no inserts. For the source endpoints that create inbox items (and the conflict rules about how items get resolved), see the Inbox guide. All endpoints require an authenticated session and a workspace context.

Visibility model

Every query restricts to:
workspace_id = <current workspace> AND (
   (target_user_id IS NULL AND target_role IS NULL)   -- workspace-wide
   OR target_user_id = <session user>                 -- addressed to me
   OR target_role    = <my workspace role>            -- addressed to my role (OWNER, ADMIN, ...)
)
An item addressed to OWNER never appears for a MEMBER, even if both belong to the same workspace. The visibility predicate is identical across List / UnreadCount / PatchState — a cross-workspace or cross-target id returns 404, not a silent no-op, so the surface can’t be probed for which items exist.

Item shape

FieldTypeNotes
idstring (CUID)
workspace_idstringAlways the current workspace.
kindstringwaitpoint, escalation, failed_run, message (extensible).
source_idstringSource row id — e.g. waitpoint token, escalation id, pipeline run id.
target_user_idstring | omittedSet when addressed to a specific user.
target_rolestring | omittedSet when addressed to a workspace role.
titlestringDisplay title.
body_mdstring | omittedMarkdown body for the detail panel.
sender_typestring | omitteduser or agent.
sender_idstring | omitted
sender_namestring | omittedPre-joined display name.
state"unread" | "read" | "resolved"
prioritystringlow / normal / high / urgent.
blockingboolWhen true, the source flow is paused until the item is resolved.
payloadobject | omittedParsed JSON — inlined so the UI doesn’t need a second JSON.parse.
read_atRFC3339 | omitted
resolved_atRFC3339 | omitted
resolved_by_user_idstring | omitted
resolved_actionstring | omittedapproved, rejected, retried, cancelled — what the human did.
created_atRFC3339
updated_atRFC3339

GET /api/v1/inbox

Paginated, newest-first list. The UI calls this on first load and after every WS inbox.updated event.
Query paramTypeDefaultNotes
stateunread | read | resolved | allallLinear-Triage default — resolved items stay visible-but-dimmed. Invalid value → 400 invalid state.
kindstring(unset)Narrows by item type.
limitint100Capped at 500.
The response inlines both row count and unread count so the bell badge renders from the same fetch (no second round-trip on every poll):
{
  "rows": [
    {
      "id": "ibx_01HVZ...",
      "workspace_id": "ws_01HV...",
      "kind": "waitpoint",
      "source_id": "wp_tok_abc",
      "target_role": "OWNER",
      "title": "Review production deploy",
      "body_md": "PR #128 ready for production roll-out…",
      "sender_type": "agent",
      "sender_id": "ag_01HVY...",
      "sender_name": "Daniel",
      "state": "unread",
      "priority": "high",
      "blocking": true,
      "payload": { "deploy_target": "prod-us-east-1" },
      "created_at": "2026-05-20T08:14:22Z",
      "updated_at": "2026-05-20T08:14:22Z"
    }
  ],
  "count": 1,
  "unread_count": 7
}

GET /api/v1/inbox/count

Bell-badge endpoint. Same visibility predicate as List, cheaper payload — no JSON parse, no payload column read.
{ "unread_count": 7 }

PATCH /api/v1/inbox/{id}

Flip an item’s state. The body is JSON:
{
  "state": "resolved",
  "resolved_action": "approved"
}
FieldTypeNotes
state"unread" | "read" | "resolved"Required. Invalid → 400 state must be unread|read|resolved.
resolved_actionstringOptional. Recorded on resolved transitions so the audit trail captures what the user did, not just that they did something. Conventional values: approved, rejected, retried, cancelled.
Returns 200:
{ "id": "ibx_01HVZ...", "state": "resolved" }

Source-managed kinds — 409 Conflict

For kind in { waitpoint, escalation, failed_run }, the inbox row is a mirror of an authoritative source row (the waitpoint token, the escalation row, the failed pipeline run). PATCH only supports state: "read" for these — unread and resolved would desync the inbox row from the source, because the user expects the flip to also approve the waitpoint / close the escalation / retry the run, and the inbox PATCH doesn’t do that. The non-read transition returns 409 with a hint at the right endpoint:
{
  "error": "use the source endpoint for this kind (e.g. /pipelines/waitpoints/{token}/approve) — inbox PATCH only supports 'read' for source-managed items",
  "kind": "waitpoint"
}
Generic kinds (message) flip freely between all three states.

State transitions

  • → read sets read_at and read_by_user_id (both COALESCE’d so re-reading doesn’t move the timestamp).
  • → unread clears read_at, read_by_user_id, resolved_at, resolved_by_user_id, resolved_action.
  • → resolved sets resolved_at, resolved_by_user_id, resolved_action (and overwrites whatever’s currently set, in case the user resolved with a different action).

WebSocket event

Every successful PATCH broadcasts on the workspace channel:
{
  "type": "inbox.updated",
  "data": { "id": "ibx_01HVZ...", "state": "resolved" }
}
The frontend re-fetches list + count on this event so every connected client sees the same state without polling.

See also

  • Inbox guide — the user-facing UI on top of these endpoints, and the source-handler conflict rules.
  • Notifications — the read-only activity feed (different table, different fan-out path).
  • Approvals — waitpoint approve/reject endpoints invoked when an inbox item’s source is a waitpoint.
  • WebSocket — channel + event format for inbox.updated.