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:
https://api.keepable.co/sender/v2Paths in this documentation are written relative to it.
API version
The v2 in that URL is the version. There is nothing else to set: no header, no
date to keep in step with a release, nothing to get wrong.
Inside a major version the API only ever grows. We add operations, optional request fields and response fields. We do not remove or rename anything, make an optional field required, narrow a type, tighten a limit, or change the status code of an outcome you already handle. So an integration that works today keeps working, and you upgrade when you want something new, not because we shipped.
When a change cannot be made that way, /v3 opens next to /v2 and both serve.
You move by changing one segment in a URL, at a time you choose, and /v2 keeps
running for twelve months after /v3 is announced. Watch the
changelog for that.
One thing we ask in return: handle values you have not seen before. New kinds of correspondence, new states and new document types arrive inside a version, so a client that treats an unfamiliar enum value as a fatal error will break on something we added for somebody else. Keep unknown values rather than rejecting them.
Idempotency
Most mutating operations accept an Idempotency-Key header, a
client-generated string of 1-255 visible ASCII characters, unique per logical
operation. Send one on any create you want to be safely retryable (content
delivery requires it); 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, and take two query parameters:
| Parameter | Meaning |
|---|---|
limit | Page size, 1-200. Defaults to 50. |
cursor | Opaque cursor from the previous response's next_cursor. |
Every list response carries a next_cursor. When it is null, you have reached
the end.
# First page
GET https://api.keepable.co/sender/v2/correspondence?limit=50
Authorization: Bearer {{KEEPABLE_TOKEN}}
###
# Next page: pass the previous next_cursor as `cursor`
GET https://api.keepable.co/sender/v2/correspondence?limit=50&cursor=eyJvZmZzZXQiOjUwfQ
Authorization: Bearer {{KEEPABLE_TOKEN}}Draining the whole list is the same two calls in a loop:
async function* allCorrespondence(token: string) {
let cursor: string | undefined;
do {
const url = new URL("https://api.keepable.co/sender/v2/correspondence");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
const page = await res.json();
yield* page.correspondence;
cursor = page.next_cursor ?? undefined;
} while (cursor);
}A cursor is opaque and short-lived. Do not parse one, store one, or build a permalink from one: it encodes a position in a result set that is still moving.
Rate limits
Responses carry RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset.
Read them rather than discovering the ceiling by hitting it, and back off on
429 using RateLimit-Reset
rather than a fixed sleep.
POST /recipient-checks is throttled per identifier on top of the ordinary
limits, which is what keeps verification costs sane. It is a check, not an
enumeration tool. See Recipients.
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
One organisation-scoped API key, sent as a bearer token. Its prefix chooses the data plane, and its scopes decide which operations it may call.
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.