Skip to content

Auth Flows (no SDK)

The endpoints on this page are the ones the Bridge SDKs call under the hood. If there is no SDK for your language, or you want full control over your login UI, you can drive every flow yourself with plain HTTP: no redirects to the hosted login, no cookies, everything as JSON request/response pairs.

Every flow ends the same way: you hold a short-lived login session token and a chosen workspace (the API calls it a tenant; you manage your app, its allowed origins, and its workspaces in the Control Center), and you exchange them for OAuth tokens with POST /auth/token/direct. From there on you are in standard token territory: refresh, verification, and revocation are covered on the Authentication page.

Conventions that apply to every call on this page:

  • SDK mode. Calls that carry "mode": "sdk" in the body tell The Bridge to return everything as JSON instead of setting cookies and redirecting. That is the mode you want here.
  • Origin checks. Requests with "mode": "sdk" must send an Origin header that matches one of your app’s allowed origins (configured in Control Center). Browsers add it automatically; when testing with curl you must add it yourself, so the curl examples below include it.
  • The session token. Credential endpoints return session, a short-lived JWT representing the half-finished login, together with expires (its expiry hint) and mfaState. It is not an access token. Store it (memory is fine) and pass it to the next step.
  • mfaState tells you what the next step is:
    • DISABLED: no MFA required, go straight to the token exchange.
    • REQUIRED: the user must pass an MFA challenge first.
    • SETUP: MFA is enabled for your app but the user has no phone enrolled yet, run MFA setup first.
    • COMPLETED: the MFA step has been passed for this session.
  • tenantUsers is the list of workspaces the user can enter. Each entry’s id is the tenantUserId you pass to the token exchange. If there is exactly one, select it automatically; if there are several, show a workspace picker.

Every login flow (password, magic link, passkey) finishes with this call.

Exchange a login session and a chosen workspace for OAuth tokens. Requires the session’s mfaState to be COMPLETED or DISABLED, and tenantUserId must be one of the session user’s tenantUsers entries.

POST https://api.thebridge.dev/auth/token/direct

Body Parameters

ParameterTypeRequiredDescription
sessionstringRequiredThe session JWT from the credential step
tenantUserIdstringRequiredThe id of the chosen tenantUsers entry
appIdstringRequiredYour app ID. Must match the app the session was issued for
scopestringOptionalDefaults to openid profile email onboarding tenant
modestringRequiredSet to sdk

The full token set plus user_profile (the decoded id_token). Store access_token, refresh_token, and id_token; the login session can be discarded. HTTP 401 when the session is invalid, MFA is not completed, or the tenant user does not belong to the session’s user.

Request example

curl --request POST 'https://api.thebridge.dev/auth/token/direct' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "session": "SESSION_JWT",
  "tenantUserId": "63d25a9e0796d40008680f9a",
  "appId": "YOUR_APP_ID",
  "scope": "openid profile email onboarding tenant",
  "mode": "sdk"
}'

Response example:

{
    "access_token": "XXXX",
    "refresh_token": "XXXX",
    "token_type": "Bearer",
    "expires_in": 3600,
    "id_token": "XXXX",
    "user_profile": {
        "sub": "63d25a9e0796d40008680f9a",
        "name": "John Doe",
        "preferred_username": "john@example.com",
        "email": "john@example.com",
        "email_verified": true,
        "tenant_id": "63d25a9e0796d40008680f96",
        "tenant_name": "Johns Family"
    }
}

Create a new workspace with the user as its owner. The sequence:

  1. POST /auth/auth/signup with the user’s email and name. The Bridge sends the user a verification email.
  2. The user clicks the verification link and sets up their credentials.
  3. The user logs in through any of the login flows below.

Nothing needs to be stored between steps; the flow hands over to a normal login.

Self-signup must be enabled for your app, otherwise the call fails with HTTP 403.

POST https://api.thebridge.dev/auth/auth/signup

Body Parameters

ParameterTypeRequiredDescription
emailstringRequiredThe new user's email address
firstNamestringOptionalGiven name
lastNamestringOptionalFamily name
appIdstringRequiredYour app ID
modestringRequiredSet to sdk

{ "success": true, "message": "Check your email to verify your account" }

HTTP 403 when tenant self-signup is not enabled for the app or the origin is not allowed.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/signup' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "email": "john@example.com",
  "firstName": "John",
  "lastName": "Doe",
  "appId": "YOUR_APP_ID",
  "mode": "sdk"
}'

Response example:

{
    "success": true,
    "message": "Check your email to verify your account"
}

The classic flow. The sequence:

  1. (Optional) POST /auth/auth/credentialsConfig with the email address, to learn which login methods this user has and render the right UI.
  2. POST /auth/auth/authenticate with email and password. Store session, note mfaState, and keep tenantUsers.
  3. If mfaState is REQUIRED or SETUP, run the matching MFA flow. It returns an updated session; use that from here on.
  4. POST /auth/token/direct with the session and the chosen tenantUsers[i].id. Store the returned tokens.

Check which authentication methods are available for a username: password, passkeys, and any federation connections (SSO). Use it after the user types their email to decide whether to show a password field, a passkey button, or an SSO redirect.

POST https://api.thebridge.dev/auth/auth/credentialsConfig

Body Parameters

ParameterTypeRequiredDescription
usernamestringRequiredThe user's email address
appIdstringRequiredYour app ID
modestringRequiredSet to sdk

{ hasPassword, hasPasskeys, federationConnections }. HTTP 401 for unknown users.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/credentialsConfig' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "username": "john@example.com",
  "appId": "YOUR_APP_ID",
  "mode": "sdk"
}'

Response example:

{
    "hasPassword": true,
    "hasPasskeys": false,
    "federationConnections": [
        { "id": "64a1f00b8a1c4d0008b1e001", "type": "saml", "name": "Acme SSO" }
    ]
}

Submit the user’s email and password. On success you get the login session and the user’s workspaces.

POST https://api.thebridge.dev/auth/auth/authenticate

Body Parameters

ParameterTypeRequiredDescription
usernamestringRequiredThe user's email address
passwordstringRequiredThe user's password
appIdstringRequiredYour app ID
modestringRequiredSet to sdk

| Field | Type | Description | |---|---|---| | session | string | The login session JWT. Carry it to the next step | | expires | number | Session expiry hint | | mfaState | string | DISABLED, REQUIRED, or SETUP | | tenantUsers | array | The user’s workspaces: { id, username, fullName, tenant: { id, name, logo } } |

HTTP 401 for wrong credentials.

Between steps, keep session and the chosen tenantUsers[i].id. Nothing else is needed.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/authenticate' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "username": "john@example.com",
  "password": "SECRET",
  "appId": "YOUR_APP_ID",
  "mode": "sdk"
}'

Response example:

{
    "session": "XXXX",
    "expires": 604800,
    "mfaState": "DISABLED",
    "tenantUsers": [
        {
            "id": "63d25a9e0796d40008680f9a",
            "username": "john@example.com",
            "fullName": "John Doe",
            "tenant": {
                "id": "63d25a9e0796d40008680f96",
                "name": "Johns Family",
                "logo": ""
            }
        }
    ]
}

Passwordless login over email. The sequence:

  1. POST /auth/auth/magic-link with the user’s email and a successUrl pointing back into your app. The Bridge emails the user a login link.
  2. The user clicks the link and lands on your successUrl with ?bridge_magic_link_token=TOKEN appended.
  3. POST /auth/auth/magic-link/authenticate with that token. You get the same session, mfaState, and tenantUsers as a password login.
  4. If mfaState is REQUIRED or SETUP, run the matching MFA flow.
  5. POST /auth/token/direct with the session and the chosen workspace.

Nothing needs to be stored between steps 1 and 3: the token in the link is a signed JWT that carries the app and the user.

Send a login link to the user’s email address.

POST https://api.thebridge.dev/auth/auth/magic-link

Body Parameters

ParameterTypeRequiredDescription
usernamestringRequiredThe user's email address
successUrlstringOptionalThe page in your app the emailed link should land on. The magic link token is appended as ?bridge_magic_link_token=TOKEN
appIdstringRequiredYour app ID
modestringRequiredSet to sdk

{ "expiresIn": 900000 }, the link’s validity in milliseconds. Unknown email addresses also get HTTP 200 with a faked expiresIn, so the endpoint cannot be used to discover which emails have accounts.

Always pass successUrl when you build your own UI. Without it the link is built for the hosted login flow instead: it points at /auth/magic-link/login?t=TOKEN on your origin (or on the hosted login if the Origin header is not an allowed origin).

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/magic-link' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "username": "john@example.com",
  "successUrl": "https://your-app.example.com/login/magic-link",
  "appId": "YOUR_APP_ID",
  "mode": "sdk"
}'

Response example:

{
    "expiresIn": 900000
}

When the user lands on your successUrl, read bridge_magic_link_token from the query string and exchange it for a login session. No appId or mode is needed: the token itself identifies the app and the user.

POST https://api.thebridge.dev/auth/auth/magic-link/authenticate

Body Parameters

ParameterTypeRequiredDescription
tokenstringRequiredThe bridge_magic_link_token value from the URL

| Field | Type | Description | |---|---|---| | session | string | The login session JWT. Carry it to the next step | | expires | number | Session expiry hint | | mfaState | string | DISABLED, REQUIRED, or SETUP | | tenantUsers | array | The user’s workspaces: { id, username, fullName, tenant: { id, name, logo } } |

HTTP 401 when the token is invalid or expired; send a fresh link.

The magic link JWT and the login session are different tokens. Exchange the link token here first, then use the returned session everywhere else.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/magic-link/authenticate' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "token": "MAGIC_LINK_TOKEN"
}'

Response example:

{
    "session": "XXXX",
    "expires": 604800,
    "mfaState": "DISABLED",
    "tenantUsers": [
        {
            "id": "63d25a9e0796d40008680f9a",
            "username": "john@example.com",
            "fullName": "John Doe",
            "tenant": {
                "id": "63d25a9e0796d40008680f96",
                "name": "Johns Family",
                "logo": ""
            }
        }
    ]
}

WebAuthn login. Both ceremonies follow the same shape: fetch options from The Bridge, run the browser ceremony (navigator.credentials.create() or .get()), and post the resulting credential back for verification. In SDK mode the server’s challenge comes back in the options response as sdkChallengeToken (instead of a cookie), and you must echo it on the verify call together with sdkOrigin, your app’s origin as the browser sees it.

Registration (the user proves email ownership first):

  1. POST /auth/auth/passkeys/request-setup-link with the user’s email. The Bridge emails a setup link that lands on /auth/setup-passkey/PASSKEY_SETUP_TOKEN on your origin (when the request Origin is one of your allowed origins). Serve that route in your app.
  2. On that page, GET /auth/auth/passkeys/registration-options?passkeySetupToken=.... Pull sdkChallengeToken out of the response; the rest is standard WebAuthn creation options.
  3. Run navigator.credentials.create() with the options and serialize the credential to JSON.
  4. POST /auth/auth/passkeys/verify-registration?passkeySetupToken=... with the credential JSON plus appId, sdkChallengeToken, and sdkOrigin. Response: { "verified": true }.

Login:

  1. GET /auth/auth/passkeys/authentication-options with your app ID in the x-app-id header. Pull out sdkChallengeToken.
  2. Run navigator.credentials.get() with the options and serialize the assertion to JSON.
  3. POST /auth/auth/passkeys/verify-authentication with the assertion plus mode, appId, sdkChallengeToken, and sdkOrigin. Same response as a password login: session, mfaState, tenantUsers.
  4. If mfaState is REQUIRED or SETUP, run the matching MFA flow, then POST /auth/token/direct.

The WebAuthn JSON encoding (base64url rawId, clientDataJSON, and friends) is fiddly to hand-roll. The @simplewebauthn/browser package’s startRegistration/startAuthentication produce exactly the JSON these endpoints expect; The Bridge verifies with @simplewebauthn/server.

Email the user a link to register a new passkey device. Registration is gated behind this email round-trip so that only someone with access to the inbox can add a passkey.

POST https://api.thebridge.dev/auth/auth/passkeys/request-setup-link

Body Parameters

ParameterTypeRequiredDescription
usernamestringRequiredThe user's email address
appIdstringRequiredYour app ID
modestringRequiredSet to sdk

{ "success": true }. The emailed link points at /auth/setup-passkey/PASSKEY_SETUP_TOKEN on your origin when the request Origin is allowed, otherwise on the hosted login.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/passkeys/request-setup-link' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "username": "john@example.com",
  "appId": "YOUR_APP_ID",
  "mode": "sdk"
}'

Response example:

{
    "success": true
}

Generate the WebAuthn creation options for a new passkey. Identify your app with the x-app-id header; that is what switches the endpoint into SDK mode and makes it return sdkChallengeToken in the body.

GET https://api.thebridge.dev/auth/auth/passkeys/registration-options?passkeySetupToken=PASSKEY_SETUP_TOKEN

Query Parameters

ParameterTypeRequiredDescription
passkeySetupTokenstringRequiredThe token from the setup link URL

Headers

ParameterTypeRequiredDescription
x-app-idstringRequiredYour app ID

Standard WebAuthn PublicKeyCredentialCreationOptions JSON (challenge, rp, user, pubKeyCredParams, timeout, …) plus sdkChallengeToken. Remove sdkChallengeToken before handing the rest to navigator.credentials.create(), and keep it for the verify call. HTTP 401 when the setup token is invalid or expired.

Request example

curl --request GET 'https://api.thebridge.dev/auth/auth/passkeys/registration-options?passkeySetupToken=PASSKEY_SETUP_TOKEN' \
--header 'x-app-id: YOUR_APP_ID' \
--header 'Origin: https://your-app.example.com'

Response example:

{
    "challenge": "y5PDGD3PD9EBhgGGDf-pTP...",
    "rp": {
        "name": "Nblocks",
        "id": "your-app.example.com"
    },
    "user": {
        "id": "63d25a9e0796d40008680f99",
        "name": "john@example.com",
        "displayName": "john@example.com"
    },
    "pubKeyCredParams": [
        { "alg": -7, "type": "public-key" },
        { "alg": -257, "type": "public-key" }
    ],
    "timeout": 60000,
    "attestation": "none",
    "excludeCredentials": [],
    "sdkChallengeToken": "XXXX"
}

Submit the credential produced by navigator.credentials.create(). The body is the serialized WebAuthn registration response spread at the top level, with the SDK fields added next to it.

POST https://api.thebridge.dev/auth/auth/passkeys/verify-registration?passkeySetupToken=PASSKEY_SETUP_TOKEN

Query Parameters

ParameterTypeRequiredDescription
passkeySetupTokenstringRequiredThe same token used for the options call

Body Parameters

ParameterTypeRequiredDescription
idstringRequiredFrom the WebAuthn credential JSON
rawIdstringRequiredFrom the WebAuthn credential JSON (base64url)
typestringRequiredpublic-key
responseobjectRequiredThe attestation: { clientDataJSON, attestationObject, transports }
clientExtensionResultsobjectOptionalFrom the WebAuthn credential JSON
appIdstringRequiredYour app ID. Its presence is what selects SDK mode here
sdkChallengeTokenstringRequiredEchoed from the options response
sdkOriginstringRequiredYour app's origin exactly as the browser sends it, e.g. https://your-app.example.com

{ "verified": true }. The device is now registered and shows up as hasPasskeys: true in credentials config. HTTP 401 when the challenge, setup token, or attestation does not verify.

Registration only stores the device. To log the user in afterwards, run the passkey login ceremony below.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/passkeys/verify-registration?passkeySetupToken=PASSKEY_SETUP_TOKEN' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "id": "CREDENTIAL_ID",
  "rawId": "CREDENTIAL_ID",
  "type": "public-key",
  "response": {
    "clientDataJSON": "BASE64URL",
    "attestationObject": "BASE64URL",
    "transports": ["internal"]
  },
  "clientExtensionResults": {},
  "appId": "YOUR_APP_ID",
  "sdkChallengeToken": "XXXX",
  "sdkOrigin": "https://your-app.example.com"
}'

Response example:

{
    "verified": true
}

Generate the WebAuthn request options for a passkey login. Identify your app with the x-app-id header; that switches the endpoint into SDK mode and makes it return sdkChallengeToken in the body. No user identifier is needed: allowCredentials is empty, so the browser offers whatever passkeys it holds for this site.

GET https://api.thebridge.dev/auth/auth/passkeys/authentication-options

Headers

ParameterTypeRequiredDescription
x-app-idstringRequiredYour app ID

Standard WebAuthn PublicKeyCredentialRequestOptions JSON (challenge, rpId, timeout, userVerification, allowCredentials) plus sdkChallengeToken. Remove sdkChallengeToken before handing the rest to navigator.credentials.get(), and keep it for the verify call.

Request example

curl --request GET 'https://api.thebridge.dev/auth/auth/passkeys/authentication-options' \
--header 'x-app-id: YOUR_APP_ID' \
--header 'Origin: https://your-app.example.com'

Response example:

{
    "challenge": "meWpAY-x02Yz3PtRAKQXH1...",
    "timeout": 60000,
    "rpId": "your-app.example.com",
    "userVerification": "preferred",
    "allowCredentials": [],
    "sdkChallengeToken": "XXXX"
}

Submit the assertion produced by navigator.credentials.get(). On success you get the same login session shape as a password login; continue with MFA (if required) and the token exchange.

POST https://api.thebridge.dev/auth/auth/passkeys/verify-authentication

Body Parameters

ParameterTypeRequiredDescription
idstringRequiredFrom the WebAuthn assertion JSON
rawIdstringRequiredFrom the WebAuthn assertion JSON (base64url). Identifies the device
typestringRequiredpublic-key
responseobjectRequiredThe assertion: { clientDataJSON, authenticatorData, signature, userHandle }
clientExtensionResultsobjectOptionalFrom the WebAuthn assertion JSON
modestringRequiredSet to sdk
appIdstringRequiredYour app ID
sdkChallengeTokenstringRequiredEchoed from the options response
sdkOriginstringRequiredYour app's origin exactly as the browser sends it

| Field | Type | Description | |---|---|---| | session | string | The login session JWT. Carry it to the next step | | expires | number | Session expiry hint | | mfaState | string | DISABLED, REQUIRED, or SETUP | | tenantUsers | array | The user’s workspaces: { id, username, fullName, tenant: { id, name, logo } } |

HTTP 401 when the signature or challenge does not verify, or when no passkey is registered for this device in this environment (error code NBLOCKS_PASSKEY_NOT_REGISTERED; offer another login method and passkey setup).

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/passkeys/verify-authentication' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "id": "CREDENTIAL_ID",
  "rawId": "CREDENTIAL_ID",
  "type": "public-key",
  "response": {
    "clientDataJSON": "BASE64URL",
    "authenticatorData": "BASE64URL",
    "signature": "BASE64URL",
    "userHandle": "BASE64URL"
  },
  "clientExtensionResults": {},
  "mode": "sdk",
  "appId": "YOUR_APP_ID",
  "sdkChallengeToken": "XXXX",
  "sdkOrigin": "https://your-app.example.com"
}'

Response example:

{
    "session": "XXXX",
    "expires": 604800,
    "mfaState": "DISABLED",
    "tenantUsers": [
        {
            "id": "63d25a9e0796d40008680f9a",
            "username": "john@example.com",
            "fullName": "John Doe",
            "tenant": {
                "id": "63d25a9e0796d40008680f96",
                "name": "Johns Family",
                "logo": ""
            }
        }
    ]
}

When a login returns mfaState: "SETUP", MFA is enforced for your app but the user has no phone number enrolled yet. The token exchange will refuse the session until MFA is completed, so enroll a phone first:

  1. POST /auth/auth/startMfaUserSetup with the phone number and the session. The Bridge texts a 6-digit code to that phone and returns a new session carrying the pending code. Use the new session from here on.
  2. POST /auth/auth/finishMfaUserSetup with the code the user typed. The response contains a one-time backupCode (the recovery code) and a session with mfaState: "COMPLETED".
  3. Show backupCode to the user once and tell them to store it safely; it is their only way back in if they lose the phone. It is not returned again.
  4. POST /auth/token/direct with the latest session.

Every MFA endpoint returns a fresh session. Always replace the one you stored with the one from the latest response; the old one no longer carries the right MFA state.

Enroll a phone number: The Bridge texts a 6-digit verification code to it. Only valid while the session’s mfaState is SETUP.

POST https://api.thebridge.dev/auth/auth/startMfaUserSetup

Body Parameters

ParameterTypeRequiredDescription
phoneNumberstringRequiredThe phone number to enroll, in international format, e.g. +46700000000
sessionstringRequiredThe session JWT from the login step
appIdstringRequiredYour app ID
modestringRequiredSet to sdk

{ session, expires, mfaState: "SETUP" }. The returned session embeds the pending code; pass it to the finish call. HTTP 401 when the session is invalid.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/startMfaUserSetup' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "phoneNumber": "+46700000000",
  "session": "SESSION_JWT",
  "appId": "YOUR_APP_ID",
  "mode": "sdk"
}'

Response example:

{
    "session": "XXXX",
    "expires": 604800,
    "mfaState": "SETUP"
}

Verify the texted code and complete the enrollment. Use the session returned by the start call.

POST https://api.thebridge.dev/auth/auth/finishMfaUserSetup

Body Parameters

ParameterTypeRequiredDescription
mfaCodestringRequiredThe 6-digit code the user received by SMS
sessionstringRequiredThe session JWT returned by startMfaUserSetup
appIdstringRequiredYour app ID
modestringRequiredSet to sdk

{ backupCode, session, expires, mfaState: "COMPLETED" }. HTTP 401 when the code is wrong (error code NBLOCKS_INVALID_MFA_CODE) or the session is invalid.

backupCode is the user’s one-time recovery code. Display it once, prompt the user to store it, and never log it. It is what resetUserMfaSetup asks for when the phone is lost.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/finishMfaUserSetup' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "mfaCode": "123456",
  "session": "SESSION_JWT",
  "appId": "YOUR_APP_ID",
  "mode": "sdk"
}'

Response example:

{
    "backupCode": "XXXX",
    "session": "XXXX",
    "expires": 604800,
    "mfaState": "COMPLETED"
}

When a login returns mfaState: "REQUIRED", a 6-digit code has already been texted to the user’s enrolled phone as part of the credential step. The sequence:

  1. Ask the user for the code.
  2. POST /auth/auth/commitMfaCode with the code and the session. You get a fresh session with mfaState: "COMPLETED".
  3. POST /auth/token/direct with that session.

Two side paths:

  • The text never arrived. POST /auth/auth/resendMfaCode sends a new code and returns a fresh session tied to it; use that session for the commit call. The old code stops working.
  • The phone is lost. POST /auth/auth/resetUserMfaSetup with the user’s recovery code wipes the enrollment and returns a session with mfaState: "SETUP"; run MFA setup again with the new phone number.

Verify the texted code and mark the session’s MFA step as passed. Only valid while the session’s mfaState is REQUIRED.

POST https://api.thebridge.dev/auth/auth/commitMfaCode

Body Parameters

ParameterTypeRequiredDescription
mfaCodestringRequiredThe 6-digit code the user received by SMS
sessionstringRequiredThe session JWT from the login step (or from resendMfaCode)
appIdstringRequiredYour app ID
modestringRequiredSet to sdk

{ session, expires, mfaState: "COMPLETED" }. Use the returned session for the token exchange. HTTP 401 when the code is wrong (error code NBLOCKS_INVALID_MFA_CODE) or the session is invalid.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/commitMfaCode' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "mfaCode": "123456",
  "session": "SESSION_JWT",
  "appId": "YOUR_APP_ID",
  "mode": "sdk"
}'

Response example:

{
    "session": "XXXX",
    "expires": 604800,
    "mfaState": "COMPLETED"
}

Text a new code to the enrolled phone. Only valid while the session’s mfaState is REQUIRED.

POST https://api.thebridge.dev/auth/auth/resendMfaCode

Body Parameters

ParameterTypeRequiredDescription
sessionstringRequiredThe session JWT from the login step
appIdstringRequiredYour app ID
modestringRequiredSet to sdk

{ session, expires, mfaState: "REQUIRED" }. The returned session is tied to the new code; the previous code no longer validates. HTTP 401 when the session is invalid.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/resendMfaCode' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "session": "SESSION_JWT",
  "appId": "YOUR_APP_ID",
  "mode": "sdk"
}'

Response example:

{
    "session": "XXXX",
    "expires": 604800,
    "mfaState": "REQUIRED"
}

For users who lost their phone. Submits the one-time backupCode from enrollment, wipes the old phone number, and drops the session back to mfaState: "SETUP" so the user can enroll a new one. Only valid while the session’s mfaState is REQUIRED.

POST https://api.thebridge.dev/auth/auth/resetUserMfaSetup

Body Parameters

ParameterTypeRequiredDescription
backupCodestringRequiredThe recovery code handed out by finishMfaUserSetup
sessionstringRequiredThe session JWT from the login step
appIdstringRequiredYour app ID
modestringRequiredSet to sdk

{ session, expires, mfaState: "SETUP" }. Continue with MFA setup using the returned session; finishing it issues a new recovery code. HTTP 401 when the recovery code or session is invalid.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/resetUserMfaSetup' \
--header 'Content-Type: application/json' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "backupCode": "RECOVERY_CODE",
  "session": "SESSION_JWT",
  "appId": "YOUR_APP_ID",
  "mode": "sdk"
}'

Response example:

{
    "session": "XXXX",
    "expires": 604800,
    "mfaState": "SETUP"
}

The classic email round-trip. The sequence:

  1. POST /auth/auth/password with the user’s email. The Bridge emails a reset link.
  2. The link lands on /auth/set-password/RESET_TOKEN?flow=forgot on your origin (when the request Origin is one of your allowed origins, otherwise on the hosted login). Serve that route in your app and read the token from the path.
  3. (Optional) GET /auth/auth/password/token/RESET_TOKEN to check the token is still valid before rendering the form.
  4. PUT /auth/auth/password with the token and the new password. The token is destroyed on use.
  5. The user logs in again through any login flow.

Unlike the rest of this page, the password endpoints identify your app with the x-app-id header and have no mode field. The Origin header is still checked against your allowed origins.

Send the reset email.

POST https://api.thebridge.dev/auth/auth/password

Headers

ParameterTypeRequiredDescription
x-app-idstringRequiredYour app ID

Body Parameters

ParameterTypeRequiredDescription
usernamestringRequiredThe user's email address

Empty body. Unknown email addresses also get HTTP 200 (no email is sent), so the endpoint cannot be used to discover which emails have accounts.

Request example

curl --request POST 'https://api.thebridge.dev/auth/auth/password' \
--header 'Content-Type: application/json' \
--header 'x-app-id: YOUR_APP_ID' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "username": "john@example.com"
}'

Response example:

HTTP 200 with an empty body.


Check a reset token before showing the new-password form, so expired links get a friendly error instead of a failed submit.

GET https://api.thebridge.dev/auth/auth/password/token/RESET_TOKEN

Path Parameters

ParameterTypeRequiredDescription
tokenstringRequiredThe reset token from the emailed link

{ "appId": "...", "valid": true }. HTTP 404 when the token is unknown or expired; HTTP 403 when the Origin is not allowed for the token’s app.

Request example

curl --request GET 'https://api.thebridge.dev/auth/auth/password/token/RESET_TOKEN' \
--header 'Origin: https://your-app.example.com'

Response example:

{
    "appId": "YOUR_APP_ID",
    "valid": true
}

Commit the new password using the reset token. The token is single-use: it is destroyed on success.

PUT https://api.thebridge.dev/auth/auth/password

Headers

ParameterTypeRequiredDescription
x-app-idstringRequiredYour app ID

Body Parameters

ParameterTypeRequiredDescription
tokenstringRequiredThe reset token from the emailed link
passwordstringRequiredThe new password

Empty body. HTTP 401 when the token is invalid, expired, or already used. There is no session in the response; send the user to a login flow.

The same emailed reset token also works as forgotPasswordToken on the passkey registration endpoints, so your set-password page can offer “register a passkey instead” with the same token.

Request example

curl --request PUT 'https://api.thebridge.dev/auth/auth/password' \
--header 'Content-Type: application/json' \
--header 'x-app-id: YOUR_APP_ID' \
--header 'Origin: https://your-app.example.com' \
--data-raw '{
  "token": "RESET_TOKEN",
  "password": "NEW_SECRET"
}'

Response example:

HTTP 200 with an empty body.