ZevAuth
Sign up

Getting started

Testing against ZevAuth

Minting tokens without passwords, reading captured email, resetting users, and forcing failures.

Test environments have a set of endpoints that live environments do not. They exist so an integration test can set up the state it needs, read what we would have emailed, and tear itself down afterwards.

Everything on this page needs a test secret key (sk_test_…) and is refused outright in live, so none of it can run against real users.

A token without a password

POST /v1/sandbox/users/{userId}/tokens

The usual way to get a token is to create a user with a password and sign them in. That works, and it means every test suite stores a credential and spends its rate limit exercising our sign-in flow on the way to testing something else.

This mints a session directly:

curl -X POST https://api.zevauth.net/v1/sandbox/users/user_.../tokens \
  -H "Authorization: Bearer sk_test_..."
{
  "accessToken": "eyJhbGciOiJSUzI1NiIs...",
  "refreshToken": "rt_...",
  "expiresIn": 3600
}

It returns exactly what sign-in returns, and the session is a real one: it appears in the user’s session list, it can be revoked, and it expires on schedule. A test that passes against this token proves the same thing it would have proved against a token from the sign-in form.

The one difference is that the session records provider: "sandbox", so your sign-in log can tell a test harness apart from a person.

// The whole setup for a test that needs an authenticated caller.
const user = await zevauth.users.create({ email: 'ada@example.com' });
const { accessToken } = await zevauth.sandbox.mintToken(user.id);

await fetch('/api/my-endpoint', {
  headers: { Authorization: `Bearer ${accessToken}` },
});

A blocked user is refused, as sign-in refuses them. An unknown user is a 404.

Reading the email we captured

GET /v1/sandbox/emails

Test environments capture messages instead of delivering them, which is what "emailDelivery": "captured" on GET /v1/environment means. This is how you read what was captured, so flows that depend on a link we sent can be tested rather than only built: email verification, magic link sign in, password reset.

curl "https://api.zevauth.net/v1/sandbox/emails?to=ada@example.com" \
  -H "Authorization: Bearer sk_test_..."
{
  "data": [
    {
      "id": "cem_...",
      "to": "ada@example.com",
      "from": "no-reply@zevauth.com",
      "subject": "Verify your email",
      "kind": "email_verification",
      "code": "418293",
      "actionUrl": "https://accounts.yourapp.com/verify?token=...",
      "html": "…",
      "text": "…",
      "createdAt": "2026-08-28T18:00:00.000Z"
    }
  ]
}

code and actionUrl are stored as their own fields so a test never has to parse our HTML. An assertion against a template breaks the next time somebody changes the design; an assertion against code does not.

Newest first. ?to= filters by recipient and is case insensitive, ?kind= filters by message type, and ?limit= defaults to 25 with a maximum of 100.

const { data } = await zevauth.sandbox.emails({ to: 'ada@example.com' });
const code = data[0].code;
await zevauth.auth.verifyEmail({ code });

Resetting between runs

DELETE /v1/sandbox/users

Deletes every end user in the test environment, and with them their identities, sessions and refresh tokens.

curl -X DELETE https://api.zevauth.net/v1/sandbox/users \
  -H "Authorization: Bearer sk_test_..."
{ "deleted": 42 }

Call it from CI between runs. Test environments cap the number of users they hold, so a suite that creates accounts and never clears them eventually cannot sign anybody up at all.

Forcing a failure

GET /v1/sandbox/simulations

Lists the outcomes you can make this environment produce on demand, so your error paths get exercised without waiting for a real failure.

{
  "environment": "env_test_...",
  "outcomes": [
    {
      "outcome": "rate_limited",
      "email": "you+zevauth_sim_rate_limited@example.com",
      "header": { "X-Zevauth-Simulate": "rate_limited" }
    }
  ]
}

Trigger one either by signing up with the address, or by sending the header on any request. The list is served at runtime rather than written down here, so it never drifts from what the API actually supports.

Getting the keys into your test config

You do not need to open the dashboard to wire this up. Given a secret key, GET /v1/environment returns the publishable key, so a provisioning script or a per-branch preview environment can seed a frontend .env on its own. See API keys.

Updated at, Wednesday, October 21, 2026