Conventions
The headers, idempotency rules, and pagination model that are constant across every Keepable API. Learn them once.
A handful of conventions hold across the Keepable Sender API. Learn them once here and the per-endpoint guides stay short.
Base URL
The Sender API is served from a single base URL:
https://api.keepable.coPaths in this documentation are written relative to it. There is no /v1/
path prefix: versioning is by header, not URL (below).
API version
Keepable is date-versioned. Send the Keepable-Version header (a contract
date in YYYY-MM-DD form) on every request:
Keepable-Version: 2026-05-24The server pins its behaviour to that contract date, so a partner is never force-cut to a new version: you adopt a newer contract date deliberately, after reading the changelog, not because we shipped. Omitting the header pins you to the latest version, which means your integration can shift under you, so always send it explicitly in production.
Idempotency
Every mutating operation (POST, PUT, DELETE that changes state)
requires an Idempotency-Key header, a client-generated string of 1–255
visible ASCII characters, unique per logical operation. A UUID is the natural
choice:
Idempotency-Key: 9f1c8e2a-7b3d-4f10-9a2e-6c5b4d3e2f1aThis makes retries safe. If a request times out and you do not know whether it landed, replay it with the same key:
- Same key, identical request fingerprint → you get the original result back, and the operation runs exactly once.
- Same key, different body → the API rejects the replay with
409 Conflict, protecting you from accidentally double-charging the key for a different payload.
Generate one key per logical action and persist it before you make the call, so a crash-and-retry reuses the same key. Generating a fresh key on every attempt defeats the protection: each attempt looks like a new operation.
Pagination
List endpoints return a page plus an opaque cursor. They take two query parameters:
| Parameter | Meaning |
|---|---|
limit | Page size, 1–100. Defaults to 50. |
next | Opaque cursor from the previous response's next_token. |
Every list response carries a next_token. When it is null, you have reached
the end. Walk a full collection by feeding each next_token back as next:
# First page
curl "https://api.keepable.co/tenants?limit=50" \
-H "Authorization: Bearer $KEEPABLE_TOKEN" \
-H "Keepable-Version: 2026-05-24"
# Next page: pass the previous next_token as `next`
curl "https://api.keepable.co/tenants?limit=50&next=eyJvZmZzZXQiOjUwfQ" \
-H "Authorization: Bearer $KEEPABLE_TOKEN" \
-H "Keepable-Version: 2026-05-24"async function* allTenants(token: string) {
let next: string | undefined;
do {
const url = new URL("https://api.keepable.co/tenants");
url.searchParams.set("limit", "100");
if (next) url.searchParams.set("next", next);
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
"Keepable-Version": "2026-05-24",
},
});
const page = await res.json();
yield* page.tenants;
next = page.next_token ?? undefined;
} while (next);
}Treat next_token as opaque: do not parse, construct, or store assumptions
about its contents. It is not a stable identifier and its format may change.
Response headers
Successful mutations echo the id of the resource they created as a
keepable--prefixed response header, in addition to returning it in the body,
for example keepable-content-id on a delivery, keepable-agreement-id on an
agreement, or keepable-tenant-key on a new tenant. The body is the canonical
source; the headers exist for clients that want the id without parsing the
payload.
Errors
Every non-2xx response is an RFC 7807 application/problem+json document. The
full catalogue (every problem type, what triggers it, and whether to
retry) is in Errors.
Authentication
The Sender API uses OAuth2 client-credentials. Exchange your client_id and client_secret for a short-lived bearer token, scoped to exactly the operations a credential needs.
Errors
Every non-2xx response is an RFC 7807 problem document. Here is the full catalogue of problem types, what triggers each, and whether to retry.