Storefront accounts

The five account routes a shop front-end calls — register, login, me, logout and the two-step password recovery — and why they belong to a trusted server rather than a browser.

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.

These are for a trusted server, not a browser. The login response carries a session 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

Request
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 fieldEffect
emailBecomes 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_nameFounds a new company with this contact as its admin. This is what makes the registration a B2B one.
organization_idJoins an existing company — the invite shape.
localeBCP 47, and one of the store's configured locales, or the call is a 400.
first_name, last_nameOptional.

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_requirednot a session. A finished registration still has to log in.

registration_mode decides what happens

SettingResult
open (default)A finished account: registration_status: "approved", status: "active", login works.
approval_requiredAn 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

Request
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:

FieldIs
sessionThe auth session: $id, userId, secret, expire, provider.
contactThe 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".
permissionsThe 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

Request
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

Request
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

Request
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

Request
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 wantUse
Buyer accounts on a storefrontThese five routes
A Cockpit operator or an API integration signing inAuthentication
A storefront built on the platform's own stackExperience Studio authentication

Where to go next

  • Contacts — the record a registration creates, and the approve/reject routes.
  • Roles and permissions — what the permissions block contains.
  • Carts — claiming a guest cart once a buyer signs in.
Was this page helpful?