Keepable docs
Recipient surface

Email verification

Capture a recipient email and prove control of it with a one-time code. PUT /recipient/account/email issues a code, POST /recipient/account/email/verify redeems it, both idempotent.

A recipient's email is what notifications route to and what initiates account recovery, so Keepable verifies control of it before trusting it. Verification is two steps: capture the address (which issues a one-time code), then redeem the code. Both steps are mutations and carry an Idempotency-Key.

Step 1: capture the email

PUT /recipient/account/email records the address and dispatches a six-digit one-time code to it.

curl -X PUT https://api.keepable.co/recipient/account/email \
  -H "Authorization: Bearer $RECIPIENT_TOKEN" \
  -H "Keepable-Version: 2026-05-24" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "email": "ada.new@example.ng" }'
const res = await fetch("https://api.keepable.co/recipient/account/email", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${token}`,
    "Keepable-Version": "2026-05-24",
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ email: "ada.new@example.ng" }),
});

const { challenge_id, expires_at } = await res.json();
202 Accepted
{
  "challenge_id": "evc_3f2a...",
  "expires_at": "2026-06-01T12:15:00Z"
}

The 202 means a code was issued, not that the email is verified yet. The code is valid until expires_at (a window of roughly 15 minutes). Requesting a new code supersedes any outstanding one, and requests are rate-limited per recipient, so a flood of requests returns 429 Too Many Requests.

In development (where no real email is sent) the response also carries a dev_code field with the six-digit code, so a developer can complete the flow without an inbox. dev_code is never present in production, where the code is delivered only by email.

Step 2: redeem the code

POST /recipient/account/email/verify redeems the code issued above. On success the email is linked as a hashed identifier and the profile's email_verified flips to true.

curl -X POST https://api.keepable.co/recipient/account/email/verify \
  -H "Authorization: Bearer $RECIPIENT_TOKEN" \
  -H "Keepable-Version: 2026-05-24" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "code": "048213" }'
const res = await fetch(
  "https://api.keepable.co/recipient/account/email/verify",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Keepable-Version": "2026-05-24",
      "Idempotency-Key": crypto.randomUUID(),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ code: "048213" }),
  },
);

if (res.status === 204) {
  // Email verified and linked.
}

A success is 204 No Content. There is no body: re-read GET /recipient/account to see email_verified: true and the linked email.

When verification fails

The code is checked in constant time, and a wrong code counts against an attempts cap. The failure modes map to distinct statuses so the apps can give the recipient the right next step:

StatusProblemWhat happenedWhat to do
400Bad requestThe submitted code is incorrect (but the challenge is still open).Let the recipient retry with the right code.
404Not foundThere is no pending verification to redeem.Send the recipient back to step 1 to request a code.
409ConflictThe email is already linked to another account. An email belongs to exactly one recipient.Prompt for a different address.
422UnprocessableThe code has expired.Request a fresh code (step 1).
429Too many requestsToo many failed attempts; the challenge is locked.Request a fresh code (step 1).

Re-verifying to change email

Verifying a new address replaces any prior email identifier: the same flow both sets an email for the first time and changes an existing one. The new address is not trusted until its code is redeemed, so a recipient's verified email never silently changes out from under them.

What a verified email unlocks

The moment an email is verified and linked it becomes a resolvable identifier. Any content a sender addressed to that email while the recipient was unreachable is released into their inbox, and they are notified. That held-pending to delivered release is covered in Delivery notifications.