Skip to content
Fluxer API

Phone verification

Phone verification proves that the current account controls a phone number. The account either receives a one-time code over outbound SMS, or texts an issued challenge code to a Fluxer number. Success sets the account’s verified phone state, which the phone required actions and the VERY_HIGH guild verification level consult.

Every route here needs a non-bot user session, and each one admits a session with suspicious account state.

Send phone verification and Verify phone code admit only an account that satisfies at least one of four conditions.

  • The account already holds a verified phone.
  • A TOTP authenticator is enrolled on the account.
  • The stored suspicious activity bitfield is non-zero.
  • The account belongs to at least one guild whose verification level is VERY_HIGH.

An account satisfying none of them is refused with 403 PHONE_ADD_NOT_ELIGIBLE.

Both operations run the check before they examine the submitted number. A refused account receives no message and consumes no challenge. Start inbound phone challenge skips the check entirely and issues a challenge for any non-bot session.

An instance can defer a new account’s phone requirement instead of imposing it at registration. The deferred phone gate policy sets the window after registration in which a qualifying join makes the requirement due, and the member count above which a guild is a qualifying one. A guild with the DISCOVERABLE feature qualifies at any member count.

A deferred requirement is absent from the required actions array and restricts nothing. An attempt to join a qualifying guild inside the window makes it due, and Accept invite refuses that attempt with 403 ACCOUNT_SUSPICIOUS_ACTIVITY. The account then owes REQUIRE_VERIFIED_PHONE or REQUIRE_REVERIFIED_PHONE, and every ordinary user route refuses it with the same code.

Get phone gate escape preview and Run phone gate escape set that requirement aside again in place of verifying a number. Both admit an account the rest of the API refuses. The escape is available only while all six of these hold.

  • The instance has the deferred phone gate enabled.
  • The account holds no verified phone.
  • The account owes REQUIRE_VERIFIED_PHONE or REQUIRE_REVERIFIED_PHONE.
  • The account does not owe REQUIRE_INBOUND_PHONE_VERIFICATION.
  • The requirement became due through a qualifying join and has not been deferred again.
  • The account has at least one visible required action.

The phone field is a number already in canonical E.164 form. Fluxer strips control and formatting characters, trims the value, and then matches it against ^\+[1-9]\d{1,14}$. A value that fails returns 400 INVALID_FORM_BODY with one validation error entry whose path is phone and whose code is PHONE_NUMBER_INVALID_FORMAT.

A number whose leading characters appear on the instance’s banned prefix list is refused with 400 INVALID_PHONE_NUMBER. The list holds a built-in set of country prefixes plus operator additions, and one entry blocks every number that starts with it.

ValueDescription
sms1Fluxer asks the SMS provider to deliver a one-time code to the submitted number
inbound_challenge2The user texts the issued challenge code to the returned Fluxer number

1 Requesting this value is equivalent to omitting the field

2 Requesting this value always yields an inbound challenge, and neither the lookup provider nor the outbound SMS provider is contacted

Verify phone code resolves the destination through the configured lookup provider before it checks the code. Send phone verification does the same before it sends a code, except on an attempt that policy routes inbound before the lookup, where no lookup runs. A lookup result is cached for 7 days and reused for later attempts against the same number. The rules below run in order, and the first match decides.

OrderConditionVerdict
1The lookup could not be performed1Refused with INVALID_PHONE_NUMBER
2The provider reports the number as invalidRefused with INVALID_PHONE_NUMBER
3The line type is fixedVoip or nonFixedVoipInbound, reason voip
4The number starts with +1 and its numbering plan area is CanadianInbound, reason canadian
5The line type is absent or unknownInbound, reason unknown_line_type
6The line type is landline, tollFree, premium, sharedCost, uan, voicemail, or pagerRefused with INVALID_PHONE_NUMBER
7The line type is anything other than mobile or personal2Refused with INVALID_PHONE_NUMBER
8The SMS pumping risk score reaches the threshold for the reported country3Refused with INVALID_PHONE_NUMBER
9No rule above matchedAccepted for outbound SMS

1 An instance whose lookup provider is unavailable or unconfigured refuses every number

2 mobile and personal are the only accepted line types

3 The threshold is 100 for US and CA, 70 for GB, DE, FR, IT, ES, NL, SE, NO, DK, FI, AU, NZ, JP, KR, CH, AT, BE, IE, and PT, and 35 for every other country and for a lookup reporting no country

Only Send phone verification converts an inbound verdict into a challenge. Verify phone code refuses the same three verdicts with 400 INVALID_PHONE_NUMBER.

A reason explains why the attempt could not complete over outbound SMS.

ValueDescription
voipProvider lookup classified the destination as a VoIP line
canadian1The destination is a Canadian numbering plan area
unknown_line_typeProvider lookup reported no line type or an unknown line type
expensive_destination2The configured inbound-required prefix policy matches the destination
account_forced3The account has the flag that forces inbound verification
behavioural_riskPhone attempt risk controls forced this attempt onto the inbound channel

1 Determined from the numbering plan area of a +1 number, and evaluated after the VoIP rule, so a Canadian VoIP number reports voip instead

2 Also reported when the caller explicitly requests the inbound_challenge channel. The prefix policy itself applies only while the account holds no verified phone

3 Reported in preference to expensive_destination when both would apply

Confirmation that the SMS provider accepted a one-time code for delivery. Send phone verification returns it on an outbound result, and Verify phone code then takes the number itself.

FieldTypeDescription
channelstringThe delivery channel, always the literal sms

The instructions for texting a code to Fluxer, returned by Send phone verification when policy routed the attempt inbound. channel tells the two response shapes apart.

FieldTypeDescription
channelstringThe delivery channel, always the literal inbound_challenge
challenge_code1stringThe code the user texts to our_number
our_numberstringThe Fluxer E.164 number that receives the code
expires_at2ISO8601 timestampThe moment the challenge stops being redeemable
reasonstringThe inbound challenge reason that forced this channel

1 Six decimal digits

2 15 minutes after the challenge was issued

{
"channel": "inbound_challenge",
"challenge_code": "418207",
"our_number": "+15550000000",
"expires_at": "2026-03-04T18:15:00.000Z",
"reason": "voip"
}

The code and the Fluxer number for a caller that asked for an inbound challenge directly. Start inbound phone challenge returns it.

FieldTypeDescription
challenge_code1stringThe code the user texts to our_number
our_numberstringThe Fluxer E.164 number that receives the code
expires_at2ISO8601 timestampThe moment the challenge stops being redeemable

1 Six decimal digits

2 15 minutes after the challenge was issued

Returned by Verify phone code on a successful verification.

FieldTypeDescription
verifiedbooleanAlways the literal true

One guild in a phone gate escape preview.

FieldTypeDescription
idsnowflakeThe ID of the guild
namestringThe name the guild is listed under

What Run phone gate escape would do for the current account.

FieldTypeDescription
availablebooleanWhether the escape is open to this account right now
guildsarray[phone gate escape guild object]The qualifying guilds the escape leaves, empty while available is false
owned_guildsarray[phone gate escape guild object]The qualifying guilds the account owns, which the escape keeps
POST/v1/users/@me/phone/send-verification

Starts verification of the submitted number. Requires an account satisfying eligibility. Returns an SMS delivery object or an inbound challenge object on success.

Several independent policies can force the inbound channel, and each one overrides a requested sms. Fluxer routes the attempt inbound before it contacts the lookup and SMS providers in three cases. The caller asks for inbound_challenge. The account has the flag that forces inbound verification. The configured inbound-required prefix policy matches the number while the account holds no verified phone.

An attempt that survives that step passes through phone attempt risk controls keyed by account, by client IP address, and by /24 network. A hard block returns 429 PHONE_RATE_LIMIT_EXCEEDED with a Retry-After of 86400. A captcha decision returns 400 CAPTCHA_REQUIRED, and this route reads no CAPTCHA solution header, so the attempt cannot be retried with a solution. An inbound decision returns a challenge with the behavioural_risk reason.

An earlier attempt against the same account or number can record a provider cooldown, and a later request under that cooldown returns 429 PHONE_RATE_LIMIT_EXCEEDED. The destination is then resolved through provider lookup. A number that has already completed verification twice is refused with 400 PHONE_ALREADY_USED.

Two further controls bound outbound delivery: 3 sends per 6 hours for each account, and 3 sends per 5 days for each number. Either denial returns 429 PHONE_RATE_LIMIT_EXCEEDED. The provider can also refuse the send: an invalid destination returns 400 INVALID_PHONE_NUMBER, a throttle returns 429 PHONE_RATE_LIMIT_EXCEEDED, and every other refusal, an unreachable provider included, returns 400 SMS_VERIFICATION_UNAVAILABLE. An attempt routed inbound on an instance where the inbound flow or its receiving number is unconfigured returns the same code.

FieldTypeDescription
phonestringThe number in canonical E.164 form, matching ^\+[1-9]\d{1,14}$
channel?1stringThe preferred verification channel

1 Omitting the field requests outbound SMS. Requesting inbound_challenge always yields a challenge reported with the expensive_destination reason, unless the account flag independently forces the channel and the reason becomes account_forced

StatusBodyCondition
200SMS delivery object | inbound challenge objectVerification was started on the channel named by the response
400error responseThe number is refused with INVALID_PHONE_NUMBER, was verified twice already and returns PHONE_ALREADY_USED, risk controls demand CAPTCHA_REQUIRED, or the send could not start and returns SMS_VERIFICATION_UNAVAILABLE
403error responseThe account is not eligible and the request returns PHONE_ADD_NOT_ELIGIBLE
4291rate limit responseRisk controls, a provider throttle, or an account or per-number send control denies the request, which returns PHONE_RATE_LIMIT_EXCEEDED

1 A phone denial uses the rate limit envelope with code set to PHONE_RATE_LIMIT_EXCEEDED rather than the RATE_LIMITED an ordinary route denial returns

On the 429, X-RateLimit-Scope reports shared when the per-number control or a number-scoped provider throttle produced the denial.

On an outbound result, Fluxer asks the configured SMS provider to deliver a one-time code. On an inbound result, it creates a one-use challenge for the current account.

Fluxer records an attempt against the phone attempt risk counters only once it reaches provider lookup.

5 requests per minute for each authenticated user, on the phone:send_verification bucket.

POST/v1/users/@me/phone/inbound-challenge

Issues an inbound challenge without submitting or examining a phone number. Returns a direct inbound challenge object on success.

The eligibility check does not apply here. The challenge code is six decimal digits, lives for 15 minutes, and can be redeemed once. Requesting a further challenge does not revoke an earlier one, so several codes issued to the same account can be live at the same time until each expires or is redeemed.

StatusBodyCondition
200direct inbound challenge objectA challenge was issued
400error responseThe instance has no receiving number for the inbound challenge and the request returns SMS_VERIFICATION_UNAVAILABLE

Fluxer creates a one-use challenge for the current account. The challenge completes out of band when the provider delivers the matching inbound message, and the HTTP response has no part of it. On completion, Fluxer sets the verified phone state, clears the phone requirements from the stored suspicious activity bitfield, and dispatches User Update. When the sending number has already completed verification twice, Fluxer abandons the completion and produces no observable error.

5 requests per minute for each authenticated user, on the phone:send_verification bucket, shared with Send phone verification.

POST/v1/users/@me/phone/verify

Verifies an outbound SMS code. Requires an account satisfying eligibility. Returns a phone verification result object on success. Emits a User Update Gateway event.

The number is resolved through provider lookup again before the code is checked, so a number that has become ineligible since the code was sent is refused.

The configured SMS provider checks the number and the code together, so the provider decides the code lifetime and the attempt allowance. A rejected code returns 400 INVALID_PHONE_VERIFICATION_CODE. A provider throttle returns 429 PHONE_RATE_LIMIT_EXCEEDED and records a cooldown that also denies Send phone verification until it expires. An unreachable provider, or one answering with a server error, returns 400 SMS_VERIFICATION_UNAVAILABLE. An account record that has been deleted, or can no longer be read, fails with 400 PHONE_VERIFICATION_REQUIRED after the code has been accepted.

FieldTypeDescription
phonestringThe number in canonical E.164 form, matching ^\+[1-9]\d{1,14}$
code1stringThe code the outbound SMS delivered (1-32 characters)

1 Stripped of control and formatting characters and trimmed before the length check, and the normalised value is what the provider checks

StatusBodyCondition
200phone verification result objectThe number is now verified for the account
400error responseThe number returns INVALID_PHONE_NUMBER, the code returns INVALID_PHONE_VERIFICATION_CODE, the provider returns SMS_VERIFICATION_UNAVAILABLE, the number returns PHONE_ALREADY_USED, or the account returns PHONE_VERIFICATION_REQUIRED
403error responseThe account is not eligible and the request returns PHONE_ADD_NOT_ELIGIBLE
4291rate limit responseA provider throttle denies the request, which returns PHONE_RATE_LIMIT_EXCEEDED

1 A phone denial uses the rate limit envelope with code set to PHONE_RATE_LIMIT_EXCEEDED rather than the RATE_LIMITED an ordinary route denial returns

On the 429, X-RateLimit-Scope reports shared when a number-scoped provider throttle produced the denial.

Fluxer marks the account as holding a verified phone. It then clears every phone requirement from the stored suspicious activity bitfield: REQUIRE_VERIFIED_PHONE, REQUIRE_REVERIFIED_PHONE, the four combined email-or-phone requirements, REQUIRE_INBOUND_PHONE_VERIFICATION, and the marker deferring a phone requirement until the account joins a qualifying community guild. Email-only requirements remain, so an account that also owes email verification stays restricted.

Fluxer clears the spammer flag when present and dispatches User Update. When the spammer flag was cleared, Fluxer also dispatches Guild Member Update for the account in every guild it belongs to, before the HTTP response returns.

10 requests per minute for each authenticated user, on the phone:verify_code bucket.

GET/v1/users/@me/required-actions/phone-gate-escape

Returns a phone gate escape preview object for the current account. For an account outside the state Deferred phone requirement describes, available is false and both arrays are empty.

StatusBodyCondition
200phone gate escape preview objectThe preview was returned

20 requests per minute for each authenticated user, on the user:phone_gate_escape:preview bucket.

POST/v1/users/@me/required-actions/phone-gate-escape

Leaves the qualifying guilds the account does not own and defers the phone requirement again. Returns the user object.

The body is an empty object. An absent or empty body is read as an empty object, and any supplied property is discarded. A body that is not valid JSON returns 400 INVALID_FORM_BODY with INVALID_FORMAT at the body path. A body that parses as valid JSON but is not an object is rejected with 400.

StatusBodyCondition
200user objectThe escape ran, wholly or up to the per-request limit
400error responseThe escape is unavailable and the request returns PHONE_GATE_ESCAPE_UNAVAILABLE

Fluxer leaves each qualifying guild the account does not own, at most 25 in one request, with the same effects as Leave guild. A qualifying guild the account owns is kept and does not block the escape.

A request that leaves the whole set then defers the requirement again and dispatches User Update. A request that stopped at the limit writes no account field, dispatches nothing, and returns the account with the requirement still due. Repeat the request until the returned required_actions has no phone entry.

5 requests per hour for each authenticated user, on the user:phone_gate_escape:execute bucket.