---
title: Testing against ZevAuth
description: 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:

```bash
curl -X POST https://api.zevauth.net/v1/sandbox/users/user_.../tokens \
  -H "Authorization: Bearer sk_test_..."
```

```json
{
  "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.

```ts
// 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`](/api/authentication/) 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.

```bash
curl "https://api.zevauth.net/v1/sandbox/emails?to=ada@example.com" \
  -H "Authorization: Bearer sk_test_..."
```

```json
{
  "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.

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

> **Note:**
> Captured messages are readable only with a test secret key. Live
> environments deliver mail and capture nothing, so this returns 403 there
> rather than an empty list.

## Resetting between runs

```
DELETE /v1/sandbox/users
```

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

```bash
curl -X DELETE https://api.zevauth.net/v1/sandbox/users \
  -H "Authorization: Bearer sk_test_..."
```

```json
{ "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.

```json
{
  "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`](/api/authentication/) 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](/guide/keys/).
