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.
The Sender API authenticates with OAuth2 client-credentials, a pure
server-to-server flow with no user in the loop. You exchange a client_id and
client_secret for a short-lived bearer token, then send that token on every
request.
Get a token
POST your credentials to the token endpoint with grant_type=client_credentials.
Request only the scopes the credential actually needs (see Scopes).
curl https://api.keepable.co/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$KEEPABLE_CLIENT_ID" \
-d "client_secret=$KEEPABLE_CLIENT_SECRET" \
-d "scope=content.read content.write"const res = await fetch("https://api.keepable.co/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.KEEPABLE_CLIENT_ID!,
client_secret: process.env.KEEPABLE_CLIENT_SECRET!,
scope: "content.read content.write",
}),
});
const { access_token, expires_in } = await res.json();form := url.Values{
"grant_type": {"client_credentials"},
"client_id": {os.Getenv("KEEPABLE_CLIENT_ID")},
"client_secret": {os.Getenv("KEEPABLE_CLIENT_SECRET")},
"scope": {"content.read content.write"},
}
resp, err := http.PostForm("https://api.keepable.co/oauth2/token", form)
if err != nil {
return err
}
defer resp.Body.Close()
var tok struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
json.NewDecoder(resp.Body).Decode(&tok)The response is a standard OAuth2 token document:
{
"access_token": "...",
"token_type": "Bearer",
"expires_in": 3600
}Use the token
Send it as a bearer token on every business request, alongside the
Keepable-Version header:
POST /tenants/ten_01HXP/contents HTTP/1.1
Host: api.keepable.co
Authorization: Bearer <access_token>
Keepable-Version: 2026-05-24
Idempotency-Key: 9f1c8e2a-7b3d-4f10-9a2e-6c5b4d3e2f1a
Content-Type: application/jsonTokens are short-lived. Cache the token until shortly before expires_in
elapses and refresh it then, rather than requesting a new token per call. A request
with a missing, malformed, or expired token returns
401 Unauthorized.
Confirm your wiring with whoami
GET /auth/whoami reflects the caller the auth middleware derived from your
token. It touches no business state, so it is the cheapest way to confirm an SDK
has its bearer wired correctly:
curl https://api.keepable.co/auth/whoami \
-H "Authorization: Bearer $KEEPABLE_TOKEN" \
-H "Keepable-Version: 2026-05-24"{ "kind": "sender", "id": "ten_01HXP", "scopes": ["tenant.read", "content.write"] }Scopes
A credential is granted a set of scopes, and the token it mints can only carry
scopes the credential holds. Request the least privilege each integration
needs: a delivery worker needs content.write, not tenant.write.
| Scope | Grants |
|---|---|
tenant.read | Read tenants. |
tenant.write | Create and modify tenants, company IDs, and branding. |
content.read | Recipient matching and content reads. |
content.write | Deliver content. |
agreement.read | Read agreements and download covenants. |
agreement.write | Create and revoke agreements. |
campaigns.read | Read campaigns and their metrics. |
campaigns.write | Create, author, and run campaigns. |
forms.read | Read form templates and responses. |
forms.write | Create and delete form templates and responses. |
access.write | Request and respond to access delegation. |
webhooks.write | Manage webhook endpoints and read deliveries. |
A call whose token lacks the required scope returns
403 Forbidden.
mTLS for tier-1 senders
Tier-1 senders (the central bank, any CBN-licensed bank, and federal agencies) must present a client certificate (mTLS) in addition to the OAuth2 bearer. The certificate is verified at the gateway and layered on top of the bearer token above; it does not replace it, and the tenant in the bearer must match the tenant the certificate was issued for. A tier-1 grade keeps sending disabled until the certificate is live. There is no weaker interim path. Partner Engineering provisions it from a Keepable-operated CA during onboarding.
Never embed a client_secret in a browser, mobile app, or any distributed
client. The client-credentials flow is for back-end services only.
Introduction
Keepable is a secure digital mailbox for Nigeria. Send mail, run e-signature agreements, and publish forms to recipients identified by NIN, email, or TIN, all over the Sender API.
Conventions
The headers, idempotency rules, and pagination model that are constant across every Keepable API. Learn them once.