Plans
A reusable definition of a piece of work. Write the route, the questions, the conditions, and the retention once, then run it as often as you like.
A plan is the definition; a correspondence is one instance of it. If you send the same shape of thing repeatedly, a facility approval, a KYC refresh, a quarterly attestation, write it once as a plan.
POST https://api.keepable.co/sender/v2/plans
Authorization: Bearer {{KEEPABLE_TOKEN}}
Content-Type: application/json
{
"name": "Facility approval, over ₦25m",
"kind": "approval",
"parameters": [
{ "key": "amount", "label": "Facility amount", "type": "money", "required": true }
],
"route": [
{ "ref": "credit-review", "assignee": { "type": "member", "member_id": "mem_01J8Z" }, "act": "approve" },
{ "ref": "committee", "assignee": { "type": "group", "group_id": "grp_01J8Z" }, "act": "approve",
"quorum": { "of": 5, "need": 3 } },
{ "ref": "md", "assignee": { "type": "member", "member_id": "mem_01J9A" }, "act": "sign" }
],
"conditions": [
{ "when": { "parameter": "amount", "op": "gt", "value": 50000000 },
"then": { "action": "insert_step",
"step": { "ref": "board", "assignee": { "type": "group", "group_id": "grp_01J9B" }, "act": "approve" } } }
],
"retention_days": 90
}A condition reads as a sentence: when a parameter compares to a value,
then do one thing to the route. op is eq, ne, gt, gte, lt, lte,
or contains; action is insert_step (which carries the step to insert),
skip_step, or require_documents (both of which name a step_ref).
parameters is what the conditions read. Declare each one the plan branches on,
with a key, a label, and a type (text, number, date, money, or
choice); a run supplies the values.
Running one
POST https://api.keepable.co/sender/v2/plans/pln_01J8ZQ4T/runs
Authorization: Bearer {{KEEPABLE_TOKEN}}
Idempotency-Key: 5f3c1e02-9b7a-4a11-8c2e-6d0f4b8a1c93
Content-Type: application/json
{
"subject": "Facility FAC-2026-0118, ₦45m",
"document_ids": ["doc_01J8ZQ4T"],
"parameters": { "amount": 45000000 }
}A run returns a correspondence. From that point everything you know about correspondence applies unchanged: participants, minutes, documents accreting, and a covenant when it seals. A run is not a separate resource with its own lifecycle to learn.
A run's parameters are the values for the keys the plan declared, and they are
what its conditions evaluate against. That is how one definition covers "over
₦50m also needs the board" without a second plan.
Lifecycle
| Call | What it does |
|---|---|
GET /plans | List, filterable by status and kind. |
GET /plans/{id} | One plan. |
PATCH /plans/{id} | Change a draft or active plan. |
POST /plans/{id}/archive | Retire it. |
A plan is draft, active, or archived, and carries a run_count so you can
tell a definition somebody relies on from one nobody has used.
Archiving stops new runs. It does not touch runs already in flight, and it does not touch their sealed covenants: what was agreed under a plan stays exactly as it was agreed, which is the entire reason for sealing it.
Editing an active plan changes what the next run does, never a run already
under way. A file that changed its own rules midway through would not be
evidence of anything.
HTML and CSS
What a text/html mailpiece body may contain. The allowed tags, the inline-CSS property list, the kp-* branded blocks, and everything the sanitiser strips, so your branded mail renders the way you intend.
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.