Keepable docs
Foundations

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.co

Paths 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-24

The 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-6c5b4d3e2f1a

This 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:

ParameterMeaning
limitPage size, 1–100. Defaults to 50.
nextOpaque 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.