Skip to content
FiveCord Docs

Multi-factor authentication

Multi-factor authentication asks for a second proof of identity after the account password. FiveCord accepts a TOTP authenticator app and WebAuthn credentials such as a passkey. A one-use backup code works instead of a TOTP code, and sudo mode reuses these factors to protect every security-sensitive operation in the API.

Registering a WebAuthn credential does not make passkeys a second factor. A registered credential answers a sudo mode challenge and a passwordless login on its own, and only Set WebAuthn two-factor authentication adds the WebAuthn authenticator type that makes a passkey a required second factor at password login.

Every route here requires a non-bot user session. FiveCord rejects an account in suspicious activity state with 403 ACCOUNT_SUSPICIOUS_ACTIVITY, including on the sudo routes.

FiveCord validates a TOTP code as a six-digit HMAC-SHA-1 one-time password over a 30-second time step counted from the Unix epoch. It accepts one time step of clock skew on either side. A submitted code is exactly six decimal digits, and FiveCord rejects any other value. The shared secret is Base32, and FiveCord ignores whitespace, hyphens, and trailing padding when it decodes one.

The client generates the secret, presents it to the user, and submits it with a code derived from it when calling Enable TOTP MFA. FiveCord never generates or returns a TOTP secret.

Regenerating backup codes deletes the stored set and then issues exactly 10 replacements, so any code the user had already saved stops working.

Enabling TOTP issues 10 codes without deleting anything first. An account that already generated a set through List MFA backup codes while holding no TOTP secret keeps those codes alongside the 10 the enable response returns. Every one stays redeemable. Disabling TOTP deletes every backup code only when the account is left with no second factor, so an account that keeps passkeys as a second factor keeps its whole set.

List MFA backup codes reads the current set back at any time and reports which codes are already consumed. Consuming a code is irreversible. An account that cannot prove sudo mode reads the same set through the backup codes challenge.

A backup code has only these entry points: the mfa_code field of the sudo verification object with mfa_method set to totp, the code field of Disable TOTP MFA, and the code field of complete login with TOTP. Every other code field rejects it. All accept a current authenticator code or an unconsumed backup code. Enable TOTP MFA validates only against the secret being enrolled.

Only the code field of Disable TOTP MFA requires the account to hold a TOTP secret, so an account whose only second factor is passkeys never reaches that one. The other two accept an unconsumed backup code from such an account: the sudo verification object reads mfa_code as a backup code whenever the account holds no TOTP secret, and complete login with TOTP reads code the same way. Together they are the recovery path when no passkey is at hand. Any account reads and replaces its set through List MFA backup codes with regenerate set to true, whether or not it holds a TOTP secret, because that route needs sudo mode alone.

Set WebAuthn two-factor authentication mints 10 codes when it turns the toggle on for an account holding none, so an account whose only second factor is passkeys always has a set to fall back on.

An account that lost its saved backup codes reads the set back through an emailed challenge. Start MFA backup codes challenge opens a ticket and emails a code. Verify MFA backup codes challenge code exchanges that code for the current set and a proof. Regenerate MFA backup codes then presents the ticket and the proof to replace the set. Resend MFA backup codes challenge code sends a fresh code any time before verification.

Sudo mode applies to none of the four, so a user who no longer has the authenticator app still reaches the codes through the email address. The account needs a verified email address and TOTP enabled. Only Start MFA backup codes challenge checks the address, and Regenerate MFA backup codes checks TOTP a second time.

A ticket is a version 4 UUID valid for 30 minutes after the challenge starts, the code is resent, or the code is verified. Regeneration does not extend it. An expired, unknown, or another account’s ticket fails with INVALID_OR_EXPIRED_TICKET.

A verification code is eight characters drawn from the uppercase Latin alphabet and the decimal digits, written as two four-character groups separated by a hyphen. A code is valid for 10 minutes from the moment it is sent, and each send replaces the code the previous send issued.

Verification consumes the code and issues a proof as a version 4 UUID. An incorrect proof fails with INVALID_PROOF_TOKEN. A ticket with no proof fails with INVALID_OR_EXPIRED_TICKET on verification_proof.

Every ticket, code, and proof failure named here arrives as HTTP 400 whose top-level code is INVALID_FORM_BODY. The named value is the code of one validation error entry, and that entry’s path is the field it belongs to.

Each operation passes through its own route bucket and, separately, through one of the controls below. Exhausting either produces HTTP 429 even when the other has room.

ControlAllowanceOperation
Challenge start3 sends per 15 minutes for each accountStart MFA backup codes challenge
Challenge resend3 sends per 15 minutes for each accountResend MFA backup codes challenge code
Code verification5 attempts per 15 minutes for each ticketVerify MFA backup codes challenge code
Regeneration5 attempts per 15 minutes for each ticketRegenerate MFA backup codes

FiveCord refuses a resend for 30 seconds after the previous send on the same ticket. That refusal is HTTP 429 with a Retry-After computed from the moment the next send becomes available. It is independent of the route bucket and of the 15-minute send controls.

Sudo mode is a short-lived proof that the human in front of the session is still the account holder. An operation that requires it accepts a valid X-FiveCord-Sudo-Mode-JWT request header or the sudo verification object fields inside the JSON body.

A sudo token lasts five minutes and works only for the account that obtained it, across that account’s sessions. Treat it as opaque. A new token is issued only after an MFA proof from an account holding a TOTP secret or a registered WebAuthn credential.

The accepted proof depends on what the account can present, which is a stored TOTP secret or a registered WebAuthn credential. The authenticator types the account advertises do not enter into it, so a passkey proves sudo mode whether or not passkeys are enabled as a second factor.

Account stateAccepted proof
Neither a TOTP secret nor a registered WebAuthn credentialpassword
A TOTP secret or a registered WebAuthn credentialmfa_method with its matching fields, because password is no longer accepted
A registered WebAuthn credential and no TOTP secretThe same, and mfa_method of totp then reads mfa_code as an unconsumed backup code
Neither of those and no password credentialNothing, and eligible sudo operations pass

The totp method reads mfa_code as a current authenticator code or an unconsumed backup code. An account holding no TOTP secret is left with the backup code alone, which is how an account whose only second factor is passkeys proves sudo mode with no passkey at hand. The webauthn method reads webauthn_response and webauthn_challenge together and requires a registered credential.

A request that has no accepted proof fails with 403 SUDO_MODE_REQUIRED. Its body has the sudo mode methods object members, so a client can tell which proof to ask the user for. A proof that is present but wrong fails instead with 400 INVALID_FORM_BODY and a validation error entry. A mismatched password produces path password with code INVALID_PASSWORD. A rejected TOTP code, backup code, or WebAuthn assertion produces path mfa_code with code INVALID_MFA_CODE, so a client cannot tell which of the three was rejected.

The totp method also consumes a per-account allowance of 10 multi-factor attempts in 15 minutes, shared by every sudo-gated operation on every resource. FiveCord charges the allowance before it checks the code, so a wrong code and a correct code both draw on it. A correct code resets the counter to zero. While it is exhausted, a correct code returns the same INVALID_MFA_CODE entry as a wrong one. The webauthn method draws on no allowance. The login MFA allowances on HTTP authentication are counted separately.

FiveCord returns an issued or echoed token in the X-FiveCord-Sudo-Mode-JWT response header. A client keeps it and sends it in the same header on every later sudo-gated operation.

In a SUDO_MODE_REQUIRED error response, has_mfa and methods are at the top level of the error response object.

FieldTypeDescription
has_mfabooleanWhether the account can satisfy a sudo mode challenge
methodssudo mode method availability objectAuthenticators the account can present
FieldTypeDescription
totp1booleanWhether the account has a TOTP secret and the TOTP authenticator type
webauthnbooleanWhether the account has at least one registered WebAuthn credential
backup_codes2booleanWhether the account holds at least one unconsumed backup code

1 Both conditions are required, so an account holding a stored secret without the authenticator type reports false

2 There is no backup_codes method. The value tells a client to offer the code input, which a backup code reaches under mfa_method of totp like any other code, and to label that input as a backup code when totp is false. It reports the stored codes alone, so a client reads it only while has_mfa is true, because an account that cannot answer a sudo challenge proves sudo mode with its password however many codes it holds

Operations that require sudo mode merge these fields into their own JSON body. Every field is optional at the boundary, because the accepted combination depends on the account state described in sudo mode.

FieldTypeDescription
password?1stringAccount password (8-256 characters)
mfa_method?2stringMFA method, either totp or webauthn
mfa_code?3stringAuthenticator code or unconsumed backup code (1-32 characters)
webauthn_response?4WebAuthn assertion objectAssertion produced for the supplied challenge
webauthn_challenge?4stringChallenge returned by create sudo WebAuthn authentication options (1-256 characters)

1 Considered only while the account holds no TOTP secret and no registered WebAuthn credential, and ignored once either exists

2 Required when the account holds a TOTP secret or a registered WebAuthn credential, unless a valid sudo token is already present

3 Required when mfa_method is totp, with a current authenticator code or an unconsumed backup code. FiveCord reads it as a backup code alone while the account holds no TOTP secret

4 Both fields are required together when mfa_method is webauthn

FieldTypeDescription
backup_codesarray[MFA backup code object]The account’s current backup codes
{
"backup_codes": [
{"code": "a3f2-9kd7", "consumed": false},
{"code": "b81c-4nq0", "consumed": true}
]
}

A backup code is two four-character groups separated by a hyphen. Each group is drawn from the lowercase Latin alphabet and the decimal digits.

FieldTypeDescription
codestringOne-use backup code
consumedbooleanWhether the code has already been consumed

The state of a newly created backup codes challenge ticket.

FieldTypeDescription
ticketstringThe identifier every later step of this flow sends
code_expires_atISO8601 timestampThe moment the emailed code expires, 10 minutes after it was sent
resend_available_atISO8601 timestampThe earliest moment the code can be resent, 30 seconds after the last send

The backup codes and the proof a verified challenge returns.

FieldTypeDescription
backup_codesarray[MFA backup code object]The account’s current backup codes
verification_proof1stringThe proof Regenerate MFA backup codes consumes

1 A resend clears the stored proof and the next verification issues a different value

An account holds at most 10 WebAuthn credentials, not counting replaced passkeys. Both Create WebAuthn registration options and Register WebAuthn credential enforce this limit.

FiveCord verifies every assertion against the domain its challenge was issued for, chosen by passkey domain selection, and against the allowed origins configured for the instance. A passkey bridge assertion and a passkey update registration accept only the origin the ceremony runs on. FiveCord rejects an assertion whose reported signature counter is not greater than the stored counter. The exception is a stored and a reported counter of zero, which is how an authenticator without a counter appears.

FieldTypeDescription
idstringBase64url credential ID
namestringUser-assigned credential name (1-100 characters)
created_atISO8601 timestampThe time Register WebAuthn credential stored the credential
last_used_at1?ISO8601 timestampMost recent successful authentication
rp_id2stringThe domain the passkey was created for

1 Null until the credential completes a login, MFA, or sudo assertion for the first time

2 The instance’s FLUXER_PASSKEY_RP_ID for every passkey except one created on a new origin of the official instance, which reports fluxer.com. See passkey domain selection

FiveCord never returns the credential’s public key, signature counter, or reported transports.

A passkey works only under the domain it was created for, its relying party identifier. Most instances have one, FLUXER_PASSKEY_RP_ID, and every passkey uses it. The official instance has two while its web client moves from web.fluxer.app to fluxer.com, as the passkey bridge describes. There, a request whose Origin is https://fluxer.com or https://canary.fluxer.com is a new-origin request, and the domain follows the table below. A request with any other Origin, or with none as from the mobile apps, keeps fluxer.app wherever the account can use it.

OperationNew-origin requestAny other request
Create WebAuthn registration optionsA new fluxer.com passkeyA new fluxer.app passkey
Get discoverable WebAuthn optionsAny fluxer.com passkeyAny fluxer.app passkey, replaced ones included
Get WebAuthn MFA optionsThe fluxer.com passkeys, else the fluxer.app onesThe fluxer.app passkeys with replaced ones, else the fluxer.com ones
Create sudo WebAuthn authentication optionsThe fluxer.com passkeys, else the fluxer.app onesThe fluxer.app passkeys with replaced ones, else the fluxer.com ones

The options name the chosen domain in rpId and list only the chosen passkeys. An assertion from any other credential returns 401 PASSKEY_AUTHENTICATION_FAILED. A page on a new origin cannot run options for fluxer.app itself, so the official web client runs them through the passkey bridge.

A passkey update keeps the old fluxer.app credential as a replaced passkey, so the mobile apps and web.fluxer.app still accept it. A replaced passkey appears in no list, no Ready payload, and no WebAuthn Credentials Update, and it does not count toward the 10-credential limit.

A replaced passkey works only on requests that are not new-origin requests. It never works on a new origin or through the passkey bridge. Renaming or deleting it by ID returns 404 UNKNOWN_WEBAUTHN_CREDENTIAL. Deleting the passkey that replaced it deletes it too, and deleting the account’s last listed passkey deletes every replaced one.

A passkey update swaps one fluxer.app passkey for a fluxer.com passkey with the same name. FiveCord opens one only when a passkey bridge redemption completes from a new origin while the instance-wide domain migration switch is on. Nothing else opens one.

An open update belongs to one session and names the passkey the ceremony used. It lasts five minutes, and a later redemption on the same session replaces it.

The open update stands in for sudo mode on the update routes, because it exists only after this session used the old passkey within the last five minutes. It allows one action. Complete passkey update registers the new passkey. When the authenticator already holds a fluxer.com passkey for the account, registration fails on excludeCredentials and the old passkey stays listed, so the person can remove it themselves.

FieldTypeDescription
pending1?pending passkey update objectThe passkey this session can update

1 Null when the session has no open update, and when the passkey it names was deleted or replaced since

FieldTypeDescription
credential_idstringBase64url ID of the passkey to update
namestringThe passkey’s name, which the new passkey takes
cross_devicebooleanWhether the ceremony used a phone or security key rather than the device itself

The result of one redeemed sudo passkey bridge ceremony.

FieldTypeDescription
statusstringThe ceremony state, either completed or cancelled
sudo_token?1stringA sudo mode token, sent later in the X-FiveCord-Sudo-Mode-JWT request header

1 Present only when the status is completed

FieldTypeDescription
idstringBase64url credential ID
typestringCredential type, always public-key
transports?1array[string]Authenticator transports from the WebAuthn authenticator transport values

1 Present only when the authenticator reported its transports during registration

ValueDescription
bleBluetooth Low Energy
cableCloud-assisted Bluetooth Low Energy
hybridHybrid transport
internalPlatform authenticator
nfcNear-field communication
smart-cardSmart card
usbUSB authenticator

The browser WebAuthn PublicKeyCredential serialisation. Its field names are camelCase, because FiveCord accepts the exact structure the WebAuthn client API produces.

FieldTypeDescription
idstringBase64url credential ID
rawIdstringBase64url raw credential ID
responseWebAuthn assertion response objectAuthenticator assertion response
authenticatorAttachment?stringAuthenticator attachment, either cross-platform or platform
clientExtensionResultsWebAuthn client extension results objectClient extension outputs
typestringCredential type, always public-key

Malformed fields return INVALID_FORM_BODY. Each operation documents its cryptographic verification errors.

FieldTypeDescription
clientDataJSONstringBase64url client data JSON
authenticatorDatastringBase64url authenticator data
signaturestringBase64url assertion signature
userHandle?1stringBase64url user handle

1 Present only when the assertion came from a discoverable credential

FieldTypeDescription
idstringBase64url credential ID
rawIdstringBase64url raw credential ID
responseWebAuthn attestation response objectAuthenticator attestation response
authenticatorAttachment?stringAuthenticator attachment, either cross-platform or platform
clientExtensionResultsWebAuthn client extension results objectClient extension outputs
typestringCredential type, always public-key

Malformed fields return INVALID_FORM_BODY. An attestation that fails verification returns INVALID_WEBAUTHN_CREDENTIAL.

FieldTypeDescription
clientDataJSONstringBase64url client data JSON
attestationObjectstringBase64url attestation object
authenticatorData?stringBase64url authenticator data
transports?1array[string]Authenticator transports from the WebAuthn authenticator transport values
publicKeyAlgorithm?integerCOSE public key algorithm identifier
publicKey?stringBase64url credential public key

1 Retained with the credential and returned later in allowCredentials and excludeCredentials

FieldTypeDescription
appid?booleanWhether the AppID extension was used
credProps?WebAuthn credential properties objectCredential properties output
hmacCreateSecret?booleanWhether the authenticator created an HMAC secret

FiveCord accepts additional extension result fields.

FieldTypeDescription
rk?booleanWhether the created credential is discoverable

FiveCord accepts additional credential property fields.

Every operation that issues a WebAuthn authentication challenge returns this object: sudo verification here, and the login option operations on HTTP authentication.

FieldTypeDescription
challenge1stringBase64url one-use challenge
timeout2integerAuthenticator operation timeout in milliseconds
rpIdstringRelying party identifier chosen by passkey domain selection
allowCredentials?3array[WebAuthn credential descriptor object]Credentials accepted for this operation
userVerification4stringUser verification requirement, one of discouraged, preferred, or required

1 Expires five minutes after issue and is bound to the operation that issued it, so a sudo challenge cannot be redeemed as a login assertion

2 Every operation emits the fixed value 60000, a standard PublicKeyCredential request option

3 Sudo and MFA login options list the passkeys passkey domain selection chooses, and passkey bridge options list the account’s fluxer.app passkeys. The field is absent on the discoverable login route and on passkey bridge options for sign-in

4 Sudo options, MFA login completion, and passkey bridge options for two-factor sign-in and sudo request discouraged. Discoverable login and passkey bridge options for sign-in request and verify required

FiveCord emits no other PublicKeyCredential request options member, so hints and extensions never appear on this object.

FieldTypeDescription
rpWebAuthn relying party objectRelying party identity
userWebAuthn registration user objectAccount identity bound to the credential
challengestringBase64url one-use challenge
pubKeyCredParams1array[WebAuthn public key parameter object]Accepted public key algorithms
timeout2integerAuthenticator operation timeout in milliseconds
excludeCredentials3array[WebAuthn credential descriptor object]Existing credentials that cannot be registered again
authenticatorSelection4WebAuthn authenticator selection objectAuthenticator selection requirements
attestation5stringAttestation conveyance, always none
extensions6WebAuthn client extension inputs objectRequested client extensions
hints7array[string]Authenticator hints

1 Always the COSE identifiers -8, -7, and -257, in that order, standing for EdDSA, ECDSA with SHA-256, and RSASSA-PKCS1-v1_5 with SHA-256

2 Every registration emits the fixed value 60000

3 Holds every credential already stored for the account, replaced passkeys included, so an authenticator cannot enrol the same credential twice. It is an empty array when the account holds none. Passkey update options hold only the account’s listed fluxer.com passkeys

4 FiveCord requests a preferred discoverable credential and preferred user verification, and requires neither, so requireResidentKey is false

5 FiveCord requests none, so no attestation statement is retained

6 Always present with credProps set to true, the only extension FiveCord requests

7 An empty array, except on passkey update options for a passkey last used from a phone or security key, where it is hybrid then security-key

FieldTypeDescription
namestringRelying party display name configured for the instance
idstringRelying party identifier chosen by passkey domain selection
FieldTypeDescription
idstringWebAuthn user handle, the decimal account snowflake encoded as base64url
namestringAccount username
displayName1stringAccount display label

1 FiveCord supplies the account username in both name and displayName

FieldTypeDescription
algintegerCOSE algorithm identifier
typestringCredential type, always public-key
FieldTypeDescription
authenticatorAttachment?stringAuthenticator attachment, either cross-platform or platform
requireResidentKey?booleanWhether a discoverable credential is required
residentKey?stringDiscoverable credential preference, one of discouraged, preferred, or required
userVerification?stringUser verification preference, one of discouraged, preferred, or required
FieldTypeDescription
appid?stringAppID extension value
credProps?1booleanWhether credential properties are requested
hmacCreateSecret?booleanWhether HMAC secret creation is requested
minPinLength?booleanWhether minimum PIN length is requested

1 The only member FiveCord ever sets, always true on WebAuthn registration options

FieldTypeDescription
totpbooleanWhether the account has a TOTP secret and the TOTP authenticator type, as in the sudo mode method availability object
webauthnbooleanWhether the account has at least one registered WebAuthn credential
backup_codesbooleanWhether the account holds at least one unconsumed backup code, as in the sudo mode method availability object
has_mfabooleanWhether the account can satisfy a sudo mode challenge, as in the sudo mode methods object

The account as it stands after Set WebAuthn two-factor authentication, together with any backup codes that operation minted.

FieldTypeDescription
useruser objectThe updated account, with authenticator_types reflecting the change
backup_codes1?array[MFA backup code object]The 10 codes minted by this call, or null when none were minted

1 Present with codes only when the call turned the toggle on for an account holding no backup code. A call that turned the toggle off, or that found a set already in place, reports null

POST/v1/users/@me/mfa/totp/enableMFA

Enables TOTP for the current account and returns an MFA backup codes object holding 10 new codes. Sudo mode is required. Emits a User Update Gateway event.

FiveCord checks sudo mode first, so an account that already holds a WebAuthn credential proves it with that credential.

  • The account needs a verified email address and is otherwise refused with 403 MFA_EMAIL_VERIFICATION_REQUIRED.
  • An account that already has TOTP enabled is refused with 400 TWO_FA_NOT_ENABLED.

The body extends the sudo verification object with the fields below, and an existing sudo proof is sent in the X-FiveCord-Sudo-Mode-JWT request header.

FieldTypeDescription
secret1stringBase32 TOTP secret (1-256 characters)
code2stringCurrent TOTP code generated from the submitted secret (1-32 characters)

1 Generated by the client and stored verbatim on the account

2 A backup code is rejected here

StatusBodyCondition
200MFA backup codes objectTOTP was enabled and 10 backup codes were issued
400error responseThe TOTP code did not match the submitted secret, returning INVALID_CODE on the path code, or TOTP is already enabled and the request returns TWO_FA_NOT_ENABLED
403error responseThe account email is unverified and the request returns MFA_EMAIL_VERIFICATION_REQUIRED, or sudo mode was not proven and the request returns SUDO_MODE_REQUIRED

TOTP is enabled and 10 backup codes are issued. Any backup code the account already held stays in place, so the response has only the 10 new codes. FiveCord also copies the account’s authenticator types to every bot account the user owns. User Update reaches the user’s sessions and each owned bot whose authenticator types changed.

10 requests per minute for each authenticated user, on the user:mfa:totp:enable bucket.

POST/v1/users/@me/mfa/totp/disableMFA

Disables TOTP for the current account and returns 204 with an empty body. Sudo mode is required. Emits a User Update Gateway event.

FiveCord checks one authenticator code or backup code for each request. With a valid sudo token, it checks code. Without one, the sudo verification fields prove sudo mode when mfa_method is set, and FiveCord does not check code. Otherwise code proves sudo mode as if it were mfa_code with mfa_method set to totp, so the attempt draws on the TOTP allowance described under sudo mode.

TOTP must already be enabled, or the request is refused with 400 TWO_FACTOR_REQUIRED. A verified email is not required, so an account whose address later became unverified can still remove its authenticator.

The body extends the sudo verification object with the field below, and an existing sudo proof is sent in the X-FiveCord-Sudo-Mode-JWT request header.

FieldTypeDescription
code1stringCurrent TOTP code or an unconsumed backup code (1-32 characters)

1 A wrong value returns INVALID_CODE on the path code with a sudo token, and INVALID_MFA_CODE on the path mfa_code when code proves sudo mode

StatusBodyCondition
204emptyTOTP was disabled
400error responseThe code did not match, or TOTP is not enabled and the request returns TWO_FACTOR_REQUIRED
403error responseSudo mode was not proven and the request returns SUDO_MODE_REQUIRED

TOTP is disabled. Every backup code stops working unless the account keeps passkeys enabled as a second factor, in which case the whole set survives. FiveCord removes the TOTP authenticator type, along with the unassigned legacy authenticator value 1 when the account still held it. FiveCord also copies the account’s authenticator types to every bot account the user owns. User Update goes to the user’s sessions and each owned bot whose authenticator types changed.

10 requests per minute for each authenticated user, on the user:mfa:totp:disable bucket.

POST/v1/users/@me/mfa/backup-codesMFA

Returns an MFA backup codes object, replacing the set first when regenerate is true. Sudo mode is required.

Neither a verified email nor an authenticator is required, so an account with no MFA at all can prove sudo mode with its password and use this operation. An account whose only second factor is passkeys proves sudo mode with a passkey, or with one of these codes as mfa_code under mfa_method of totp, and reads or replaces its set the same way. Complete login with TOTP redeems one of those codes later.

A backup code sent as mfa_code to satisfy sudo mode is consumed. With regenerate false it comes back with consumed set to true. With regenerate true it is deleted with the rest of the previous set and does not appear.

The body extends the sudo verification object with the field below, and an existing sudo proof is sent in the X-FiveCord-Sudo-Mode-JWT request header.

FieldTypeDescription
regenerate1booleanWhether to discard the current set and issue 10 replacements

1 The field is required. Passing false reads the current set without changing it

StatusBodyCondition
200MFA backup codes objectThe current or replacement codes were returned
403error responseSudo mode was not proven and the request returns SUDO_MODE_REQUIRED

6 requests per minute for each authenticated user, on the user:mfa:backup_codes bucket.

POST/v1/users/@me/mfa/backup-codes/challenge

Creates a backup codes challenge ticket and sends a verification code to the account email address. Returns an MFA backup codes challenge object on success.

Sudo mode is not required, and the route reads no sudo verification field.

The send consumes the challenge start control.

  • The account needs a verified email address and is otherwise refused with 403 MFA_EMAIL_VERIFICATION_REQUIRED.
  • An account holding no email address is refused with 400 INVALID_FORM_BODY and the validation code USER_DOES_NOT_HAVE_AN_EMAIL_ADDRESS on the path email.
  • The account needs TOTP enabled and is otherwise refused with 400 TWO_FACTOR_REQUIRED.

The body can be omitted, and any supplied body is an object with no fields. FiveCord strips unknown keys before it handles the request.

StatusBodyCondition
200MFA backup codes challenge objectThe ticket was created and a code was sent
400error responseTOTP is not enabled and the request returns TWO_FACTOR_REQUIRED, or the account holds no address
403error responseThe account email is unverified and the request returns MFA_EMAIL_VERIFICATION_REQUIRED

FiveCord stores a ticket holding the code and its expiry, and one verification email goes to the account address. No account field changes and no backup code is issued.

6 requests per minute for each authenticated user, on the user:mfa:backup_codes_challenge:start bucket.

POST/v1/users/@me/mfa/backup-codes/challenge/resend

Sends a fresh verification code for an active backup codes challenge ticket. Returns 204 with an empty body.

The route reads the ticket and the account address only. An account holding no email address is refused with 400 INVALID_FORM_BODY and the validation code USER_DOES_NOT_HAVE_AN_EMAIL_ADDRESS on the path email.

The send consumes the challenge resend control, and the ticket enforces its own 30-second cooldown.

FieldTypeDescription
ticketstringThe identifier returned by Start MFA backup codes challenge (1-256 characters)
StatusBodyCondition
204emptyA replacement code was sent
400error responseThe ticket is unknown and the request returns INVALID_OR_EXPIRED_TICKET

FiveCord replaces the ticket’s code, send time, and code expiry, which invalidates the previous code. The stored proof is cleared and one verification email goes to the account address.

6 requests per minute for each authenticated user, on the user:mfa:backup_codes_challenge:resend bucket.

POST/v1/users/@me/mfa/backup-codes/challenge/verify

Verifies the emailed code and returns an MFA backup codes verification object holding the current codes and a proof.

Verification consumes the code. A ticket holding no code fails with VERIFICATION_CODE_NOT_ISSUED, which is what a second verification of the same ticket returns. A code past its 10-minute lifetime fails with VERIFICATION_CODE_EXPIRED. A mismatch fails with INVALID_VERIFICATION_CODE.

FiveCord charges the per-ticket verification allowance before it reads the code, so a wrong code and a correct code both draw on it.

FieldTypeDescription
ticketstringThe identifier returned by Start MFA backup codes challenge (1-256 characters)
codestringThe code sent to the account address (1-256 characters)
StatusBodyCondition
200MFA backup codes verification objectThe code was accepted
400error responseThe ticket returns INVALID_OR_EXPIRED_TICKET, or the code was not issued, has expired, or did not match

The ticket stores a fresh proof and its code is cleared. The response holds every backup code on the account, including the consumed ones. No backup code is issued, consumed, or deleted.

20 requests per minute for each authenticated user, on the user:mfa:backup_codes_challenge:verify bucket.

POST/v1/users/@me/mfa/backup-codes/challenge/regenerate

Replaces the account’s backup codes with 10 new ones and returns an MFA backup codes object. Sudo mode is not required.

The ticket and its proof are the only authorisation. Without TOTP enabled the request is refused with 400 TWO_FACTOR_REQUIRED.

A ticket holding no proof returns INVALID_OR_EXPIRED_TICKET on the path verification_proof, and a proof that does not match returns INVALID_PROOF_TOKEN.

FieldTypeDescription
ticketstringThe identifier returned by Start MFA backup codes challenge (1-256 characters)
verification_proofstringThe proof issued by Verify MFA backup codes challenge code (1-256 characters)
StatusBodyCondition
200MFA backup codes objectThe previous set was deleted and 10 replacements were issued
400error responseTOTP is not enabled and the request returns TWO_FACTOR_REQUIRED, or the ticket or the proof was rejected

The previous set is replaced with 10 new codes. A failure can leave the old codes unusable. Authenticator types are unchanged and no Gateway event is emitted.

6 requests per minute for each authenticated user, on the user:mfa:backup_codes_challenge:regenerate bucket.

GET/v1/users/@me/mfa/webauthn/credentials

Returns an array of WebAuthn credential objects registered to the current account, or an empty array when none exist. Replaced passkeys are left out. Sudo mode is not required.

StatusBodyCondition
200array[WebAuthn credential object]Credentials were returned

40 requests per 10 seconds for each authenticated user, on the mfa:webauthn:list bucket.

POST/v1/users/@me/mfa/webauthn/credentials/registration-optionsMFA

Creates a one-use registration challenge and returns a WebAuthn registration options object for the domain passkey domain selection chooses. Sudo mode is required.

This operation sets no X-FiveCord-Sudo-Mode-JWT response header.

  • The account needs a verified email address.
  • An account already holding 10 credentials receives 400 WEBAUTHN_CREDENTIAL_LIMIT_REACHED and no challenge.

The body is a sudo verification object. An existing sudo proof is sent in the X-FiveCord-Sudo-Mode-JWT request header.

StatusBodyCondition
200WebAuthn registration options objectChallenge and registration options were issued
400error response10 credentials are already registered and the request returns WEBAUTHN_CREDENTIAL_LIMIT_REACHED
403error responseThe account email is unverified and the request returns MFA_EMAIL_VERIFICATION_REQUIRED, or sudo mode was not proven and the request returns SUDO_MODE_REQUIRED

FiveCord issues a registration challenge for the current user. It expires after five minutes and can be redeemed once.

20 requests per 10 seconds for each authenticated user, on the mfa:webauthn:registration_options bucket.

POST/v1/users/@me/mfa/webauthn/credentials

Consumes a registration challenge, adds one WebAuthn credential to the current account, and returns 204 with an empty body. Emits a WebAuthn Credentials Update Gateway event.

Registration changes no authenticator type. The credential answers a sudo mode challenge and a passwordless login straight away, and passkeys become a second factor at password login only through Set WebAuthn two-factor authentication.

The account needs a verified email and fewer than 10 registered credentials. This route accepts no sudo verification fields of its own. FiveCord verifies the attestation against the domain the challenge was issued for and the instance’s allowed origins, and does not require user verification. The credential keeps that domain.

FieldTypeDescription
responseWebAuthn registration response objectAuthenticator response for the registration challenge
challengestringOne-use registration challenge (1-1024 characters)
namestringUser-assigned credential name (1-100 characters)
StatusBodyCondition
204emptyCredential was registered
400error responseThe challenge or attestation failed and the request returns INVALID_WEBAUTHN_CREDENTIAL, the public key returns INVALID_WEBAUTHN_PUBLIC_KEY_FORMAT, the signature counter returns INVALID_WEBAUTHN_CREDENTIAL_COUNTER, or the limit returns WEBAUTHN_CREDENTIAL_LIMIT_REACHED
403error responseThe account email is unverified and the request returns MFA_EMAIL_VERIFICATION_REQUIRED

The credential is added to the account. No authenticator type changes, and no bot account the user owns is touched.

WebAuthn Credentials Update reaches the user’s sessions with the complete current credential summaries.

10 requests per minute for each authenticated user, on the mfa:webauthn:register bucket.

PATCH/v1/users/@me/mfa/webauthn/credentials/{credential_id}MFA

Changes the user-assigned name of one WebAuthn credential owned by the current account and returns 204 with an empty body. Sudo mode is required. Emits a WebAuthn Credentials Update Gateway event.

A verified email is not required.

FieldTypeDescription
credential_idstringBase64url credential ID registered to the current account (1-2048 characters)

The body extends the sudo verification object with the field below, and an existing sudo proof is sent in the X-FiveCord-Sudo-Mode-JWT request header.

FieldTypeDescription
namestringReplacement credential name (1-100 characters)
StatusBodyCondition
204emptyCredential was renamed
403error responseSudo mode was not proven and the request returns SUDO_MODE_REQUIRED
404error responseThe credential is not registered to the current account or is a replaced passkey, returning UNKNOWN_WEBAUTHN_CREDENTIAL

The credential name is replaced and no authenticator type changes. WebAuthn Credentials Update reaches the current user’s sessions with the complete current credential summaries.

20 requests per 10 seconds for each authenticated user, on the mfa:webauthn:update bucket.

DELETE/v1/users/@me/mfa/webauthn/credentials/{credential_id}MFA

Deletes one WebAuthn credential owned by the current account and returns 204 with an empty body. Sudo mode is required. Emits a WebAuthn Credentials Update Gateway event, and a User Update event when this was the account’s final WebAuthn credential and passkeys were enabled as a second factor.

A verified email is not required.

FieldTypeDescription
credential_idstringBase64url credential ID registered to the current account (1-2048 characters)

The body is a sudo verification object. An existing sudo proof is sent in the X-FiveCord-Sudo-Mode-JWT request header.

StatusBodyCondition
204emptyCredential was deleted
403error responseSudo mode was not proven and the request returns SUDO_MODE_REQUIRED
404error responseThe credential is not registered to the current account or is a replaced passkey, returning UNKNOWN_WEBAUTHN_CREDENTIAL

The credential is deleted, together with the replaced passkeys it took over from. When no listed credential remains, every replaced passkey the account still holds is deleted as well. When it was the final one and the account had passkeys enabled as a second factor, the WebAuthn authenticator type is removed from the account and from every bot account owned by the user.

WebAuthn Credentials Update reaches the user’s sessions with the remaining credential summaries. When the authenticator types changed, User Update also reaches the user and each affected owned bot.

10 requests per minute for each authenticated user, on the mfa:webauthn:delete bucket.

GET/v1/users/@me/mfa/webauthn/migration

Returns a passkey update object for the current session. Sudo mode is not required, and any Origin is accepted.

StatusBodyCondition
200passkey update objectThe session’s update state was returned

An open update whose passkey was deleted or replaced since is discarded. No Gateway Dispatch is emitted.

20 requests per minute for each authenticated user, on the mfa:webauthn:migration bucket, shared with the other passkey update routes.

Create passkey update registration options

Section titled “Create passkey update registration options”
POST/v1/users/@me/mfa/webauthn/migration/registration-options

Creates a one-use registration challenge for the fluxer.com passkey that replaces the session’s open passkey update. Returns a WebAuthn registration options object.

The request has no body. It needs a new-origin request and an open update on this session, and otherwise returns 404 UNKNOWN_PASSKEY_MIGRATION. Sudo mode, a verified email, and room under the credential limit are not required.

excludeCredentials lists the account’s fluxer.com passkeys. When the update’s cross_device is true, hints is hybrid then security-key, so the browser offers the phone or key used a moment ago.

StatusBodyCondition
200WebAuthn registration options objectChallenge and registration options were issued
404error responseThe request is not from a new origin or the session has no open update, returning UNKNOWN_PASSKEY_MIGRATION

FiveCord issues a registration challenge for the current user, bound to the passkey update. It expires after five minutes, can be redeemed once, and no other WebAuthn route accepts it. The update stays open.

20 requests per minute for each authenticated user, on the shared mfa:webauthn:migration bucket.

POST/v1/users/@me/mfa/webauthn/migration

Registers the fluxer.com passkey that replaces the session’s open passkey update and returns 204 with an empty body. Emits a WebAuthn Credentials Update Gateway event.

FieldTypeDescription
responseWebAuthn registration response objectAuthenticator response for the update challenge
challengestringThe challenge from create passkey update registration options (1-1024 characters)

The request needs a new-origin request and an open update on this session, and otherwise returns 404 UNKNOWN_PASSKEY_MIGRATION. FiveCord verifies the attestation for fluxer.com and accepts only the request’s own Origin in its client data.

A failed verification returns the codes of register WebAuthn credential and leaves the update open. It consumes the challenge, so fetch new options before trying again. An update that another request settled first, and a passkey that was deleted or replaced since, return 404 UNKNOWN_PASSKEY_MIGRATION.

StatusBodyCondition
204emptyThe passkey was updated
400error responseVerification failed, with a code from register WebAuthn credential
404error responseNo open update applies, returning UNKNOWN_PASSKEY_MIGRATION

FiveCord closes the update, adds the new passkey under the old one’s name, and makes the old one a replaced passkey. No authenticator type changes.

WebAuthn Credentials Update reaches the user’s sessions with the complete current credential summaries.

20 requests per minute for each authenticated user, on the shared mfa:webauthn:migration bucket.

PUT/v1/users/@me/mfa/webauthn/two-factorMFA

Turns passkeys on or off as a second factor for the current account and returns a WebAuthn two-factor object. Sudo mode is required. Emits a User Update Gateway event.

Enabling adds the WebAuthn authenticator type, so password login then asks for a passkey. Disabling removes it and leaves every registered credential in place, still usable for sudo mode and for passwordless login.

  • Enabling while the account has no registered WebAuthn credential is refused with 400 NO_PASSKEYS_REGISTERED.

The body extends the sudo verification object with enabled, and the sudo fields it merges in are repeated below. An existing sudo proof is sent in the X-FiveCord-Sudo-Mode-JWT request header.

FieldTypeDescription
enabledbooleanWhether passkeys count as a second factor at password login
password?1stringAccount password (8-256 characters)
mfa_method?2stringMFA method, either totp or webauthn
mfa_code?3stringAuthenticator code or unconsumed backup code (1-32 characters)
webauthn_response?4WebAuthn assertion objectAssertion produced for the supplied challenge
webauthn_challenge?4stringChallenge returned by create sudo WebAuthn authentication options (1-256 characters)

1 Considered only while the account holds no TOTP secret and no registered WebAuthn credential, and ignored once either exists

2 Required when the account holds a TOTP secret or a registered WebAuthn credential, unless a valid sudo token is already present

3 Required when mfa_method is totp, with a current authenticator code or an unconsumed backup code. FiveCord reads it as a backup code alone while the account holds no TOTP secret

4 Both fields are required together when mfa_method is webauthn

StatusBodyCondition
200WebAuthn two-factor objectThe authenticator type was set to the requested state
400error responseEnabling was requested with no registered credential and the request returns NO_PASSKEYS_REGISTERED
403error responseSudo mode was not proven and the request returns SUDO_MODE_REQUIRED

The WebAuthn authenticator type is added or removed, and FiveCord copies the account’s authenticator types to every bot account the user owns. User Update reaches the user’s sessions and each owned bot whose authenticator types changed.

Enabling on an account that holds no backup code issues 10, which the response returns. They are the recovery path for an account whose only second factor is passkeys, and complete login with TOTP redeems one. An account that already holds a set keeps it and the response reports null. A request that asks for the state the account already has changes nothing and issues no code.

10 requests per minute for each authenticated user, on the mfa:webauthn:two_factor bucket.

GET/v1/users/@me/sudo/mfa-methods

Returns a sudo MFA methods object describing which sudo challenges the current account can answer. Sudo mode is not required.

StatusBodyCondition
200sudo MFA methods objectAvailable methods were returned

20 requests per minute for each authenticated user, on the sudo:mfa:methods bucket.

Create sudo WebAuthn authentication options

Section titled “Create sudo WebAuthn authentication options”
POST/v1/users/@me/sudo/webauthn/authentication-options

Creates a one-use sudo challenge and returns a WebAuthn authentication options object listing the passkeys passkey domain selection chooses.

The account needs at least one credential in the chosen group. Sudo mode is not required to obtain the challenge.

StatusBodyCondition
200WebAuthn authentication options objectChallenge and options were issued
400error responseNo WebAuthn credential is registered and the request returns NO_PASSKEYS_REGISTERED

FiveCord issues a sudo challenge for the current user. It expires after five minutes and can be redeemed once by submitting it as webauthn_challenge alongside webauthn_response in a sudo verification object.

10 requests per minute for each authenticated user, on the sudo:webauthn:options bucket.

POST/v1/users/@me/passkey-bridge

Starts a sudo passkey bridge ceremony for the current account. Sudo mode is not required. Returns a passkey bridge start object.

FieldTypeDescription
runnerstringWhere the ceremony runs, either page or native
nonce_hashstringThe SHA-256 digest of the nonce the new origin keeps, as 64 lowercase hex characters

The request MUST send an Origin header of https://fluxer.com or https://canary.fluxer.com. Any other Origin, none, or a self-hosted instance returns 403 INVALID_API_ORIGIN. An account with no passkey listed for fluxer.app returns 400 NO_PASSKEYS_REGISTERED.

StatusBodyCondition
200passkey bridge start objectThe ceremony was started
400error responseThe body is invalid, or the account has no passkey for fluxer.app
403error responseThe Origin is refused, returning INVALID_API_ORIGIN

FiveCord stores the ceremony for 10 minutes. It changes no account state. No Gateway Dispatch is emitted.

10 requests per minute for each authenticated user, on the mfa:passkey_bridge:start bucket.

POST/v1/users/@me/passkey-bridge/{ceremony_id}/redeem

Redeems a finished sudo passkey bridge ceremony once for a sudo mode token. Returns a passkey bridge sudo redemption object.

FieldTypeDescription
ceremony_idstringThe identifier from the start response, 43 base64url characters
FieldTypeDescription
noncestringThe nonce whose digest started the ceremony, as base64url, 16 to 256 characters
completion_codestringThe completion code from the return fragment or the finish response, 43 base64url characters

The Origin MUST be the new origin that started the ceremony, or the request returns 403 INVALID_API_ORIGIN. A pending ceremony, a sign-in ceremony, and a ceremony another account started return 404 UNKNOWN_PASSKEY_BRIDGE and stay in place, as does an unknown or expired identifier. A wrong nonce or completion code deletes the ceremony and returns 400 INVALID_PASSKEY_BRIDGE_NONCE.

A cancelled ceremony returns cancelled. A completed one returns a token that works like one sudo mode issues, for five minutes and for this account. The response sets no X-FiveCord-Sudo-Mode-JWT header, so the client sends the token in that header itself.

StatusBodyCondition
200passkey bridge sudo redemption objectThe ceremony was redeemed
400error responseThe body or ceremony ID is invalid, or a secret does not match, returning INVALID_PASSKEY_BRIDGE_NONCE
403error responseThe Origin is refused, returning INVALID_API_ORIGIN
404error responseNo redeemable sudo ceremony of this account has this identifier, returning UNKNOWN_PASSKEY_BRIDGE

A redemption that gets past the secret check deletes the ceremony. When the request comes from a new origin and the domain migration switch is on, FiveCord opens a passkey update for the calling session. No Gateway Dispatch is emitted.

60 requests per minute for each authenticated user, on the mfa:passkey_bridge:redeem bucket.