Status Cards

A status card is a standing question about a company's work, and the living answer to it. You give the card one message in plain language — what to watch, and how the update should read — and Paperclip's Summarizer compiles that into structured company-search queries, then keeps writing a Markdown summary as the matching issues move.

The API mirrors that lifecycle. You create a card and Paperclip queues a compile task; you ask for refreshes and it queues update tasks; the summarizer agent writes the compiled query and the finished summary back through two dedicated endpoints that only it may call.

Status cards are company-scoped, and company access is enforced on every request. If you are new to the API, read the API Overview first for base URL, authentication, and error-code conventions — this page builds on those and won't repeat them.

Experimental — off by default. Status cards are gated behind an instance experimental setting. Until an operator turns on enableStatusCards, every endpoint on this page responds 404 Not Found with { "error": "Status cards are not enabled" }. Because the feature is still experimental, treat the request and response shapes below as subject to change between releases.

For the board itself — creating cards in the UI, reading the tiles, and what the update policy does — see Status Cards in the experimental section.


Enabling status cards

Every route here starts by asking the instance whether status cards are on. The server reads the instance's experimental settings, and if enableStatusCards is not true it stops immediately with:

{ "error": "Status cards are not enabled" }

returned as 404 Not Found. It is an instance-wide switch, not a per-company one, and it gates the API as well as the UI.

You also need the built-in Summarizer agent provisioned and ready for the company, unless the card names its own agent. When neither is available, requests that would start a run fail with 422 Unprocessable Entity, { "error": "Summarizer built-in agent is not configured" }, and a details object carrying code: "summarizer_not_configured" plus the built-in's current status. See Built-in Agents.


Who can call what

Three levels of access show up across these routes:

Level What it means
Company access The caller can see the company. Enough for the read routes.
tasks:assign The caller may assign work to agents. Required to create a card or to manage one, because both queue agent tasks. Board users without it get 403 Forbidden with the authorization explanation.
Summarizer only Only the card's designated agent — its agentId override, or the built-in Summarizer — may write, and only from the run of the generation task it was given.

Agents get one extra restriction on top: an agent may only manage status cards it authored. Managing someone else's returns 403 Forbidden with { "error": "Agents can only manage status cards they authored" }. Reading is unrestricted within the company.

Two hard limits apply to agent callers, from STATUS_CARD_AGENT_MAX_CARDS and STATUS_CARD_AGENT_MAX_INTEREST_PROMPT_LENGTH:

Limit Value Failure
Cards one agent may author in a company 20 422 Unprocessable Entity, { "error": "Agents can author at most 20 status cards" }. Deleting a card frees a slot.
interestPrompt length for agent callers 4,000 characters 422 Unprocessable Entity, { "error": "Agent-authored status card prompts cannot exceed 4000 characters" }.

Board callers keep the general 20,000-character limit from the request schema.

A card that is out of reach — another company's, or one that does not exist — is always reported the same way: 404 Not Found with { "error": "Status card not found" }.


The card object

Field Meaning
id, companyId Identity.
createdByUserId, createdByAgentId Who authored the card. Exactly one is set.
title The card's name. Auto-generated by the compiler unless pinned.
titlePinned When true, a recompile leaves title alone.
interestPrompt The one message that drives the card: what to watch and how the update should read.
queries The compiled company-search queries. Empty until the first compile lands.
queryVersion Increments on every compiled-query write.
queryCompiledAt, queryCompiledByAgentId When the query was last compiled, and by whom.
agentId Per-card summarizer override, or null for the built-in Summarizer.
refreshPolicy The update policy — see Refresh policy.
state compiling, active, error, paused_budget, or paused_hours.
pendingChangeCount, lastChangeAt Changes seen since the last update, and when they started piling up.
fingerprint, fingerprintAt The snapshot of watched issues that change detection diffs against.
mentionedIssueIds Issues referenced in the latest summary that joined the watched set.
documentId The document holding the summary text, or null before the first write.
lastUpdateRunKind full or incremental for the last content update, or null after a configuration change.
lastGeneratedAt, lastModel When the last summary was written, and the model the agent reported.
generatingIssueId The generation task currently in flight, or null.
failureReason Plain-language explanation when state is error, otherwise null.
nextEvalAt When the scheduler next evaluates this card. null in manual mode and while archived.
archivedAt, archivedByUserId, archivedByAgentId Archive bookkeeping.
createdAt, updatedAt Timestamps.

Read routes return cards hydrated with three extra fields the database does not store: summaryBody (the current Markdown), todayTokens and todayCostCents (this card's spend since midnight UTC), and watchedIssueCount (how many issues the compiled query matches right now — omitted if the count could not be computed).

Card states

State What it means
compiling Just created, or its prompt changed. The agent is compiling the query and writing the first summary.
active Working normally. Whether the board shows it as fresh, stale, or updating is derived from pendingChangeCount and generatingIssueId.
error The last generation task reached done, cancelled, or blocked without writing a summary. failureReason says which.
paused_budget The daily token cap was reached. Automatic updates are suspended; a manual refresh still runs.
paused_hours Outside the card's active hours. Changes batch into one update when the window opens.

Refresh policy

refreshPolicy is an object on both create and update:

Field Required Notes
mode no manual (default), interval, or reactive.
intervalMinutes for interval Positive integer. Omitting it in interval mode fails validation with "Required for interval mode".
debounceSeconds for reactive Positive integer. Omitting it in reactive mode fails validation with "Required for reactive mode".
maxUpdatesPerHour no Positive integer. Applies to reactive mode; defaults to 6 when unset.
triggers no Which changes count. See below.
activeHours no { start, end, timezone }, with start and end as HH:MM in 24-hour form and timezone a valid IANA identifier. Outside the window the card moves to paused_hours.
dailyTokenCap no Positive integer. When unset the engine applies a default of 100,000 tokens per UTC day before pausing to paused_budget.

triggers decides what counts as a change worth an update:

Trigger Default Fires on
statusTransitions true A watched issue's status changed.
membershipChanges true An issue started or stopped matching the query.
humanComments true A person commented on a watched issue.
assigneeChanges true A watched issue's assignee changed.
anyUpdate false Any change at all, including in-progress churn.

Active hours and the token cap only gate automatic work. A manual refresh or recompile request runs regardless.


List Status Cards

GET /api/companies/{companyId}/status-cards

Return the company's status cards, most recently updated first, each hydrated as described above. Any caller with access to the company may call it.

Query parameter Notes
archived true returns archived cards, false (the default) returns live ones.

Example

curl -sS \
  -H "Authorization: Bearer {token}" \
  "https://paperclip.example.com/api/companies/{companyId}/status-cards?archived=false"

Create a Status Card

POST /api/companies/{companyId}/status-cards

Create a card and immediately queue its compile task. Requires tasks:assign.

Request body:

Field Required Notes
interestPrompt yes The card's standing request, trimmed, 1–20,000 characters. Agent callers are capped at 4,000.
title no 1–300 characters. Omit it and the compiler names the card.
titlePinned no Defaults to false. true protects the title from recompiles.
agentId no A per-card summarizer override, or null for the built-in Summarizer. Must belong to this company — otherwise 422 Unprocessable Entity, { "error": "Summarizer agent must belong to this company" }.
refreshPolicy no Defaults to { "mode": "manual" } with the default triggers.

Status code. 201 Created, returning the card as it stands after the compile was queued — state: "compiling" with generatingIssueId set.

Creation and compilation are all-or-nothing: if the compile task cannot be created or the agent cannot be woken, the new card is deleted again and the underlying error is returned.

An agent caller must belong to the company it is creating in; if it does not, the response is 403 Forbidden with { "error": "Agent cannot author status cards for this company" }.

Example

curl -sS -X POST \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  "https://paperclip.example.com/api/companies/{companyId}/status-cards" \
  -d '{
    "interestPrompt": "Watch the launch project. Tell me what is blocked and the single next action.",
    "refreshPolicy": {
      "mode": "interval",
      "intervalMinutes": 30,
      "dailyTokenCap": 50000
    }
  }'

Get a Status Card

GET /api/status-cards/{id}

Return one hydrated card. Company access is enough.


Update a Status Card

PATCH /api/status-cards/{id}

Change a card's configuration, or archive and restore it. Requires tasks:assign, and agents may only patch cards they authored.

Request body — at least one field is required, otherwise the request fails validation with "At least one field is required":

Field Notes
interestPrompt 1–20,000 characters (4,000 for agents). Changing it queues a recompile and resets the card to compiling.
title 1–300 characters, or null to hand naming back to the compiler.
titlePinned Whether recompiles may rename the card.
agentId Summarizer override, or null. Must belong to this company.
refreshPolicy The full policy object; it replaces the stored one and reschedules the card.
archived true archives the card, false restores it.

Two of these have side effects worth planning for:

  • Editing interestPrompt queues a compile task and clears lastUpdateRunKind, so the next content update is a full rebuild rather than a patch. The response is the card returned by that compile.
  • Archiving (archived: true) clears nextEvalAt and generatingIssueId and cancels any in-flight generation task. Restoring (archived: false) queues a full refresh with trigger restore, provided the card has compiled queries and nothing is already running.

Changing the agent also clears lastUpdateRunKind, for the same reason: a new writer should not patch text it did not write.


Delete a Status Card

DELETE /api/status-cards/{id}

Permanently delete the card and its update history. Requires tasks:assign, and agents may only delete cards they authored. Responds 204 No Content with an empty body.


Update History

GET /api/status-cards/{id}/updates

Return the card's update ledger, newest first. Company access is enough.

Each entry carries:

Field Meaning
kind compile, full, or incremental.
trigger manual, interval, reactive, or restore.
status running, ok, or failed.
generationIssueId, runId The task and agent run behind the update.
changes The issue changes this update integrated, each with issueId, identifier, from, to, and changeKind.
inputTokens, outputTokens, costCents, model What the update cost, attributed from the run's cost events.
queryVersion The compiled-query version the update ran against.
changeSummary The one-line note the agent wrote about this update.
startedAt, finishedAt Timing.
error Set when status is failed.

changeKind is one of new, removed, status, assignee, human_comment, or updated.


Summary Revisions

GET /api/status-cards/{id}/summary-revisions

Return the card's past summaries, newest first, each with id, revisionNumber, title, body, changeSummary, and createdAt. A card that has never had a summary written returns an empty array. Company access is enough.


Recompile

POST /api/status-cards/{id}/recompile

Rebuild the card's compiled query from its current interestPrompt, then write a fresh full summary. Requires tasks:assign, and agents may only recompile cards they authored.

There is no request body.

Response.

Field Meaning
card The card, now compiling, with generatingIssueId set.
generatingIssue The compile task that was created or joined.
alreadyGenerating true when a compile for this exact prompt was already genuinely in flight and this request joined it.

Status codes. A newly queued compile responds 202 Accepted. Joining a compile that was already running responds 200 OK — so pressing the button twice does not queue two runs. A task that is blocked does not count as in flight: it is stuck waiting on a human, so a re-kick reopens it and counts as a fresh run.

Archived cards cannot be compiled: 422 Unprocessable Entity, { "error": "Archived status cards cannot be compiled" }.


Refresh

POST /api/status-cards/{id}/refresh

Ask the card to update now. Requires tasks:assign, and agents may only refresh cards they authored.

Request body:

Field Required Notes
full no Defaults to false. true forces a full rebuild instead of letting Paperclip choose.

A manual refresh always runs — it bypasses the card's active-hours window and daily token cap, and it runs even when change detection found nothing new.

Response.

Field Meaning
card The card after the request.
generatingIssue The update task, or null when nothing was queued.
alreadyGenerating true when an update was already in flight and this request joined it.
enqueued true when this request actually queued work.
kind full or incremental, present when work was queued.
changes The changes the update will integrate, present when work was queued.

Status codes. 202 Accepted when enqueued is true and alreadyGenerating is false — that is, this request started the run. Everything else is 200 OK: joining a run already in flight, or a policy evaluation that decided to wait, pause, or do nothing.

Two refusals to plan for:

  • A card whose query has not compiled yet: 409 Conflict, { "error": "Compile the status-card query before refreshing it" }.
  • An archived card: 422 Unprocessable Entity, { "error": "Archived status cards cannot be refreshed" }.

Example

curl -sS -X POST \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  "https://paperclip.example.com/api/status-cards/{id}/refresh" \
  -d '{ "full": true }'

Dry Run

GET /api/status-cards/{id}/dry-run

Execute the card's compiled queries right now and return what they match, without writing anything or spending model tokens. This is what the board's Watched issues tab reads.

Unlike the other read routes, this one asks for an explicit company_scope:read decision — a caller confined to a narrower boundary gets 403 Forbidden with { "error": "Status-card dry-run is outside this actor's low-trust authorization boundary" }.

Response.

Field Meaning
cardId The card.
queryVersion The compiled-query version that was executed.
queries One entry per compiled query, each with the query itself and its result. Queries are a union, so the same issue can appear under more than one.
mentionedIssues Issue summaries for the issues the latest summary referenced, which are watched alongside the query matches.

Write the Compiled Query

PUT /api/status-cards/{id}/query

This is the first of the two endpoints the summarizer agent calls during a compile. It is documented so you can understand what happens to the card — not because you will normally call it yourself.

Request body:

Field Required Notes
queries yes 1–10 company-search queries. They are evaluated as a union.
title yes 1–300 characters. Ignored when the card's titlePinned is true.
changeSummary yes 1–2,000 characters, non-empty.
generationIssueId yes The compile task this write belongs to.

A successful write stores the queries, increments queryVersion, stamps queryCompiledAt and queryCompiledByAgentId, keeps the card in compiling, and records a compile entry in the update ledger — plus a running entry for the full summary that is expected to follow in the same run.


Write the Summary

PUT /api/status-cards/{id}/summary

The endpoint the summarizer agent calls to land a finished summary, whether from a compile or from an update run.

Request body:

Field Required Notes
markdown yes The summary body, 1–200,000 characters. Stored as a Markdown document.
title no 1–300 characters.
changeSummary yes 1–2,000 characters, non-empty. Attached to the new revision.
generationIssueId yes The generation task this write belongs to.
model no The model the agent actually used, up to 200 characters, or null. Stored on the card as lastModel.

The rules both writes have to satisfy

Every one of these failures returns 403 Forbidden:

  • The caller must be an agent, and must be either the card's agentId override or an agent carrying the summarizer built-in marker for this company — otherwise { "error": "Only the card's summarizer agent may write status cards" }. Both stay eligible, so a task created before an agent switch can still land its result.
  • The write must name the task the card is currently waiting on — { "error": "Status-card write does not match the active generation task" }.
  • That task must be assigned to the calling agent — { "error": "Generation task is not assigned to this agent" }.
  • It must still be live — { "error": "Generation task is no longer active" }.
  • Its payload must target this card and company — { "error": "Generation task does not target this status card" }.
  • The request must come from that task's run — { "error": "Status-card write must run from the linked generation task" }.

Conflicts return 409 Conflict: { "error": "Status-card compilation was superseded by a newer task" } on the query write, and { "error": "Status-card generation was superseded by a newer task" } on the summary write.

Archived cards refuse both: 422 Unprocessable Entity, with { "error": "Archived status cards cannot accept generation writes" } for the query and { "error": "Archived status cards cannot accept summaries" } for the summary. A summary write also needs a compiled query first — 409 Conflict, { "error": "Compile the status-card query before writing its summary" }.

What a successful write does

The summary is appended as a new document revision rather than replacing the old text. The card then settles: state returns to active, generatingIssueId and failureReason are cleared, lastGeneratedAt / lastModel / lastUpdateRunKind are stamped, pending-change counters reset, and nextEvalAt is rescheduled from the refresh policy.

Two things happen to the watched set at the same time. Issues the Markdown references — by identifier such as ABC-123, or by an /issues/{uuid} link — are resolved against the card's company and stored in mentionedIssueIds, up to 200, so their later changes trigger updates too. Unknown identifiers and other companies' issues are dropped. And the fingerprint keeps the generation-time baseline for everything else, so an issue that changed while the summary was being written still shows up in the next diff instead of being silently absorbed.

The response contains the updated card, the document, and the new revision. The matching entry in the update ledger is closed out with status: "ok", the run's token and cost totals, and the model.

Response.

Field Meaning
card The settled card.
document The summary document, including latestBody and latestRevisionNumber.
revision The revision that was just appended.

Activity logging

Every mutating route records an entry on the company activity log against entityType: "status_card":

Action Recorded when
status_card.created A card is created. Details include its initial state.
status_card.updated A card is patched. Details include the changed fields and whether it is now archived.
status_card.deleted A card is deleted.
status_card.recompile_requested A compile is requested. Details include generatingIssueId and alreadyGenerating.
status_card.refresh_requested A refresh is requested. Details include full, generatingIssueId, alreadyGenerating, and enqueued.
status_card.query_written The agent writes a compiled query. Details include queryVersion, generationIssueId, and changeSummary.
status_card.summary_written The agent writes a summary. Details include queryVersion, generationIssueId, documentId, and changeSummary.

See the Activity page for how to read the log.


Where to go next

  • Status Cards — the board itself, the update policy explained, and how to turn the feature on.
  • Status Card Query skill — the bundled skill that teaches an agent to author cards and compile prompts into queries.
  • Built-in Agents — how to provision the Summarizer, and what its lifecycle states mean.
  • Summary Slots — the other Summarizer-written surface, fixed to a project or workspace header.
  • Issues — compile and update tasks are ordinary issues.
  • API Overview — base URL, authentication, company scoping, and the shared error-code table.