Skip to main content
The Crew Journal is the canonical, append-only event log for a workspace. These endpoints let you read entries (with rich filtering), stream them live over SSE, count a filtered set, look up display metadata for journal cards, and set the one operator-mutable field — the priority marker. See the Crew Journal guide for the data model and entry-type catalog.
Every endpoint requires authentication and is workspace-scoped via the session context. workspace_id is always pulled from the session, never from query params — cross-workspace reads are impossible.
The journal is append-only at the entry level — there is no endpoint that creates, mutates, or deletes a journal row’s payload; entries come from backend code emitting via journal.Writer. The one writable surface is the operator-facing priority marker (POST /api/v1/journal/{id}/priority), which updates only the priority column on an existing row and emits its own memory.priority_changed audit entry.

Endpoints


List entries

The primary read surface — page through entries with a rich, AND-combined filter set.
Query parameters: All filters are AND-combined; CSV-valued ones expand to IN (?, ?, ...) predicates. Response: 200 OK
Errors:

Stream entries (SSE)

Live tail of the journal over Server-Sent Events, seeded with a recent batch then switched to polling.
Server-Sent Events feed. Same query-param filters as List (limit is forced to 50 for the seed batch). Response headers:
Event frames:
  • Seed: sends the most recent 50 entries matching filters, then switches to live polling.
  • Poll interval: 1 second.
  • Heartbeat: : heartbeat comment every 15s to keep proxies from closing idle connections. Event handlers ignore comments.
  • Watermark: compound (ts, id) so bursts sharing a ms timestamp aren’t partially dropped.
  • Reconnect: clients send the SSE Last-Event-ID header; the server looks the entry up, treats its ts as the lower bound, and pages through the gap (up to 500 entries, 10 × 50-row pages) before switching to live polling. A disconnect that produced more than 500 matching entries truncates the older end of the gap and the server logs a warn — clients should reconcile by re-fetching /api/v1/journal?since=<last-known-ts> if they need a longer window.
Errors:

Get single entry

Fetch one entry by ID, workspace-scoped.
Path parameters: id is the journal entry ID. The request has no body; the workspace is always taken from the authenticated session. Returns one entry by ID, workspace-scoped. The response body is a single object whose shape matches one element of the entries[] array on List — same fields, same omitempty rules. Response: 200 OK
Errors:

Count entries

Total matching a filter, for result-set badges that stay honest under filter changes.
Returns the total number of entries matching the same query parameters as List, ignoring cursor and limit. The UI uses this to render result-set badges that stay honest under filter changes — without it, the only way to know the total was to page through every entry. Query parameters: identical to List, except cursor and limit are silently ignored. Response: 200 OK
Errors:

Set entry priority

The only operator-mutable surface — flag an entry’s importance without touching its payload.
Annotate one entry with an importance marker (normal, high, pin, permanent). Marker affects compaction and recall — see the Crew Journal guide.
Authorization: caller must hold OWNER or ADMIN on the workspace. MEMBER and below get 403.
Request body:
Response: 200 OK
Side effect: writes a memory.priority_changed audit entry to the journal whose payload carries target_entry_id, previous_priority, new_priority, and the supplied reason. The audit emit is best-effort — the priority update is durable even if the audit write fails (a warning is logged in that case). Errors:

Workspace lookup table

The join surface for journal cards — entries store stable IDs only, this resolves them to display strings.
Request: no body. The workspace is derived from the authenticated session; there are no query parameters. The authenticated workspace context determines the lookup scope. Returns workspace-scoped crews, agents, and missions for journal-card enrichment (palette colours and lucide icons). Fetched once on page mount; the frontend invalidates it from realtime crew/agent/mission events. Backend handler: internal/api/journal_lookup.go. Response: 200 OK
Each list is capped at 1000 rows; workspaces beyond that get a useful subset. Empty lists are guaranteed non-null (the JSON arrays are always present), so the frontend can .find() without nil guards. Soft-deleted crews and agents (deleted_at IS NOT NULL) are filtered out. Missions have no soft-delete column; all rows are returned, ordered by created_at DESC within the cap. The journal entries themselves never carry display strings — they store stable IDs only. This endpoint is the join surface used by every UI card. Errors: 401 on missing workspace; 500 on DB error.

Spend rollup

Cost rollup over cost.incurred journal entries and pipeline_runs.cost_usd (#1404) — a separate, journal-native surface from Paymaster’s /api/v1/paymaster/spend/*.
Query parameters: Response: 200 OK
Errors: CLI: crewship spend --window 7d --top 10 — see crewship spend.

Tenancy

  • workspace_id is always pulled from the session context — not from query params.
  • Cross-workspace reads are impossible: journal.List / Get / Count refuse to run without a workspace filter.
  • Unknown or cross-tenant IDs return 404 with the same body as “not found”.