Keepable
Sender API

Routing, minutes, and covenants

How a file moves desk to desk, what the hash-chained ledger records at each step, and what sealing produces at the end.

An approval correspondence carries an ordered route. The file starts at the first desk and moves when that desk acts.

The file is the workspace

A routed file is not a notification that points at a document somewhere else. It is the workspace: documents accrete to it as it moves, and the third desk attaches the memo the fourth desk needs.

POST https://api.keepable.co/sender/v2/correspondence/cor_01J8ZQ4T/documents
Authorization: Bearer {{KEEPABLE_TOKEN}}
Content-Type: application/json

{ "document_id": "doc_01J9ABCD", "title": "Due diligence", "note": "Vendor due-diligence pack" }

A desk acting

A desk is usually discharged by a person in the portal. It can also be discharged over the API, which is how a decision taken in your own system reaches the file:

POST https://api.keepable.co/sender/v2/correspondence/cor_01J8ZQ4T/act
Authorization: Bearer {{KEEPABLE_TOKEN}}
Content-Type: application/json

{ "act": "approve", "note": "Vendor pack is current; no outstanding findings." }

The step is not named, and that is deliberate: the act discharges whichever desk is currently open, which the server resolves. A client that could name a desk could sign for one the file has already moved past.

act must be the act the desk asks for: approve, sign, acknowledge, or contribute. reject is allowed anywhere approve is, and it must carry a note, because the next person to open a rejected file is whoever has to fix it, and "rejected" on its own tells them only that they are stuck.

The refusals are deliberately different answers, so your code can tell "wait" from "change the request": 403 not_at_this_desk, 409 already_acted, 409 not_your_turn (true later), 409 desk_blocked, 409 file_sealed, and 422 wrong_act (never true).

A quorum counts decisions filed, deduplicated by who filed them, rather than participant states: one person filing twice is one decision. When the quorum is met the desk settles, the next one opens, and when there is no next one the file seals.

Minutes

Every act appends a minute: who, what, when, at which step, with whatever they said about it.

GET /correspondence/cor_01J8ZQ4T/minutes
{
  "minutes": [
    { "seq": 3, "at": "2026-07-14T09:12:00Z",
      "actor": { "member_id": "mem_01J8Z", "display_name": "Grace Adeyemi" },
      "step_ref": "internal-control", "action": "approve",
      "detail": "Vendor due-diligence pack is current; no outstanding findings.",
      "entry_hash": "c4", "prev_hash": "c3" }
  ]
}

Each entry carries the hash of the one before it, so the ledger can be checked against itself. An entry cannot be edited, reordered, or quietly dropped without the chain saying so, which is what makes it worth reading later.

Only people appear as actors. A system event is recorded as a system event and never wears somebody's name.

Sealing

When the last desk acts, the file seals into a covenant: a tamper-evident record of exactly what was agreed, by whom, in what order.

GET /correspondence/cor_01J8ZQ4T/covenant

The covenant carries a document_id for the sealed rendering and a receipt code. Those two are not interchangeable, and the difference matters:

  • A receipt proves bytes somebody already holds. keepable.co/verify takes the receipt and the file, and hands nothing over. It is safe in a spreadsheet, in an email, on a printed page.
  • A retrieval handle hands bytes over. It happens signed in, through GET /documents/{document_id}, and only for somebody entitled to the file.

Which is why the verifier can be public with no account and no login: it is a checker, not a door.

Conditions

A plan's conditions evaluate against the parameters a run supplies, so one definition covers the variation instead of three near-identical ones. Reference a step by its ref, which is also the handle its minutes carry.

Reminders and stalls

POST /correspondence/{id}/participants/{participant_id}/remind

GET /metrics/workflows gives you the rollup: how many files are in_flight and how many are awaiting_decision. (GET /metrics/delivery is the other half, over mail rather than desks: totals, delivered, opened, and the two rates over a window.) A route stuck at one desk for a week is a fact about your organisation rather than about the API, and this is where you see it.

On this page