Skip to content
Coritan Docs

Organization API: Customer Authentication: Auth

The 32 Organization API operations for auth.

View as Markdown

Part of Customer Authentication.

Method Path Summary
POST /api/v1/orgs/{org_slug}/auth/checkout-session Give a visitor at checkout a customer session from their email address
POST /api/v1/orgs/{org_slug}/auth/code/request Email the customer a 6-digit code to sign in with
POST /api/v1/orgs/{org_slug}/auth/code/verify Sign in with the code from the email
POST /api/v1/orgs/{org_slug}/auth/forgot-password Email a link that sets a new password, to the address of an active account
POST /api/v1/orgs/{org_slug}/auth/login Password sign-in with an email address or a username
POST /api/v1/orgs/{org_slug}/auth/logout Revoke the current access token (and its refresh twin if present)
GET /api/v1/orgs/{org_slug}/auth/me The signed-in customer
PATCH /api/v1/orgs/{org_slug}/auth/me Update customer profile
POST /api/v1/orgs/{org_slug}/auth/me/new-account-notice/dismiss Hide the "this is a new account" notice for good
GET /api/v1/orgs/{org_slug}/auth/mfa Customer MFA status
POST /api/v1/orgs/{org_slug}/auth/mfa/disable Turn the factor off with a current code, and with the password when the account has one
POST /api/v1/orgs/{org_slug}/auth/mfa/enable Customer MFA enable
POST /api/v1/orgs/{org_slug}/auth/mfa/recovery-codes Customer MFA regenerate recovery codes
POST /api/v1/orgs/{org_slug}/auth/mfa/setup Customer MFA setup
POST /api/v1/orgs/{org_slug}/auth/mfa/verify Customer MFA verify
GET /api/v1/orgs/{org_slug}/auth/oauth/connections List OAuth connections
DELETE /api/v1/orgs/{org_slug}/auth/oauth/connections/{provider} Disconnect OAuth provider
GET /api/v1/orgs/{org_slug}/auth/oauth/providers Public: which social providers the storefront offers
POST /api/v1/orgs/{org_slug}/auth/oauth/set-password OAuth set password
GET /api/v1/orgs/{org_slug}/auth/oauth/{provider}/authorize OAuth authorize
GET /api/v1/orgs/{org_slug}/auth/oauth/{provider}/callback Finish a provider sign-in and send the customer back to the storefront
POST /api/v1/orgs/{org_slug}/auth/reauth/code Request reauth code
POST /api/v1/orgs/{org_slug}/auth/reauth/code/verify Trade the code from POST /auth/reauth/code for a reauthtoken
POST /api/v1/orgs/{org_slug}/auth/refresh Customer refresh token
POST /api/v1/orgs/{org_slug}/auth/register Self-service customer signup for the organization's storefront
POST /api/v1/orgs/{org_slug}/auth/resend-verification Send another confirmation link to the signed-in customer's own address
POST /api/v1/orgs/{org_slug}/auth/reset-password Set a new password with the token from a password reset email
POST /api/v1/orgs/{org_slug}/auth/reset-password/resend Email a new password link to the customer a link was made for
GET /api/v1/orgs/{org_slug}/auth/sessions Where this account is currently signed in
POST /api/v1/orgs/{org_slug}/auth/sessions/revoke-others Sign out everywhere except here
GET /api/v1/orgs/{org_slug}/auth/username-available Whether a username can be taken at signup, and why not when it cannot
POST /api/v1/orgs/{org_slug}/auth/verify-email Confirm an address from an emailed link

Give a visitor at checkout a customer session from their email address

Section titled Give a visitor at checkout a customer session from their email address

POST /api/v1/orgs/{org_slug}/auth/checkout-session

Give a visitor at checkout a customer session from their email address.

  • An address with no account and verify: false: we make the account with no password and the details sent, and answer 201 {"status": "created", access_token, refresh_token, expires_in, customer}. We email a link to choose a password (account_set_password, good for 3 days). When your organization requires two-factor authentication, the answer holds the set-up step (mfa_token with mfa_setup_required) instead of a session.
  • An address with no account and verify: true: we email a code and answer 200 {"status": "code_sent", challenge, email_hint, expires_in, resend_after, new_account: true}. POST /auth/code/verify then makes the account and signs the customer in.
  • An address that has an account: we email a code to sign in and answer 409 with detail {"error": "account_exists", "message", challenge, email_hint, has_password, expires_in, resend_after}, and never a session. The customer gives the code to POST /auth/code/verify, or their password to POST /auth/login.

The names, company, country and address are saved only on an account this request makes; otherwise save them after sign-in. The first proof of the address (a code, or the password link) ends the session this request returned.

Refusals as for POST /auth/code/request, and 429 when one network made 20 accounts here in the last hour.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required Description
email string yes The customer's email address.
first_name string or null no Saved on an account this request makes.
last_name string or null no Saved on an account this request makes.
company string or null no Saved on an account this request makes.
country_code string or null no The billing country as ISO 3166-1 alpha-2, such as DE. Saved on an account this request makes.
address BillingAddressBody or null no The billing address. Saved on an account this request makes.
address.line1 string or null no Street and number.
address.line2 string or null no Flat, suite or building.
address.city string or null no City or town.
address.postal_code string or null no Postal or ZIP code.
address.state string or null no State, province or region.
turnstile_token string or null no The Cloudflare Turnstile answer, needed when GET /storefront/branding reports turnstile.enabled.
verify boolean no true to prove the address with a code before an account is made, as a free server needs. false makes the account at once.
Status Meaning
201 Success.
422 The request is not valid. detail lists each problem.

Email the customer a 6-digit code to sign in with

Section titled Email the customer a 6-digit code to sign in with

POST /api/v1/orgs/{org_slug}/auth/code/request

Email the customer a 6-digit code to sign in with.

identifier is an email address or a Minecraft username. An address with an account gets a code to sign in to it. An address with no account gets one too, and new_account is true: giving the code makes the account. Both answers show where the code went in email_hint.

A name finds the active account that first bought for that player or named it in POST /portal/players (a later customer who uses the same name does not take it over), and then the account whose own username it is; the code goes to that account's address. The answer for a name is the same whether or not an account uses it: email_hint is null, new_account is false, and challenge is always present. For a name no account uses, no email goes out and no code can open the challenge. Names count against the same limits as addresses, by name and by network.

Send challenge and the code to POST /auth/code/verify. A code works for 10 minutes and 5 tries. An address can ask for another after 30 seconds and gets at most 5 codes in 15 minutes, and one network at most 20 an hour.

Refusals: 400 for an identifier with @ that is not an address; 403 as sign-up refuses a new address (a blocked email domain, the customer limit) and for an account that is banned, suspended or closed; 403 turnstile_failed; 429 with Retry-After while the address or the network must wait; 503 when we cannot send your organization's email.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required Description
identifier string yes The customer's email address, or a Minecraft username as the game shows it, with the prefix the network gives its edition when it has one.
turnstile_token string or null no The Cloudflare Turnstile answer, needed when GET /storefront/branding reports turnstile.enabled.
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Fields of a 200 response:

Field Type Description
sent boolean Always true for an address. For a name it is true whether or not a code went out.
challenge string Send this back with the code to POST /auth/code/verify.
email_hint string or null Where the code went, with most of the address hidden, such as s•••@example.com. null when the identifier was a player name or a username: the answer must not say whether an account uses that name.
new_account boolean true when no account has this address yet: giving the code makes one. Always false for a name.
expires_in integer Seconds the code works for: 600.
resend_after integer Seconds before another code can be asked for: 30.

Sign in with the code from the email

Section titled Sign in with the code from the email

POST /api/v1/orgs/{org_slug}/auth/code/verify

Sign in with the code from the email.

The answer is the one POST /auth/login gives: a session (access_token, refresh_token, expires_in, scopes), or the second-factor step (mfa_token with mfa_required, or mfa_setup_required when your organization requires two-factor authentication and the customer has not set it up).

The code proves the address. When no account has it, we make one with no password, send your welcome email, and your webhooks receive customer.created. The first time an account's address is proven, every session made before ends, such as one a checkout made for an address its owner had not proven yet. On an account with no password, the same step removes an authenticator, which only such a session can have added, and applies invites to share a server with the address.

Refusals: 400 with detail.error wrong_code, expired or used, a message to show and attempts_left. The fifth wrong code spends the challenge. 403 as for POST /auth/code/request.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required Description
challenge string yes The challenge from the answer that sent the code.
code string yes The 6 digits from the email. Spaces and dashes are ignored.
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Email a link that sets a new password, to the address of an active account

Section titled Email a link that sets a new password, to the address of an active account

POST /api/v1/orgs/{org_slug}/auth/forgot-password

Email a link that sets a new password, to the address of an active account.

The answer is always 200 {"message": "If the account exists, we issued a reset link"}, whether or not the address has an account, and the link works for 2 hours. Only the newest forgot-password link works. It never replaces a link we made on our own, such as the set-password button of an account move, so a stranger who types an address cannot void the link its owner was sent.

429 when one network has asked 10 times in an hour. One address gets 3 links an hour: later requests for it answer the same 200 and send nothing. Both counts include addresses that have no account, so neither says whether an address has one.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required
email string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Password sign-in with an email address or a username

Section titled Password sign-in with an email address or a username

POST /api/v1/orgs/{org_slug}/auth/login

Password sign-in with an email address or a username.

Send identifier (or email or username) and password. We answer 200 with a session (access_token and refresh_token), or with the second-factor step (mfa_token) when the account or the organization asks for one.

A name that is no customer's username may be a Minecraft player name: it signs in the active customer who first bought for that player or named it in POST /portal/players. A later customer who uses the same name does not take it over.

The password is checked by check_password: a hash imported from a WordPress store ($wp$, phpass $P$ or $H$, plain MD5) opens the account like a bcrypt one, and is replaced by a bcrypt hash of the same password in this sign-in's transaction.

Every refusal has detail, a sentence to show, and code, a word to act on:

  • 401 invalid_credentials: the login or the password is wrong.
  • 401 no_password: the account has no password yet. POST /auth/forgot-password emails the link that sets one, and a storefront with a code form (POST /auth/code/request) can offer that as well.
  • 429 rate_limited, with Retry-After: 300: the old site asked the person to wait before another try.
  • 401 legacy_unavailable: the organization checks old accounts at sign-in, and the old site did not answer. The password may still be right; try again in a minute.

While the organization moves accounts from its previous website and asks that site at sign-in, a login this organization does not know, or a password that fails here, is checked there too. A password longer than 256 characters is never sent. A password that opens an old account answers 200 with legacy instead of 401: action is move with a ticket for the move page, or old_site with a url on the old site's own domain that signs the person in there, because that account still has servers there. An old username of an account that already moved to this organization signs in to that account here, with its password.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required
identifier string or null no
email string or null no
username string or null no
password string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Revoke the current access token (and its refresh twin if present)

Section titled Revoke the current access token (and its refresh twin if present)

POST /api/v1/orgs/{org_slug}/auth/logout

Revoke the current access token (and its refresh twin if present).

Name In Type Required
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

GET /api/v1/orgs/{org_slug}/auth/me

The signed-in customer.

support_session is set when the token is a support session that your team or Coritan support opened as the customer: its mode (read_only, servers or full) and expires_at. A storefront that receives a token at /auth/callback with impersonated=1 reads this to confirm it is a support session before it signs the visitor in, and shows whose account is on screen until the session ends.

Name In Type Required
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Fields of a 200 response:

Field Type Description
id integer
org_id integer
email string
first_name string or null
last_name string or null
chat_handle string or null
chat_handle_locked_for integer
company string or null
phone string or null
status string
credit_balance string
currency string
country_code string or null
currency_source string
custom_fields object or null
last_login_at string (date-time) or null
created_at string (date-time) or null
updated_at string (date-time) or null
has_password boolean
staff_member boolean
support_session SupportSessionInfo or null Set when the token is a support session the organization's team or Coritan support opened as this customer (read_only, servers or full), null for the customer's own sign-in. A storefront that receives a support session shows whose account it is and that the session ends.
support_session.mode string read_only, servers (an organization's customer only) or full.
support_session.expires_at string (date-time) or null When the session ends. It cannot be renewed.
new_account_notice object or null Set while an account that a Google or Discord sign-in created has no services and the customer has not dismissed it: {provider} is how this account was created. The storefront says this is a new account and that servers bought before are on the account they used then. POST /{org_slug}/auth/me/new-account-notice/dismiss clears it.
email_verified boolean
avatar_url string or null
marketing_consent boolean Whether the customer agreed to marketing email. Mail about their own orders and services needs no agreement.
marketing_consent_at string (date-time) or null When the customer gave that answer.
marketing_consent_source string or null Where the answer came from: signup, account, newsletter, staff or unsubscribe.
credit_currency string What credit_balance is held in: the wallet's currency (US dollars), whatever currency the customer is billed in.

PATCH /api/v1/orgs/{org_slug}/auth/me

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required Description
first_name string or null no
last_name string or null no
chat_handle string or null no
company string or null no
phone string or null no
password string or null no
email string or null no
current_password string or null no
marketing_consent boolean or null no The customer's own choice about marketing email, stored with the time and the source account. true also takes their address off the organization's suppression list. Left out, the choice stays as it is.
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Fields of a 200 response:

Field Type Description
id integer
org_id integer
email string
first_name string or null
last_name string or null
chat_handle string or null
chat_handle_locked_for integer
company string or null
phone string or null
status string
credit_balance string
currency string
country_code string or null
currency_source string
custom_fields object or null
last_login_at string (date-time) or null
created_at string (date-time) or null
updated_at string (date-time) or null
has_password boolean
staff_member boolean
support_session SupportSessionInfo or null Set when the token is a support session the organization's team or Coritan support opened as this customer (read_only, servers or full), null for the customer's own sign-in. A storefront that receives a support session shows whose account it is and that the session ends.
support_session.mode string read_only, servers (an organization's customer only) or full.
support_session.expires_at string (date-time) or null When the session ends. It cannot be renewed.
new_account_notice object or null Set while an account that a Google or Discord sign-in created has no services and the customer has not dismissed it: {provider} is how this account was created. The storefront says this is a new account and that servers bought before are on the account they used then. POST /{org_slug}/auth/me/new-account-notice/dismiss clears it.
email_verified boolean
avatar_url string or null
marketing_consent boolean Whether the customer agreed to marketing email. Mail about their own orders and services needs no agreement.
marketing_consent_at string (date-time) or null When the customer gave that answer.
marketing_consent_source string or null Where the answer came from: signup, account, newsletter, staff or unsubscribe.
credit_currency string What credit_balance is held in: the wallet's currency (US dollars), whatever currency the customer is billed in.

Hide the "this is a new account" notice for good

Section titled Hide the "this is a new account" notice for good

POST /api/v1/orgs/{org_slug}/auth/me/new-account-notice/dismiss

Hide the "this is a new account" notice for good. Refused in a support session: it is the customer's to dismiss.

Name In Type Required
org_slug path string yes
Status Meaning
204 Success.
422 The request is not valid. detail lists each problem.

GET /api/v1/orgs/{org_slug}/auth/mfa

Name In Type Required
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Turn the factor off with a current code, and with the password when the account has one

Section titled Turn the factor off with a current code, and with the password when the account has one

POST /api/v1/orgs/{org_slug}/auth/mfa/disable

Turn the factor off with a current code, and with the password when the account has one. Refused while the brand requires a factor of everyone.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required
password string or null no
code string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

POST /api/v1/orgs/{org_slug}/auth/mfa/enable

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required
code string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Customer MFA regenerate recovery codes

Section titled Customer MFA regenerate recovery codes

POST /api/v1/orgs/{org_slug}/auth/mfa/recovery-codes

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required
code string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

POST /api/v1/orgs/{org_slug}/auth/mfa/setup

Name In Type Required
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

POST /api/v1/orgs/{org_slug}/auth/mfa/verify

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required
code string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

GET /api/v1/orgs/{org_slug}/auth/oauth/connections

Name In Type Required
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

A 200 response is a list; each item has these fields:

Field Type
[].provider string
[].provider_username string or null
[].provider_email string or null
[].provider_avatar string or null
[].created_at string (date-time) or null
[].account_created_at string (date-time) or null

DELETE /api/v1/orgs/{org_slug}/auth/oauth/connections/{provider}

Name In Type Required
provider path string yes
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Public: which social providers the storefront offers

Section titled Public: which social providers the storefront offers

GET /api/v1/orgs/{org_slug}/auth/oauth/providers

Public: which social providers the storefront offers.

A provider is listed when Coritan has it configured and the organization's settings do not leave it out.

Name In Type Required
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Fields of a 200 response:

Field Type
providers array of string

POST /api/v1/orgs/{org_slug}/auth/oauth/set-password

Set a first password on an account that has none, such as one made with a social account, a code or at checkout.

Needs the signed-in customer's token. The address must be proven first (email_verified in GET /auth/me): a session can exist for an address its owner has not confirmed, such as the one POST /auth/checkout-session returns, and a password chosen there would still open the account after the owner confirmed the address. 403 with detail.error email_unproven and a message to show says so. POST /auth/forgot-password emails a link that proves the address and sets the password in one step. 400 when the account already has a password, 422 for a password that is too weak. The answer is {"message": "Password set successfully"}.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required Description
password string yes The new password: 8 to 128 characters with a lowercase letter, an uppercase letter, a digit and a symbol.
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

GET /api/v1/orgs/{org_slug}/auth/oauth/{provider}/authorize

Name In Type Required
org_slug path string yes
provider path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Finish a provider sign-in and send the customer back to the storefront

Section titled Finish a provider sign-in and send the customer back to the storefront

GET /api/v1/orgs/{org_slug}/auth/oauth/{provider}/callback

Finish a provider sign-in and send the customer back to the storefront.

The provider's redirect lands here with a code. We answer 302 to the storefront's /auth/callback page with the result in the URL fragment: access_token, refresh_token and expires_in, or mfa_token and its flags, or error. new_account=true marks an account this sign-in made, and a link a signed-in customer started ends with linked=true and provider.

A provider only vouches for an address it says it checked. We make an account from a sign-in only when it did, and we never link a provider to an account the customer did not sign in to. These error values say what to tell the customer:

  • account_exists_login_required: an account with this address has a password. The customer signs in with it, then connects the provider in their account settings.
  • account_exists_no_password: an account with this address has no password. The customer asks for a sign-in code or a password link by email, then connects the provider in their account settings.
  • email_unverified_by_provider: the provider did not say it had checked the address, so we made no account. The customer confirms the address with the provider and tries again, or signs up with email.
  • no_email_from_provider: the provider shared no address.
  • email_unproven: a customer tried to connect a provider to an account whose address nobody has proven. They confirm the address by email first, with a sign-in code, the confirmation link or a password link.
  • provider_already_linked_to_another_account: another customer has linked this provider account.
  • discord_link_limit, with retry_at: this Discord account moved between customers too often. retry_at is when it can be linked again.
  • account_disabled, email_domain_blocked and customer_limit_reached: the account is not active, the address's domain is blocked, or the organization is at its customer limit.
  • access_denied, invalid_state, missing_code_or_state, token_exchange_failed, no_access_token, profile_fetch_failed, provider_not_available and the rest: the sign-in did not finish, and the customer starts it again.
Name In Type Required
org_slug path string yes
provider path string yes
code query string or null no
state query string or null no
error query string or null no
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

POST /api/v1/orgs/{org_slug}/auth/reauth/code

Email the signed-in customer a 6-digit code that confirms an action that cannot be undone, such as deleting a server or closing the account.

Needs the customer's own session. The code always goes to the address on the account, so it works for an account with no password and no linked social account, and the body is empty ({}). Send challenge and the code to POST /auth/reauth/code/verify, which answers with a reauth_token that the request needing it accepts as reauth_token.

A code works for 10 minutes and 5 tries. It counts against the same limits as a sign-in code, because both go to the same inbox: the address can ask for another after 30 seconds and gets at most 5 codes in 15 minutes, and one network at most 20 an hour.

Refusals: 401 without a session; 403 for a support session opened as the customer, and for an account that is banned, suspended or closed; 429 with Retry-After while the address or the network must wait; 503 when we cannot send your organization's email.

Name In Type Required
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Fields of a 200 response:

Field Type Description
sent boolean Always true: we emailed a code to the account's own address.
challenge string Send this back with the code to POST /auth/reauth/code/verify.
email_hint string Where the code went, with most of the address hidden, such as s•••@example.com.
expires_in integer Seconds the code works for: 600.
resend_after integer Seconds before another code can be asked for: 30.

Trade the code from POST /auth/reauth/code for a reauthtoken

Section titled Trade the code from POST /auth/reauth/code for a reauthtoken

POST /api/v1/orgs/{org_slug}/auth/reauth/code/verify

Trade the code from POST /auth/reauth/code for a reauth_token.

Needs the same customer's session. The answer is {"reauth_token", "expires_in": 300}: send the token as reauth_token with the request that needs a confirmation, within 5 minutes. A code works once.

Refusals: 400 with detail.error wrong_code, expired or used, a message to show and attempts_left. The fifth wrong code spends the challenge. A code that was sent for signing in, or to another customer, reads as expired, and so does a challenge your organization did not issue. 401 without a session; 403 for a support session; 429 with Retry-After after many wrong codes from one network.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required Description
challenge string yes The challenge from the answer that sent the code.
code string yes The 6 digits from the email. Spaces and dashes are ignored.
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Fields of a 200 response:

Field Type Description
reauth_token string Proof that the customer just confirmed it is them. Send it as reauth_token with the request that needs it, such as POST /portal/account/close.
expires_in integer Seconds the token works for: 300.

POST /api/v1/orgs/{org_slug}/auth/refresh

Name In Type Required
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Fields of a 200 response:

Field Type
access_token string
refresh_token string
token_type string
expires_in integer
scopes array of string or null
customer_id integer or null

Self-service customer signup for the organization's storefront

Section titled Self-service customer signup for the organization's storefront

POST /api/v1/orgs/{org_slug}/auth/register

Self-service customer signup for the organization's storefront.

Send email and password; username, the names, company, phone, turnstile_token and marketing_consent are optional. We answer 201 with a session (access_token and refresh_token), or with the second-factor step when the organization asks every customer for one. The address starts unconfirmed: nothing waits for the confirmation link, and we email it.

Refusals carry a code beside the detail sentence: 409 email_taken when an account here has the address, 409 username_taken for a name that is in use, 403 email_banned for an address that cannot register again, and 403 customer_limit_reached when the organization is full. A sentence in detail stays the same for each, so older pages keep working.

While the organization moves accounts from its previous website and asks that site at sign-in, we also ask it whether the address or the username belongs to an account there. When it does, we answer 409 account_on_old_site: the person should sign in with the old username and password, and we move the account for them. When the old site does not answer, the sign-up goes ahead.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required Description
email string yes
password string yes
username string or null no
first_name string or null no
last_name string or null no
company string or null no
phone string or null no
turnstile_token string or null no
marketing_consent boolean or null no Whether the customer agreed to marketing email. It is false unless the sign-up form sends true. An address on the organization's suppression list is still not mailed.
Status Meaning
201 Success.
422 The request is not valid. detail lists each problem.

Send another confirmation link to the signed-in customer's own address

Section titled Send another confirmation link to the signed-in customer's own address

POST /api/v1/orgs/{org_slug}/auth/resend-verification

Send another confirmation link to the signed-in customer's own address.

Authenticated rather than taking an email in the body: an anonymous version would let anyone post mail to any address on this org's behalf, and there is nothing to gain from it when the customer can already sign in.

Name In Type Required
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Set a new password with the token from a password reset email

Section titled Set a new password with the token from a password reset email

POST /api/v1/orgs/{org_slug}/auth/reset-password

Set a new password with the token from a password reset email.

The token comes from the forgot-password email (it works for 2 hours), from the email that asks a customer to choose a password for an account made at checkout (3 days), from the set-password button of an account moved from a previous site (30 days) or from the new link that POST /auth/reset-password/resend sends (7 days). It works once. The reset signs the customer out everywhere, and the answer is {"message": "Password updated", "email"} with the account's address, so the page can sign the customer in with the new password straight away.

A link that names the customer carries id beside token. Send both: the token is then checked on that customer alone. Without id the token is looked up among the organization's customers, which is how links sent before id existed still work; that lookup is limited to 10 requests an hour from one network (429).

400 Invalid reset token for a token that is wrong, was replaced by a newer one or was used already, and for an id that is unknown, belongs to another organization or is not an active customer: the answer is the same for all of them. A genuine token past its time answers 400 Reset token expired; when the request carried id the detail is {"code": "reset_link_expired", "message": "Reset token expired"}, and the page can offer POST /auth/reset-password/resend for a new link.

The link went to the account's address, so using it proves the address. When nobody had proven it before, we mark it confirmed, remove an authenticator from an account that had no password (only a session nobody had vouched for can have added one), remove the sign-in links to other services that an account move carried to it, and apply invites to share a server with the address.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required
id integer or null no
token string yes
password string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Email a new password link to the customer a link was made for

Section titled Email a new password link to the customer a link was made for

POST /api/v1/orgs/{org_slug}/auth/reset-password/resend

Email a new password link to the customer a link was made for.

Send the id and token from the link that does not work, expired or not. When that token is one we hold for an active customer, we issue a new link that works for 7 days, replace every older link of the customer with it (only the newest link works) and email it to the address on the account. The page offers this when POST /auth/reset-password answers reset_link_expired.

The answer is always 200 {"message": "If that link was ours, we sent a new one to the address on file."}, whether or not a link went out, so it says nothing about which customers exist. When the organization cannot send email, nothing is sent and the old link stays as it was.

429 when the network has asked 20 times in an hour, or when the customer has had 3 new links in an hour. The customer's budget counts only requests with the right token, so someone who knows an id cannot use it up.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required
id integer yes
token string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Where this account is currently signed in

Section titled Where this account is currently signed in

GET /api/v1/orgs/{org_slug}/auth/sessions

Where this account is currently signed in.

The token row already records the address and user agent it was issued to, so a customer can see a session they do not recognise without us keeping a second log of it. Tokens are never returned, only the hash we match the caller's own session on. country_code is where the address places (see _session_countries), or null when nothing can place it, so the page can say "Germany · 203.0.113.9" rather than an address alone.

Support sessions are labelled rather than hidden. An admin looking at an account shows up here as a sign-in from an address the customer will not recognise, and the honest answer to that is to say what it is.

Name In Type Required
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Sign out everywhere except here

Section titled Sign out everywhere except here

POST /api/v1/orgs/{org_slug}/auth/sessions/revoke-others

Sign out everywhere except here.

The one action worth having after a shared or stolen laptop, and it must not log the customer out of the browser asking for it, so the presented token is the one row left alone.

Name In Type Required
org_slug path string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Whether a username can be taken at signup, and why not when it cannot

Section titled Whether a username can be taken at signup, and why not when it cannot

GET /api/v1/orgs/{org_slug}/auth/username-available

Whether a username can be taken at signup, and why not when it cannot.

Public, because the signup form asks while somebody is still typing and has no session yet. It gives away nothing the community pages do not: a handle is the name everyone already sees above every message its owner posts. Answers rather than raising, so a 422 per keystroke is not the shape of it.

Name In Type Required
org_slug path string yes
username query string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.

Confirm an address from an emailed link

Section titled Confirm an address from an emailed link

POST /api/v1/orgs/{org_slug}/auth/verify-email

Confirm an address from an emailed link.

Public, because the customer may follow the link in a browser that is not signed in. The token is the credential. The first time an address is confirmed, every session the account had before ends, such as one a checkout handed to a stranger, and invites to share a server with the address now apply (settle_first_proof). Following the link again changes nothing. The answer is {"verified": true, "email"}; 400 for a link that is not valid, has expired or was sent to another address.

Name In Type Required
org_slug path string yes

application/json (required)

Field Type Required
customer_id integer yes
token string yes
Status Meaning
200 Success.
422 The request is not valid. detail lists each problem.