Exports
Pull form answers, a consent register, or claim codes out as a CSV. Same three steps every time, and the same first two columns.
An export is a copy of what the API would show you, taken as a CSV. It inherits the file's visibility rule rather than routing around it with a signed URL.
POST https://api.keepable.co/sender/v2/correspondence/cor_01J8ZQ4T/exports
Authorization: Bearer {{KEEPABLE_TOKEN}}
Content-Type: application/json
{ "kind": "responses" }{ "export_id": "exp_01J8ZQ4T", "kind": "responses", "status": "queued", "created_at": "2026-07-14T09:12:00Z" }Poll GET /exports/{export_id} until status is ready; it runs queued →
running → ready, or failed. There is no webhook for this one: an export is
something you asked for and are waiting on, not something that happens to you.
A ready export carries a signed download_url with a download_expires_at.
It is minted per poll, so re-poll rather than storing it, and fetch it
server-side: it carries no identity, and this one is a spreadsheet of other
people's data.
Three kinds
kind | What it holds |
|---|---|
responses | Form answers, one row per participant, files as document ids. |
register | The consent register a DPO keeps: a projection over the sealed decisions. |
claims | Code, declared name, ref, state, expiry: the fulfillment file. |
All three lead with the same two columns, in the same order: the person, then your reference for them. Uniform because a reader who downloads two of these for the same audience will join them to each other, and because the reference is the only column that rejoins any of them to a contact you hold. A row with an empty reference is one you sent without one.
Files do not travel
An export never bundles bytes or hot-links them. A row carries three distinct things, and conflating them is the mistake this format is shaped to prevent:
- Identity: the filename.
- Verification: the receipt code. It proves a file somebody already holds, and fetches nothing, which is why it is safe in a spreadsheet.
- Retrieval: a link back into the portal, which resolves only in an authenticated session and then streams the bytes.
Scopes
Every export needs correspondence.read, and a responses or register one
needs responses.read on top: the kind of thing being copied decides what it
takes to copy it.
Sensitive answers
Masked by default. include_sensitive: true exports them in the clear, which
needs responses.sensitive, needs reader rights on the file, and is written to
the minute ledger.
An export is the bulk form of a reveal, so it is held to the same gate. Otherwise the cheaper route is the way around the gate and the gate is decoration. Collecting a sensitive export is minuted too, not only creating it: holding the strong scope to create and a weaker one to collect would have been the same hole one step later.
The masked default is usually what you want. Most downstream reconciliation needs the last four digits and nothing else.
Collecting responses
Publish questions, read the answers back, and handle the two things a Nigerian intake form gets wrong: holding data you should not have, and letting everyone in the organisation read it.
Work connections
Your organisation's identity provider and document store, surfaced as two vendor-neutral connections. Read live, never synced, and never mirrored.