Generation needs a session; verification deliberately doesn't — see Backup Codes for why (it's a real account-recovery login path). Real values (count, format, hashing) are covered there too.
POST /api/v1/backup-codes/generate
Authorization: Bearer <threshold1_jwt>
Content-Type: application/json
{}
Real response, captured live — 10 real codes, shown exactly once:
{
"success": true,
"codes": [
"GZ8H2-Y2HPP", "T7C5R-8F6VA", "JPEK8-9P4EW", "ER7H5-KXQF7", "ZC9J6-4CTAM",
"Q28XP-SQ97R", "8G7WS-A7QF9", "BHWAW-S9KDX", "J7EEZ-CA55S", "8VCQ9-FW6RP"
]
}
Calling this again immediately invalidates this entire batch and returns a new one — there's no way to retrieve a previously-generated set again.
POST /api/v1/backup-codes/verify
Authorization: Bearer th_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{ "email": "user@example.com", "code": "GZ8H2-Y2HPP" }
Authenticated by API key, not a user JWT — the whole point is recovering access when the user has no session. { "externalUserId": "...", "code": "..." } works the same way if you're using the identity bridge instead of email.
Real response, captured live with a real generated code:
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiJ9...",
"userId": "274f4b7d-34cb-4770-941f-71f60681bd8b"
}
A used code, or one from an invalidated batch, returns invalid_code. See Error Reference for the full list.