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.

Every endpoint requires authentication and is workspace-scoped via the session context. 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. See the Crew Journal guide for the data model and entry-type catalog.

List entries

GET /api/v1/journal
Query parameters: All filters are AND-combined; CSV-valued ones expand to IN (?, ?, ...) predicates.
ParamTypeDescription
crew_idstringFilter by single crew ID.
agent_idstringFilter by single agent ID.
mission_idstringFilter by mission ID.
trace_idstringNarrow to one run’s spans (trace_id == run id).
crew_idsstringCSV multi-crew filter — takes precedence over crew_id when non-empty.
agent_idsstringCSV multi-agent filter — takes precedence over agent_id when non-empty.
entry_typestringCSV of entry types (e.g. peer.escalation,keeper.decision).
exclude_entry_typestringCSV of entry types to exclude (NOT IN). Useful for hiding container.metrics noise. AND-combines with entry_type.
severitystringCSV of info, notice, warn, error.
actor_typestringCSV of agent, user, system, keeper, sidecar, orchestrator.
prioritystringCSV of normal, high, pin, permanent. Invalid values → 400.
sincestringRFC3339 lower bound on ts.
untilstringRFC3339 upper bound on ts.
qstringFTS5 search across summary + payload (migration 55). Bounded to 200 chars; phrase-wrapped before MATCH; AND-combined with structural filters.
cursorstringOpaque pagination token from a prior response.
limitinteger1-500, default 100.
Response: 200 OK
{
  "entries": [
    {
      "id": "j_a1b2c3d4e5f60718",
      "workspace_id": "ws_123",
      "crew_id": "crw_backend",
      "agent_id": "agt_viktor",
      "mission_id": "MIS-42",
      "ts": "2026-04-17T10:23:41.000Z",
      "entry_type": "peer.escalation",
      "severity": "warn",
      "actor_type": "agent",
      "actor_id": "agt_viktor",
      "summary": "escalating DB migration to eva",
      "payload": { "escalation_reason": "unsure about schema impact" },
      "trace_id": "0af7651916cd43dd8448eb211c80319c"
    }
  ],
  "next_cursor": "2026-04-17T10:23:41.000Z|j_a1b2c3d4e5f60718",
  "count": 1
}
FieldTypeDescription
entriesarrayPage of entries, newest first.
entries[].idstringj_<16-hex> stable identifier.
entries[].tsstringRFC3339Nano (milli precision on write).
entries[].entry_typestringSee type catalog.
entries[].severitystringinfo, notice, warn, or error.
entries[].actor_typestringagent, user, system, keeper, sidecar, orchestrator.
entries[].summarystringHuman-readable one-liner.
entries[].payloadobject?Typed payload, shape varies by entry_type. Omitted when empty.
entries[].refsobject?Cross-entry links (parent_entry_id, approval_id, …). Omitted when empty.
entries[].trace_idstring?W3C trace ID if telemetry is enabled.
next_cursorstring?Pass back via ?cursor= for the next page. Absent on the last page.
countintegerNumber of entries in this page.
Errors:
StatusCondition
400Bad since / until / limit / cursor.
401No workspace context.
500DB error.

Stream entries (SSE)

GET /api/v1/journal/stream
Server-Sent Events feed. Same query-param filters as List (limit is forced to 50 for the seed batch). Response headers:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
X-Accel-Buffering: no
Event frames:
id: j_a1b2c3d4e5f60718
event: entry
data: {"id":"j_a1b2c3d4e5f60718","ts":"...","entry_type":"peer.escalation", ...}

: heartbeat

id: j_b2c3d4e5f6071829
event: entry
data: {...}
  • 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:
StatusCondition
401No workspace context.
400Bad query parameters.
500Streaming not supported by writer (ResponseWriter doesn’t implement http.Flusher).

Get single entry

GET /api/v1/journal/{id}
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
{
  "id": "j_a1b2c3d4e5f60718",
  "workspace_id": "ws_123",
  "ts": "2026-04-17T10:23:41.000Z",
  "entry_type": "peer.escalation",
  "severity": "warn",
  "priority": "normal",
  "actor_type": "agent",
  "summary": "escalating DB migration to eva",
  "payload": {"escalation_reason": "unsure about schema impact"},
  "trace_id": "0af7651916cd43dd8448eb211c80319c"
}
Errors:
StatusCondition
401No workspace context.
404Unknown entry, OR an entry that exists but belongs to another workspace. The shape matches “not found” — existence is not leaked across tenants.

Count entries

GET /api/v1/journal/count
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
{ "total": 1283 }
Errors:
StatusCondition
400Bad query parameter (since, until, priority, oversized q, …).
401No workspace context.
500DB error.

Set entry priority

POST /api/v1/journal/{id}/priority
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:
{ "priority": "permanent", "reason": "FX compliance constraint" }
FieldTypeRequiredDescription
prioritystringyesOne of normal, high, pin, permanent.
reasonstringnoFree-text rationale recorded on the audit emit. Empty is allowed but discouraged — the audit log is the only place the reason lives.
Response: 200 OK
{
  "id": "j_a1b2c3d4e5f60718",
  "priority": "permanent",
  "previous": "normal",
  "reason": "FX compliance constraint"
}
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:
StatusCondition
400Bad JSON body or unknown priority value.
401No workspace context.
403Caller is not OWNER/ADMIN on the workspace.
404Entry not found, OR exists in another workspace.
500DB error.

Workspace lookup table

GET /api/v1/journal/lookup
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
{
  "crews": [
    {"id":"crw_abc","slug":"backend","name":"Backend","icon":"server","color":"emerald"}
  ],
  "agents": [
    {"id":"agt_xyz","slug":"viktor","name":"Viktor","crew_id":"crw_abc","avatar_seed":"viktor","avatar_style":"pixel-art"}
  ],
  "missions": [
    {"id":"MIS-42","title":"Migrate auth","status":"IN_PROGRESS"}
  ]
}
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.
FieldTypeDescription
crews[].idstringStable crew ID.
crews[].slugstringWorkspace-unique URL slug.
crews[].iconstring?Lucide icon name (server, code, rocket, …). May be null.
crews[].colorstring?Palette ID (blue, emerald, violet, amber, rose, cyan, lime, fuchsia). May be null.
agents[].crew_idstring?Foreign key to a crews[] entry in this same payload. May be null for crew-less agents.
agents[].avatar_seedstring?Deterministic seed; the frontend renders an avatar from this.
agents[].avatar_stylestring?Avatar style hint (pixel-art, lorelei, …).
missions[].statusstringPENDING, IN_PROGRESS, COMPLETED, CANCELLED.
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.

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”.