Storefront accounts
Five routes under /v1/customers/auth cover the whole buyer account surface a storefront needs. They are a passthrough: the customers app is the system of record for the contact, and the platform identity service holds the password and the sessions.
secret — treat it as a credential, keep it server-side in an HTTP-only cookie, and never let it reach client JavaScript. /auth/me takes its ids in the body rather than in a browser-facing header for the same reason.Register
curl -X POST https://api.revenexx.com/v1/customers/auth/register \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{
"email": "einkauf@example.com",
"password": "…",
"first_name": "Anna",
"last_name": "Berger",
"locale": "de-DE",
"organization_name": "Beispiel Industrietechnik GmbH"
}'
One call writes the whole buyer: the contact this app owns, and the login behind it.
| Body field | Effect |
|---|---|
email | Becomes the login and the unique key of the contact. A second registration with it is a 409 — including while the first is still waiting for approval. |
organization_name | Founds a new company with this contact as its admin. This is what makes the registration a B2B one. |
organization_id | Joins an existing company — the invite shape. |
locale | BCP 47, and one of the store's configured locales, or the call is a 400. |
first_name, last_name | Optional. |
email and password are the only required fields; vat_id may also ride along. Leaving out both organization fields registers a standalone buyer.
The response carries user_id, contact, registration_status and approval_required — not a session. A finished registration still has to log in.
registration_mode decides what happens
| Setting | Result |
|---|---|
open (default) | A finished account: registration_status: "approved", status: "active", login works. |
approval_required | An application: registration_status: "pending", status: "invited", the login exists with the applicant's own password but is disabled, and a newly founded organization is parked as blocked. |
Check approval_required in the response. When it is true, show a "we will get back to you" screen instead of signing the buyer in. A merchant then decides with approve or reject.
The registration gates are all evaluated before anything is written, and a failure after that point rolls the organization and the contact back together.
Login
curl -X POST https://api.revenexx.com/v1/customers/auth/login \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"email":"einkauf@example.com","password":"…"}'
Three things come back, so a storefront learns in one call both that the buyer is signed in and who they are:
| Field | Is |
|---|---|
session | The auth session: $id, userId, secret, expire, provider. |
contact | The customer record behind the login. Null when a login has no contact mirrored against it — treat that as "signed in, but not a customer of this app". |
permissions | The buyer's effective grants, derived at read time. |
Wrong credentials are a 401. A correct password on an undecided application is a 403.
Two ids to keep straight: session.userId is the platform user, and it is the user_id every other auth route takes. session.$id is the session, and it is the session_id those routes take. Neither is the contact id — that is in contact.
Me
curl -X POST https://api.revenexx.com/v1/customers/auth/me \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"user_id":"…","session_id":"…"}'
The user, the customer record mirrored against it, and the effective grants — in one call. user_id is session.userId from the login.
session_id is optional and changes the question: pass it to ask is this session still alive? (a revoked one is then a 401); omit it to only ask who is this user?
The grants are derived on every call rather than returned from anywhere they could be cached, so a role changed a second ago is already reflected.
Logout
curl -X POST https://api.revenexx.com/v1/customers/auth/logout \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"user_id":"…","session_id":"…"}'
Ends one session: the buyer signs out on this device and stays signed in on the others, because the session id is what is revoked and not the account. The contact row is untouched — signing out is not blocking. If you want the second thing, set status: "blocked" on the contact.
Both ids come from what the login answered, and your server should drop its own cookie whatever this answers: the session is unusable afterwards either way.
Password recovery — two steps
Step one: request the mail
curl -X POST https://api.revenexx.com/v1/customers/auth/recovery \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"email":"einkauf@example.com","url":"https://shop.example.com/reset-password"}'
Both fields are required. The identity service mails a link to url with userId and secret appended as query parameters — those two are exactly what step two takes.
The secret is not in this response — it exists only inside the mailed link, which is the whole point of the two-step shape.
An address nobody holds is deliberately not distinguished from one that exists, so no account-existence check can be built on the answer. Nothing about the contact changes here.
Step two: the browser comes back
curl -X PUT https://api.revenexx.com/v1/customers/auth/recovery \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"user_id":"…","secret":"…","password":"…"}'
The userId and secret the mailed link carried — sent here as user_id and secret — plus the password the buyer just typed. All three are required.
The secret is spent on first use and expires, so a link cannot be replayed — a second attempt with the same one is a 401, not a second password change. The new password is in effect the moment this answers.
Where this fits
| You want | Use |
|---|---|
| Buyer accounts on a storefront | These five routes |
| A Cockpit operator or an API integration signing in | Authentication |
| A storefront built on the platform's own stack | Experience Studio authentication |
Where to go next
- Contacts — the record a registration creates, and the approve/reject routes.
- Roles and permissions — what the
permissionsblock contains. - Carts — claiming a guest cart once a buyer signs in.