Keepable
Sender API

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

CallWhat it does
GET /plansList, filterable by status and kind.
GET /plans/{id}One plan.
PATCH /plans/{id}Change a draft or active plan.
POST /plans/{id}/archiveRetire 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.

On this page