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
Query parameters:
All filters are AND-combined; CSV-valued ones expand to IN (?, ?, ...) predicates.
| Param | Type | Description |
|---|
crew_id | string | Filter by single crew ID. |
agent_id | string | Filter by single agent ID. |
mission_id | string | Filter by mission ID. |
trace_id | string | Narrow to one run’s spans (trace_id == run id). |
crew_ids | string | CSV multi-crew filter — takes precedence over crew_id when non-empty. |
agent_ids | string | CSV multi-agent filter — takes precedence over agent_id when non-empty. |
entry_type | string | CSV of entry types (e.g. peer.escalation,keeper.decision). |
exclude_entry_type | string | CSV of entry types to exclude (NOT IN). Useful for hiding container.metrics noise. AND-combines with entry_type. |
severity | string | CSV of info, notice, warn, error. |
actor_type | string | CSV of agent, user, system, keeper, sidecar, orchestrator. |
priority | string | CSV of normal, high, pin, permanent. Invalid values → 400. |
since | string | RFC3339 lower bound on ts. |
until | string | RFC3339 upper bound on ts. |
q | string | FTS5 search across summary + payload (migration 55). Bounded to 200 chars; phrase-wrapped before MATCH; AND-combined with structural filters. |
cursor | string | Opaque pagination token from a prior response. |
limit | integer | 1-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
}
| Field | Type | Description |
|---|
entries | array | Page of entries, newest first. |
entries[].id | string | j_<16-hex> stable identifier. |
entries[].ts | string | RFC3339Nano (milli precision on write). |
entries[].entry_type | string | See type catalog. |
entries[].severity | string | info, notice, warn, or error. |
entries[].actor_type | string | agent, user, system, keeper, sidecar, orchestrator. |
entries[].summary | string | Human-readable one-liner. |
entries[].payload | object? | Typed payload, shape varies by entry_type. Omitted when empty. |
entries[].refs | object? | Cross-entry links (parent_entry_id, approval_id, …). Omitted when empty. |
entries[].trace_id | string? | W3C trace ID if telemetry is enabled. |
next_cursor | string? | Pass back via ?cursor= for the next page. Absent on the last page. |
count | integer | Number of entries in this page. |
Errors:
| Status | Condition |
|---|
| 400 | Bad since / until / limit / cursor. |
| 401 | No workspace context. |
| 500 | DB 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:
| Status | Condition |
|---|
| 401 | No workspace context. |
| 400 | Bad query parameters. |
| 500 | Streaming not supported by writer (ResponseWriter doesn’t implement http.Flusher). |
Get single entry
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:
| Status | Condition |
|---|
| 401 | No workspace context. |
| 404 | Unknown 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
Errors:
| Status | Condition |
|---|
| 400 | Bad query parameter (since, until, priority, oversized q, …). |
| 401 | No workspace context. |
| 500 | DB 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" }
| Field | Type | Required | Description |
|---|
priority | string | yes | One of normal, high, pin, permanent. |
reason | string | no | Free-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:
| Status | Condition |
|---|
| 400 | Bad JSON body or unknown priority value. |
| 401 | No workspace context. |
| 403 | Caller is not OWNER/ADMIN on the workspace. |
| 404 | Entry not found, OR exists in another workspace. |
| 500 | DB 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.
| Field | Type | Description |
|---|
crews[].id | string | Stable crew ID. |
crews[].slug | string | Workspace-unique URL slug. |
crews[].icon | string? | Lucide icon name (server, code, rocket, …). May be null. |
crews[].color | string? | Palette ID (blue, emerald, violet, amber, rose, cyan, lime, fuchsia). May be null. |
agents[].crew_id | string? | Foreign key to a crews[] entry in this same payload. May be null for crew-less agents. |
agents[].avatar_seed | string? | Deterministic seed; the frontend renders an avatar from this. |
agents[].avatar_style | string? | Avatar style hint (pixel-art, lorelei, …). |
missions[].status | string | PENDING, 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”.