Auth schemas
Auth schemas give your site's visitors their own accounts — registration, login, email verification, password reset — without you building any of it. These are end users: your customers, stored in your own database, entirely separate from the teammates who use the dashboard.
Creating one
Under Auth schemas, create a schema with:
- Name — a slug like
customers; it names the user collection (auth_data_customers) and the public URL path. - Application URL — the site these users belong to, e.g.
https://shop.example.com. This is also the only origin allowed to call the auth API from a browser — a browser on any other site is refused with403ORIGIN_NOT_ALLOWED, checked on the server, not only by CORS. - Fields — at minimum an
emailand apasswordfield; add whatever profile fields you need (name, phone, preferences…). Field rules work as in schemas; passwords are stored as bcrypt hashes and are never returned by any API. - Require email verification — when on, accounts must verify before they can sign in; verification emails go through your mail integration.
The API your site calls
All routes live under /api/projects/{project}/auth/{schema-name}/… and are called from your site (browser or server).
Every call needs a project API key holding **auth.use** (End-user auth (login/register from your site)) in the ESC_API_KEY header — it identifies your site. Calls for a signed-in user also carry their token in Authorization: Bearer <token>; the two travel side by side and never replace each other. Without the key the answer is 401 (API_KEY_REQUIRED), even when a valid user token is present.
| Route | Does |
|---|---|
POST …/register | Creates the account (validated like any record), sends the verification email if required, returns the user and a token |
POST …/login | Email + password → { user, token }; blocked with EMAIL_NOT_VERIFIED until verification when required |
GET …/me | The signed-in user, from an Authorization: Bearer <token> header |
POST …/verify-email | Consumes the emailed verification token |
POST …/resend-verification | Sends a fresh verification email |
POST …/forgot-password | Sends a reset email (always answers success — it never reveals whether an email exists) |
POST …/reset-password | Consumes the emailed reset token, sets the new password |
POST …/change-password | Signed-in password change (current password required) |
POST …/logout | Ends the session client-side |
A minimal login on your site, then a call as that user:
const { token, user } = await fetch(
`${API}/auth/customers/login`,
{ method: 'POST',
headers: { 'content-type': 'application/json', ESC_API_KEY: SITE_KEY },
body: JSON.stringify({ email, password }) },
).then(r => r.json());
localStorage.setItem('token', token);
const me = await fetch(`${API}/auth/customers/me`, {
headers: { ESC_API_KEY: SITE_KEY, Authorization: `Bearer ${token}` },
}).then(r => r.json());About the key in the browser. Called from browser code, the key is visible to every visitor — which is acceptable only because a key holding nothing but auth.use lets them do exactly what your sign-in form already does. Make a dedicated key for it and never add other permissions. If your site has a server, calling these routes from there keeps the key private altogether. Browser calls are accepted only from the schema's application URL, and its preflight allows the ESC_API_KEY header. The application URL is one of the allowed-origins layers: when the project or the key has its own list too, the origin must be on every one, so a project list that leaves out your application URL's origin blocks sign-in from the browser (403, layer: project). Give the auth.use key allowed origins of its own naming your site.
Links opened from an email
Two pages are opened straight from the emails these routes send, and a link clicked in a mail client cannot attach a header:
- **
GET …/verify-email?token=…** — the verification link. It needs no key: the one-time token is its credential, and all it can do is mark that one address verified. (POST …/verify-email, for verifying from your own page, needs the key like everything else.) - The password reset page the reset email links to is hosted by the platform. It needs no key from you: it does not go through the public API, and the one-time reset token is its credential. If you build your own reset page, it calls
…/reset-password(both theGETcheck and thePOST) with your key, like every other route.
Before 4.0
These routes used to take no key. Sending the key as Authorization: Bearer esc_… still works on calls that carry no user token (login, register, forgot-password…) but is deprecated; on calls for a signed-in user the Bearer slot belongs to their token, so the key must be in ESC_API_KEY.
Tokens
Login returns a JWT valid for 7 days, cryptographically bound to this project, this schema and this workspace — a token from anywhere else fails verification. Send it as Authorization: Bearer <token>, next to your site's key in ESC_API_KEY. There is no server-side session to expire; logging out is discarding the token.
Users in the dashboard
The schema's Users tab lists accounts, shows verification state, and lets your team correct or remove an account. Emails the flow sends (verification, reset) are logged with the rest of your email activity.
Where it gets powerful: personal endpoints
A custom endpoint can require a signed-in end user (auth mode API key + logged-in end user), called with your site's key (holding endpoints.invoke) in ESC_API_KEY and the user's token as the Bearer. The flow then sees the caller as {{user.*}} — user.id, user.email, and your profile fields, with password and token fields stripped — so a query step filtered by customerId={{user.id}} returns only the caller's own records. That's "my orders" without a line of server code.