Skip to content

Verify the email-verification code

POST
/api/me/verify-email/
curl --request POST \
--url http://localhost:8000/api/me/verify-email/ \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "code": "428193" }'

Submits the 6-digit code from the verification email. On success, stamps email_verified_at on the admin and returns the new state. Idempotent — re-submitting after the email is already verified returns the existing verified_at (200), not an error.

Error envelope (typed detail so the UI can pick the right copy):

  • 400 no_pending_code — verify was called without first calling send.
  • 400 invalid_code — wrong code; the response carries attempts_remaining (counts down from 5).
  • 410 code_expired — the code’s 15-minute TTL elapsed; request a fresh one.
  • 429 too_many_attempts — 5 wrong codes burned the row. Honour the response and call send-verification again before retrying. This is currently the only endpoint in the API that returns a real 429; it is not a rate-limit on request frequency but a per-code attempt cap, so there is no Retry-After header — the recovery is to start a fresh verification round.

Request body for POST /api/me/verify-email/.

object
code
required

Six-digit numeric code from the verification email.

string
>= 1 characters /^\d{6}$/
Examples
ExampleSubmitTheCodeFromEmail

Submit the code from email

{
"code": "428193"
}
Media typeapplication/json

Response shape for POST /api/me/verify-email/.

object
verified_at
required

ISO-8601 UTC stamped on the admin when the code matched.

string format: date-time
Examples
ExampleVerified
{
"verified_at": "2026-05-28T15:03:21Z"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string
Examples

UI shows the typed error + the remaining attempt count

{
"detail": "invalid_code",
"attempts_remaining": 3
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string
Examplegenerated
{
"detail": "example"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string
Examplegenerated
{
"detail": "example"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string
Examples
Example410—CodeExpired(15-minTTL)

410 — Code expired (15-min TTL)

{
"detail": "code_expired"
}
Media typeapplication/json
object
detail
required

429 — rate limit exceeded. Honour the Retry-After response header (seconds to wait) before retrying; it is sent on every 429.

string
Examples
Example429—TooManyWrongAttempts

After 5 wrong codes the row is burned; send a fresh one

{
"detail": "too_many_attempts"
}