`GET /api/inbox/board` — one server-paginated page of the union Posteingang plus the per-source facets and the caller-scoped actionable count (#976). The global `/api` approval layer already gates the account state.
const url = 'https://example.com/api/inbox/board';const options = {method: 'GET'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://example.com/api/inbox/boardParameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”Source filter chip: workflow | tool | skill | governor | memory
| proposal | suggestion | notification. Absent = the full union.
Page size, clamped to [0, 100]; 0 reads counts/facets only (the badge
poll). Default 25.
Page offset into the merged union (clamped to the 200-row union depth).
Responses
Section titled “Responses”One page of the union inbox.
One page of the union inbox: merged newest-first rows, the depth-capped
union total for the current filter (see MAX_UNION_DEPTH), and the
workspace-wide facets.
object
The per-source totals plus the two derived numbers the shell consumes.
object
What waits on a decision THIS caller can take: workflow/tool gates and pending proposals for every member; skill/memory/suggestion queues only for workspace admins (their decide endpoints are admin-gated); governor only for the owner. THE badge number — notifications never count here.
TRUE total rows per visible source (uncapped — the chips). A source the caller cannot see (role/entitlement) is absent — no chip, no phantom count; a transiently-errored source stays listed at 0.
object
Unread notifications (display-only; read state is not actionability).
Workflow human gate waiting on the caller (one entry per RUN — parallel gates of the same run collapse to the oldest, matching the decide-by-run-id API).
object
Workflow human gate waiting on the caller (one entry per RUN — parallel gates of the same run collapse to the oldest, matching the decide-by-run-id API).
object
Interactive task tool/question/visual approval.
object
Interactive task tool/question/visual approval.
object
#712 UX-3 — the agent (harness) type of the owning task (e.g. claude). None
when the task was deleted.
#712 UX-3 — the owning task’s project name. None when the task has no project
or it was deleted.
#712 UX-3 — the owning task’s title, so the Freigaben inbox row shows WHICH
piece of work is asking (the id alone is an 8-hex prefix). None when the task
was since deleted (LEFT JOIN). Non-sensitive identifier; not redacted.
Induced-skill proposal in draft.
object
Induced-skill proposal in draft.
object
When the proposal was filed (#976 — the union inbox sorts newest-first; without this the row silently sank below month-old notifications).
Governor profile-version proposal (owner only).
object
Governor profile-version proposal (owner only).
object
When the proposal was opened (#976 — union-inbox sort key).
The candidate config, stored in AgentProfileInput shape so approval reuses
the validated update_profile writer. Runtime stays serde_json::Value; the
spec types it as [AgentProfileInput] (the FE reads name / fixed_model /
model_policy off it).
object
ADR 0045 §3.6 — default reasoning effort
(minimal | low | medium | high | max). Optional; preserves the current
value on update, leaves None (inherit the runner default) on create.
Issue #364 / ADR 0039 (D7); #708 who-3 — normal | workflow (ultracode
RETIRED to the effort axis). Optional; preserves the current value on update,
defaults to normal on create.
ADR 0045 §3.2 — opt this profile into the interactive tool-approval gate
(resolve_interactive_approvals_for_run). Optional; preserves the current
value on update, defaults to false on create. #708 W5 wired this WRITE
path (it was read-only before — the create/update never persisted it).
#838 — per-profile DEFAULT for the subagent concurrency ceiling (migration
358; 1..=64). The middle tier of task.config.max_subagents ?? profile ?? workspace ?? (no env). DOUBLE-Option with the same semantics as
[Self::max_turns]: absent preserves the current value on update / leaves
NULL on create; an explicit null clears it; a value sets it. “Absent =
unchanged” is load-bearing — the simple profiles tab sends a partial payload
and must never null a stored default (the #858 W4a ops-cap pattern).
#1018 — per-profile DEFAULT for the harness turn limit (migration 340,
positive). DOUBLE-Option: absent preserves the current value on update / leaves
NULL (no default — the runner’s 500 applies) on create; an explicit null
clears it; a value sets it. “Absent = unchanged” is load-bearing — the
simple profiles tab sends a partial payload, and it must never null a
stored default (the #858 W4a ops-cap pattern).
ADR 0049 (#547 / #548) — opt-in gate for the agent memory.create MCP
tool. Optional; preserves the current value on update, defaults to false
(fail-closed) on create. A workspace admin sets this on the profile editor
to open the unreviewed direct-write path.
#858 W4a — per-profile override of the profile-hourly MCP Ops budget
(migration 320; range 1–1000). Same DOUBLE-Option semantics as
mcp_ops_per_task_cap. The workspace-daily Ops ceiling stays fixed.
#858 W4a — per-profile override of the per-task/per-tool MCP Ops budget
(migration 320; range 1–500). DOUBLE-Option: absent preserves the current
value on update / leaves NULL (the ADR 0028 default, 10) on create; an
explicit null clears the override back to the default; a value sets it.
Admin-gated — the profile PUT already requires workspace admin.
#1176 — the profile’s RESOURCE CLASS (migration 361; normal | heavy).
The middle tier of task.config.resource_class ?? profile ?? workspace ?? normal.
A class is the profile’s statement about how much MACHINE its work needs:
the container’s memory cap and, inseparably, how many of the workspace’s
concurrency slots the task holds. Declaring heavy therefore does not
raise the host’s peak footprint — it trades parallelism for room.
DOUBLE-Option with the same semantics as [Self::max_turns]: absent
preserves the current value on update / leaves NULL on create; an explicit
null clears it; a value sets it. “Absent = unchanged” is load-bearing —
the simple profiles tab sends a partial payload and must never null a
stored declaration.
The script-run allowlist. Optional; absent preserves the current value on update
/ leaves None (OPEN — every script allowed) on create; a value (incl. [],
fail-closed) sets it. Mirrors capability_allowlist. #708 W9 wired this so a
workspace manifest round-trips it — the manifest apply supplies a fail-closed
Some([]) default (never the open None) when the manifest omits it.
#1175 (migration 363) — the profile’s TOOL-GATE deny budget in minutes,
the middle tier of task.config.tool_approval_deny_minutes ?? profile ?? workspace ?? 1440.
It governs ONLY the deny timeout policy, which a caller must name
explicitly. hold — the default for every runner-raised gate since #1164 —
carries no deadline, and no value here can give it one.
Range [5, 43200], enforced by the migration-363 CHECK and refused BY NAME
at write time rather than clamped, so a stored value is always the value
that applies. DOUBLE-Option, same semantics as [Self::max_turns].
Memory-governance review item (pending).
object
Memory-governance review item (pending).
object
#1392 — the producer’s CONFIDENCE in this proposal, 0..1, or NULL when the
producer has no honest signal (closeout reports, outcome-heuristic candidates,
agent/manual proposals that name none). Council candidates carry the judge’s
own judge_confidence. Distinct from proposed_importance: importance says how
much a memory matters, confidence says how sure the producer is. The review
detail renders the value, or an explicit “Unknown” when this is NULL.
#903 — the proposal’s own short TITLE (“Closeout: proposed_text,
which is the agent’s summary — so every row read as a sentence fragment.
NULL on pre-#903 rows; the frontend then falls back to the closeout evidence’s
task title and finally to the text snippet.
AI scaffold proposal (pending).
object
AI scaffold proposal (pending).
object
The target App, surfaced under the frontend’s app_id name (the row
stores it as the generic target_id). Only app.file_upsert carries one.
What an approve materialized ({ kind, resource_id }). Populated on the
approve response; null on list/get reads.
The proposing agent task id (when proposer_kind == "agent"); null for
a user-raised proposal.
Reviewer’s reject note. Not persisted on the row (it lives in the audit
trail), so it is null on reads.
Human one-liner derived from kind + payload (e.g. the script name or
“3 files”), so the inbox list reads without parsing the payload itself.
Governor recommendation (active).
object
Governor recommendation (active).
object
Support conversation (ADR 0067 Addendum A). Present in the feed, but never
actionable — it is work, not a decision, so it does not move the badge.
object
Support conversation (ADR 0067 Addendum A). Present in the feed, but never
actionable — it is work, not a decision, so it does not move the badge.
object
provider | local — WHICH SYSTEM owns the assignment. The UI must not
offer a local reassign control on a provider row; that assignment lives in
Chatwoot/Intercom and a local write would be silently overwritten on the
next delivery.
In-app notification (read or unread — read state rides the row).
object
In-app notification (read or unread — read state rides the row).
object
Body text; null for notifications carrying only a title.
Context JSON (never includes raw tokens) — an open map.
object
Email address; set for unregistered invitees; null for registered users.
When the notification was read; null while unread.
FK to the recipient user; null for a pre-registration email-only target.
Example
{ "items": [ { "source": "workflow" } ]}Structured client error
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Authentication required
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Permission denied (also raised when ?source= names a source this caller cannot see)
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Structured server error
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}