Keepable docs
Sender API

Forms

Publish reusable form templates that recipients fill in from their inbox, then read the responses back. Covers field types, template lifecycle, and collecting responses.

A form is a structured question set a recipient fills in from their inbox: an address change, a survey, a KYC update. You define a template once, deliver it, and read responses back as they come in. Forms are how you collect structured data from recipients, the inverse of sending content to them.

Templates and responses need forms.write to create and delete, forms.read to read.

Define a template

A template has a title, an optional description, and an ordered list of fields. Each field has a stable field_id (the key answers come back under), a label, a type, and whether it is required:

curl https://api.keepable.co/tenants/ten_01HXP/forms \
  -H "Authorization: Bearer $KEEPABLE_TOKEN" \
  -H "Keepable-Version: 2026-05-24" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Address change",
    "description": "Tell us where to reach you.",
    "fields": [
      { "field_id": "street", "label": "Street", "type": "text", "required": true },
      { "field_id": "state",  "label": "State",  "type": "choice", "required": true,
        "options": ["Lagos", "Abuja", "Rivers", "Kano"] }
    ]
  }'
const res = await fetch(
  "https://api.keepable.co/tenants/ten_01HXP/forms",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Keepable-Version": "2026-05-24",
      "Idempotency-Key": crypto.randomUUID(),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: "Address change",
      description: "Tell us where to reach you.",
      fields: [
        { field_id: "street", label: "Street", type: "text", required: true },
        { field_id: "state", label: "State", type: "choice", required: true,
          options: ["Lagos", "Abuja", "Rivers", "Kano"] },
      ],
    }),
  },
);

const { form_id } = await res.json();
201 Created
{
  "form_id": "frm_01HXP",
  "title": "Address change",
  "fields": [
    { "field_id": "street", "label": "Street", "type": "text", "required": true }
  ]
}

Field types

typeCollectsNotes
textA free-text string
numberA numeric value
dateA calendar date
ratingA rating valuePairs well with survey content.
choiceOne of a fixed setSupply the set in options.

Give field_id a stable, meaningful slug (street, not field_1): it is the key you read answers under, so a good id makes response handling self-documenting.

Deliver the form

Templates are delivered to recipients as content with the form content type. The recipient fills it in from their inbox, and you read the submitted responses back here.

Read responses

List the responses to a template (paginated). Each summary carries the response_id, when it was submitted_at, and which recipient_id submitted it:

curl "https://api.keepable.co/tenants/ten_01HXP/forms/frm_01HXP/responses?limit=50" \
  -H "Authorization: Bearer $KEEPABLE_TOKEN" \
  -H "Keepable-Version: 2026-05-24"
200 OK
{
  "responses": [
    { "response_id": "rsp_01HXP", "submitted_at": "2026-05-24T12:00:00Z", "recipient_id": "rcp_01HXP" }
  ],
  "next_token": null
}

Fetch a single response to get the answers, keyed by your field_ids:

curl https://api.keepable.co/tenants/ten_01HXP/forms/frm_01HXP/responses/rsp_01HXP \
  -H "Authorization: Bearer $KEEPABLE_TOKEN" \
  -H "Keepable-Version: 2026-05-24"
200 OK
{
  "response_id": "rsp_01HXP",
  "form_id": "frm_01HXP",
  "submitted_at": "2026-05-24T12:00:00Z",
  "recipient_id": "rcp_01HXP",
  "answers": { "street": "12 Marina Rd", "state": "Lagos" }
}

Manage templates and responses

OperationEndpoint
List templatesGET /tenants/{tenant_id}/forms
Fetch a templateGET /tenants/{tenant_id}/forms/{form_id}
Delete a templateDELETE /tenants/{tenant_id}/forms/{form_id}
List responsesGET /tenants/{tenant_id}/forms/{form_id}/responses
Fetch a responseGET /tenants/{tenant_id}/forms/{form_id}/responses/{response_id}
Delete a responseDELETE /tenants/{tenant_id}/forms/{form_id}/responses/{response_id}

Deleting a template does not retract forms already delivered to inboxes, and deleting a response is permanent: there is no trash for form data. Delete a response only when a data-retention or correction obligation requires it.