Appearance
Sign-up and login
Create an account, verify access, sign in, or recover a password. The authentication states page draws how these calls move a visitor between guest, unverified, signed in and banned.
Access labels such as Origin + Brand key + Customer are defined in Headers and access. Each endpoint ends in a Try it console; set the host and keys once in its Settings tab or on the overview.
Register a customer
Create a customer for the current brand or organization. Registration returns a customer ID; sign in separately to obtain a customer token.
| Method | URL path | Required access |
|---|---|---|
POST | /api/v1/register | Origin + Brand key |
Request fields:
| Field | Type | Required | Rules / meaning |
|---|---|---|---|
body.payload | object | yes | At least one of ("email" , "phoneNumber") |
body.payload.firstName | string | optional | Whitespace trimmed |
body.payload.lastName | string | optional | Whitespace trimmed |
body.payload.fullName | string | optional | Whitespace trimmed |
body.payload.password | string | optional | Whitespace trimmed; Minimum (8) |
body.payload.email | string | optional | Email address; Whitespace trimmed; Converted to lowercase; Custom validation in controller |
body.payload.phoneNumber | string | optional | replace(/\D/g , ""); Whitespace trimmed; Minimum (8) |
body.payload.phone_prefix | string | conditional | Required when phoneNumber is supplied; otherwise optional |
body.payload.dateOfBirth | date | optional | ISO date |
body.payload.tags | string | optional | Whitespace trimmed |
body.payload.source | string | optional | Whitespace trimmed; Allowed values ("organic", "scan", "referral", "promotion", "shopify_plugin", "other"); Default ("organic") |
body.payload.user_profile | string | optional | Whitespace trimmed |
body.payload.user_description | string | optional | Whitespace trimmed |
body.payload.username | string | optional | Whitespace trimmed |
body.payload.gender | string | optional | Whitespace trimmed |
query.medium | string | optional | Whitespace trimmed; Allowed values ("ios", "android", "website", "plugin", "other") |
query.device | string | optional | Whitespace trimmed; Allowed values ("mobile", "desktop", "ipad", "tablet", "other", "laptop") |
Supply email or phoneNumber; include phone_prefix when using phoneNumber. The controller rejects email addresses containing a plus sign. A password is optional at registration.
Example:
bash
curl -X POST "$BASE_URL/api/v1/register" \
-H "Origin: $ORIGIN" \
-H "api-key: $API_KEY" \
-H 'Content-Type: application/json' \
--data-raw '{"payload":{"email":"alex@example.com","password":"ExamplePass123!","firstName":"Alex","lastName":"Taylor"}}'Response: JSON with success: true and result containing the operation’s service data. Result fields/details: message, userId; phoneNumber on the phone-registration branch.
Source: app/v1/controller/customer_controller.js:395 → createCustomer; routes: routes/v1/customer_route.js:335; service: _register (app/v1/service/customer_service.js:873).
Sign in with a password or OTP
Sign in with an email or phone number, plus a password or OTP. The response contains the customer token used by protected APIs.
| Method | URL path | Required access |
|---|---|---|
POST | /api/v1/login | Origin + Brand key |
Request fields:
| Field | Type | Required | Rules / meaning |
|---|---|---|---|
body.payload | object | yes | At least one of ("email" , "phoneNumber"); At least one of ("otp" , "password") |
body.payload.email | string | optional | Email address; Whitespace trimmed; Converted to lowercase; Custom validation in controller |
body.payload.phoneNumber | string | optional | replace(/\D/g , ""); Whitespace trimmed |
body.payload.phone_prefix | string | conditional | Required when phoneNumber is supplied; otherwise optional |
body.payload.otp | string | optional | Whitespace trimmed |
body.payload.password | string | optional | Whitespace trimmed; Minimum (8) |
body.payload.source | string | optional | Whitespace trimmed; Allowed values ("organic", "scan", "referral", "promotion", "shopify_plugin", "other") |
body.payload.recaptcha_token | string | conditional | Runtime condition: recaptcha_required; Whitespace trimmed |
query.referralCode | string | optional | Whitespace trimmed |
query.medium | string | optional | Whitespace trimmed; Allowed values ("ios", "android", "website", "plugin", "other") |
query.device | string | optional | Whitespace trimmed; Allowed values ("mobile", "desktop", "ipad", "tablet", "other", "laptop") |
Supply at least one of email/phoneNumber and at least one of otp/password. If both password and OTP are sent, the service selects the password first. For website brands whose type is neither webflow nor marketplace, recaptcha_token is required by the controller. Emails containing a plus sign are rejected by the controller.
Example:
bash
curl -X POST "$BASE_URL/api/v1/login" \
-H "Origin: $ORIGIN" \
-H "api-key: $API_KEY" \
-H 'Content-Type: application/json' \
--data-raw '{"payload":{"email":"alex@example.com","otp":"123456","recaptcha_token":"<RECAPTCHA_TOKEN_IF_REQUIRED>"}}'Response: JSON with success: true and result containing the operation’s service data. Result fields/details: message, token, userId, isVerified.
Source: app/v1/controller/customer_controller.js:45 → loginUser; routes: routes/v1/customer_route.js:435; service: _loginCustomer (app/v1/service/customer_service.js:72).
Verify an OTP
Verify the supplied OTP for the customer. The current service returns a message and userId; it does not return the token claimed by the route comment.
It also does not verify the account. The service clears the stored OTP and, for a loyalty brand, records a referral or starts the customer's loyalty profile — but it leaves isVerified at non_verified. Because the code is now spent, it cannot be reused for login, and a password login is refused while the account is unverified. To verify a new account, send the OTP to Sign in with a password or OTP instead: that call marks the account verified and returns the customer token. See Lifecycle.
| Method | URL path | Required access |
|---|---|---|
POST | /api/v1/verify-otp | Origin + Brand key |
Request fields:
| Field | Type | Required | Rules / meaning |
|---|---|---|---|
body.payload | object | yes | At least one of ("email" , "phoneNumber") |
body.payload.phoneNumber | string | optional | replace(/\D/g , ""); Whitespace trimmed |
body.payload.phone_prefix | string | conditional | Required when phoneNumber is supplied; otherwise optional |
body.payload.otp | string | yes | Whitespace trimmed |
body.payload.email | string | optional | Email address; Whitespace trimmed; Converted to lowercase; Custom validation in controller |
query.referralCode | string | optional | Whitespace trimmed |
Example:
bash
curl -X POST "$BASE_URL/api/v1/verify-otp" \
-H "Origin: $ORIGIN" \
-H "api-key: $API_KEY" \
-H 'Content-Type: application/json' \
--data-raw '{"payload":{"otp":"123456","email":"alex@example.com"}}'Response: JSON with success: true and result containing the operation’s service data. Result fields/details: message, userId.
Source: app/v1/controller/customer_controller.js:138 → verifyOTP; routes: routes/v1/customer_route.js:30; service: _verifyOTP (app/v1/service/customer_service.js:297).
Send a sign-in OTP
Send a one-time code to an email address or phone number.
| Method | URL path | Required access |
|---|---|---|
POST | /api/v1/send-otp | Origin + Brand key |
Request fields:
| Field | Type | Required | Rules / meaning |
|---|---|---|---|
body.payload | object | yes | At least one of ("email" , "phoneNumber") |
body.payload.phoneNumber | string | optional | replace(/\D/g , ""); Whitespace trimmed |
body.payload.phone_prefix | string | conditional | Required when phoneNumber is supplied; otherwise optional |
body.payload.email | string | optional | Email address; Whitespace trimmed; Converted to lowercase; Custom validation in controller |
body.payload.recaptcha_token | string | conditional | Runtime condition: recaptcha_required; Whitespace trimmed |
query.hubId | number | optional | — |
query.source | string | optional | Whitespace trimmed; Allowed values ("organic", "scan", "referral", "promotion", "shopify_plugin", "other") |
query.medium | string | optional | Whitespace trimmed; Allowed values ("ios", "android", "website", "plugin", "other") |
query.device | string | optional | Whitespace trimmed; Allowed values ("mobile", "desktop", "ipad", "tablet", "other", "laptop") |
Example:
bash
curl -X POST "$BASE_URL/api/v1/send-otp" \
-H "Origin: $ORIGIN" \
-H "api-key: $API_KEY" \
-H 'Content-Type: application/json' \
--data-raw '{"payload":{"email":"alex@example.com","recaptcha_token":"<RECAPTCHA_TOKEN_IF_REQUIRED>"}}'Response: JSON with success: true and result containing the operation’s service data.
Source: app/v1/controller/customer_controller.js:204 → sendOTP; routes: routes/v1/customer_route.js:425; service: _sendOTP (app/v1/service/customer_service.js:716).
Send a password recovery link
| Method | URL path | Required access |
|---|---|---|
POST | /api/v1/resetPassword | Origin + Brand key |
Request fields:
| Field | Type | Required | Rules / meaning |
|---|---|---|---|
body.payload | object | yes | At least one of ("email" , "phoneNumber") |
body.payload.phoneNumber | string | optional | replace(/\D/g , ""); Whitespace trimmed |
body.payload.phone_prefix | string | conditional | Required when phoneNumber is supplied; otherwise optional |
body.payload.email | string | optional | Email address; Whitespace trimmed; Converted to lowercase; Custom validation in controller |
Example:
bash
curl -X POST "$BASE_URL/api/v1/resetPassword" \
-H "Origin: $ORIGIN" \
-H "api-key: $API_KEY" \
-H 'Content-Type: application/json' \
--data-raw '{"payload":{"email":"alex@example.com"}}'Response: JSON with success: true and result containing the operation’s service data.
Source: app/v1/controller/customer_controller.js:492 → resetPassword; routes: routes/v1/customer_route.js:58; service: _sendResetPassword (app/v1/service/customer_service.js:1003).
Change a password using a reset token
| Method | URL path | Required access |
|---|---|---|
POST | /api/v1/changePassword/:passwordToken | Origin |
No brand key on this route
/api/v1/changePassword/:passwordToken is listed although its route does not check api-key: the forgot password flow calls it. Sending the key anyway is harmless.
Request fields:
| Field | Type | Required | Rules / meaning |
|---|---|---|---|
body.payload | object | yes | — |
body.payload.password | string | yes | Whitespace trimmed; Minimum (8) |
path.passwordToken | string | yes | Whitespace trimmed |
Example:
bash
curl -X POST "$BASE_URL/api/v1/changePassword/<RESET_TOKEN>" \
-H "Origin: $ORIGIN" \
-H 'Content-Type: application/json' \
--data-raw '{"payload":{"password":"ExamplePass123!"}}'Response: JSON with success: true and result containing the operation’s service data.
Source: app/v1/controller/customer_controller.js:647 → changePassword; routes: routes/v1/customer_route.js:383; service: _changePassword (app/v1/service/customer_service.js:1263).
Send a magic sign-in link
| Method | URL path | Required access |
|---|---|---|
POST | /api/v1/send/magic/link | Origin + Brand key |
Request fields:
| Field | Type | Required | Rules / meaning |
|---|---|---|---|
body.payload | object | yes | — |
body.payload.email | string | yes | Email address; Whitespace trimmed; Converted to lowercase; Custom validation in controller |
The response sets a device_id cookie. Preserve it when following the magic-link flow.
Example:
bash
curl -X POST "$BASE_URL/api/v1/send/magic/link" \
-H "Origin: $ORIGIN" \
-H "api-key: $API_KEY" \
-H 'Content-Type: application/json' \
--data-raw '{"payload":{"email":"alex@example.com"}}'Response: JSON with success: true and result containing the operation’s service data.
Source: app/v1/controller/customer_controller.js:780 → sendMagicLink; routes: routes/v1/customer_route.js:77; service: _sendMagicLink (app/v1/service/customer_service.js:1390).
Sign in using a magic link
| Method | URL path | Required access |
|---|---|---|
POST | /api/v1/magic/login/:magicToken | Origin |
No brand key on this route
/api/v1/magic/login/:magicToken is listed although its route does not check api-key: the login flow calls it. Sending the key anyway is harmless.
Request fields:
| Field | Type | Required | Rules / meaning |
|---|---|---|---|
path.magicToken | string | yes | Whitespace trimmed |
Example:
bash
curl -X POST "$BASE_URL/api/v1/magic/login/<MAGIC_TOKEN>" \
-H "Origin: $ORIGIN"Response: JSON with success: true and result containing the operation’s service data. Result fields/details: message, token, userId, isVerified.
Source: app/v1/controller/customer_controller.js:826 → loginUserByMagicLink; routes: routes/v1/customer_route.js:87; service: _loginUserByMagicLink (app/v1/service/customer_service.js:1456).
Sign in or register with Google
| Method | URL path | Required access |
|---|---|---|
POST | /api/v1/user/register/google-oauth | Origin + Brand key |
Request fields:
| Field | Type | Required | Rules / meaning |
|---|---|---|---|
body.payload | object | yes | — |
body.payload.firstName | string | optional | Whitespace trimmed |
body.payload.lastName | string | optional | Whitespace trimmed |
body.payload.fullName | string | optional | Whitespace trimmed |
body.payload.email | string | yes | Email address; Whitespace trimmed; Converted to lowercase; Custom validation in controller |
body.payload.dateOfBirth | date | optional | ISO date |
body.payload.oauth_access_token | string | yes | Whitespace trimmed |
body.payload.oauth_json | object | optional | — |
body.payload.user_profile | string | optional | Whitespace trimmed |
body.payload.user_description | string | optional | Whitespace trimmed |
query.referralCode | string | optional | Whitespace trimmed; Default (null) |
query.source | string | optional | Whitespace trimmed; Allowed values ("organic", "scan", "referral", "promotion", "shopify_plugin", "other") |
query.medium | string | optional | Whitespace trimmed; Allowed values ("ios", "android", "website", "plugin", "other") |
query.device | string | optional | Whitespace trimmed; Allowed values ("mobile", "desktop", "ipad", "tablet", "other", "laptop") |
Example:
bash
curl -X POST "$BASE_URL/api/v1/user/register/google-oauth" \
-H "Origin: $ORIGIN" \
-H "api-key: $API_KEY" \
-H 'Content-Type: application/json' \
--data-raw '{"payload":{"email":"alex@example.com","oauth_access_token":"example"}}'Response: JSON with success: true and result containing the operation’s service data. Result fields/details: message, userId, email, token, existingCustomer.
Source: app/v1/controller/customer_controller.js:891 → registerGoogleVerifiedCustomer; routes: routes/v1/customer_route.js:315; service: _registerGoogleVerifiedCustomer (app/v1/service/customer_service.js:1683).
Sign in or register with Apple
| Method | URL path | Required access |
|---|---|---|
POST | /api/v1/user/register/apple-oauth | Origin + Brand key |
Request fields:
| Field | Type | Required | Rules / meaning |
|---|---|---|---|
body.payload | object | yes | — |
body.payload.oauth_access_token | string | yes | Whitespace trimmed |
query.referralCode | string | optional | Whitespace trimmed; Default (null) |
query.source | string | optional | Whitespace trimmed; Allowed values ("organic", "scan", "referral", "promotion", "shopify_plugin", "other") |
query.medium | string | optional | Whitespace trimmed; Allowed values ("ios", "android", "website", "plugin", "other") |
query.device | string | optional | Whitespace trimmed; Allowed values ("mobile", "desktop", "ipad", "tablet", "other", "laptop") |
Example:
bash
curl -X POST "$BASE_URL/api/v1/user/register/apple-oauth" \
-H "Origin: $ORIGIN" \
-H "api-key: $API_KEY" \
-H 'Content-Type: application/json' \
--data-raw '{"payload":{"oauth_access_token":"example"}}'Response: JSON with success: true and result containing the operation’s service data. Result fields/details: message, userId, email, token, existingCustomer.
Source: app/v1/controller/customer_controller.js:989 → registerAppleVerifiedCustomer; routes: routes/v1/customer_route.js:325; service: _registerAppleVerifiedCustomer (app/v1/service/customer_service.js:1873).
Lifecycle
An account's isVerified is one of non_verified, verified or banned. Which call moves it, and which calls each state refuses:
| State | Refused by |
|---|---|
non_verified | login with a password — log in with an OTP or a magic link first. register again — the account already exists |
verified | register — User Already Exists |
banned | login, verify-otp, send-otp, send/magic/link, magic/login, resetPassword and changePassword, each with a message to contact support |
changePassword changes the password but not the state, so a reset never verifies an account. Deactivation, an OTP-confirmed request, is on Profiles and accounts. How a visitor moves through these states, with every edge traced to code, is drawn on Authentication states. The journeys built on them are the sign-up, login and password reset web flows.