API reference
API overview
Base URL, authentication, and how the surface is divided.
https://api.zevauth.net
Every endpoint is under /v1. Requests and responses are JSON.
Three surfaces, three credentials
The API is divided by who is asking, and the credential differs for each.
| Surface | Credential | For |
|---|---|---|
/v1/auth/* | Publishable key | Your frontend, signing people in |
/v1/users, /v1/organizations | Secret key | Your backend, administering |
/v1/me/* | Session token | A signed-in person, about themselves |
All three use Authorization: Bearer …. They are not interchangeable: handing
/v1/me a publishable key is refused, because that key ships in every browser
bundle and would otherwise read anybody’s profile.
curl https://api.zevauth.net/v1/environment \
-H "Authorization: Bearer pk_live_..."
Verifying a token on your server
This is the first thing most backends need, and it does not use the API at all. Access tokens are ordinary JWTs signed per environment, and you verify them offline against our published keys, so an outage of ours is not an outage of yours.
Each environment publishes its own discovery document:
https://api.zevauth.net/v1/{environmentId}/.well-known/openid-configuration
Note the environment id in the path. Keys are per environment, because
the environment id is also the iss claim, so /.well-known/jwks.json
at the root is not a thing we serve. Verifying
tokens has working examples for Node, any
framework, and Python.
Calling the API from your server
@zevauth/backend wraps the secret-key surface for Node, in any
framework:
npm install @zevauth/backend
import { createZevAuthBackend } from '@zevauth/backend';
const zevauth = createZevAuthBackend({
secretKey: process.env.ZEVAUTH_SECRET_KEY!,
});
const user = await zevauth.users.create({ email: 'ada@example.com' });
It is framework-agnostic: Express, Fastify, NestJS, Hono, a script. See the backend SDK. Everything it does is also plain HTTP, documented on the pages below, if you would rather not add a dependency.
Origin locking
A publishable key is accepted only from your project’s verified domain, its subdomains, or a registered callback origin. A request from anywhere else is refused. That is what stops a copy of your key working on a phishing site.
Requests with no Origin header, like curl, are allowed: a browser always
sends one, so its absence means the request is not coming from a page.
Rate limits
Each environment has a per-minute request limit, readable from
GET /v1/environment. Development allows 1,000 requests a minute,
production 6,000.
The budget is per environment, so one project’s traffic never spends another’s, and every response tells you where you stand:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 994
X-RateLimit-Reset: 1767225660
Watch X-RateLimit-Remaining and slow down before you are refused. A client
that only finds out at the 429 has already lost the request.
Exceeding it returns 429 with code rate_limited and a Retry-After header
in seconds:
{
"error": {
"code": "rate_limited",
"message": "This environment is over its limit of 1000 requests per minute. Retry in 34s."
}
}
Idempotency
Endpoints that start a flow, such as magic links, email codes and password resets, answer identically whether or not the address belongs to anybody, and can be called repeatedly. This is deliberate: a different response would turn them into a way to test whether somebody has an account with you.
Billing counts production only
Usage is measured in monthly active users: somebody who authenticated at least once in the period. Signing in forty times counts once, and development environments are never billed.
Updated at, Wednesday, October 21, 2026