<!-- section: Auth API · status: building · source: docs/auth-api/02-sign-up.md -->

> Create an account with a six-digit code emailed to the address. No password needed.


# Sign up

**An account is created when somebody reads a code we email them.** Two calls:
ask for a code, then send the code back. That is the whole flow, and it is the
only way an account comes into being.

There is no password sign-up. `POST /v1/auth/sign-up/email` answers `404` — see
[why](#why-there-is-no-password-sign-up) below. A password is something a person
adds later, once they already own the account.

## 1. Ask for a code

```bash Request
curl -X POST "$BASE/v1/auth/email-otp/send-verification-otp" \
  -H "authorization: Bearer $PK" \
  -H "content-type: application/json" \
  -d '{"email":"ada@example.com","type":"sign-in"}'
```

| Field | Required | Notes |
|---|---|---|
| `email` | yes | Lowercased and used as the account's identity |
| `type` | yes | `sign-in` for this flow — the same code creates or signs in |

```json Response
{ "success": true }
```

**The same answer whether or not the address has an account**, which is what
keeps this from being a way to find out who your users are. One code, two
outcomes, decided when it is redeemed.

## 2. Send the code back

```bash Request
curl -X POST "$BASE/v1/auth/sign-in/email-otp" \
  -H "authorization: Bearer $PK" \
  -H "content-type: application/json" \
  -d '{"email":"ada@example.com","otp":"481920"}'
```

```json Response
{
  "token": "onSnRF8wZMuCs03Li7lcKJT2tLTvHjQO",
  "user": {
    "id": "NrzmwycxFVpxrD8RSZqieT4iqpW24W9M",
    "email": "ada@example.com",
    "emailVerified": true,
    "name": ""
  }
}
```

A `Set-Cookie` comes back with it. That cookie *is* the session — httpOnly,
Secure, SameSite=Lax, and namespaced to your project so a browser can hold
sessions for several apps at once without them colliding.

From a browser, send `credentials: "include"` or the cookie will not stick.

`emailVerified` is `true` on the first response, because proving the address is
how the account was made. There is no separate confirmation step to wait on.

## What the code looks like

Six digits, valid for ten minutes, three attempts. It arrives under **your
project's name** rather than ours, and the digits are not in the subject line —
a subject is repeated on lock screens and notification shades, and a code visible
without unlocking the mailbox is a code that did not need the mailbox.

## Adding a password afterwards

Optional, and most products do not need it. Somebody who wants one uses the
[password reset](/docs/auth-api/reset-password) flow: it sets a password whether
or not there was one before, and it proves the address on the way through.

Once set, they can use [sign in with a password](/docs/auth-api/sign-in) as well
as codes. Both work; neither replaces the other.

## Why there is no password sign-up

With one open, anybody can create an account against an address they do not own
and choose its password. When the real owner later proves the address — clicks a
confirmation link, or arrives through Google — the account becomes usable **and
the password the stranger chose still works**. Requiring email verification does
not close that; it only delays it.

Creating the account at the moment the address is proved does close it. Nothing
exists before the proof, so there is nothing for a stranger to have set.

This is not a setting. See [settings](/docs/auth-api/settings) for what is fixed
and what you can still decide.

## Errors

| Status | Code | Means |
|---|---|---|
| `400` | `INVALID_OTP` | Wrong code |
| `400` | `OTP_EXPIRED` | Older than ten minutes — ask for another |
| `403` | — | Sign-up is closed; see `disableSignUp` in [settings](/docs/auth-api/settings) |
| `401` | — | Missing or invalid key |
| `403` | — | Publishable key used from an origin you have not registered |
| `429` | — | Rate limited; see [limits](/docs/auth-api/limits) |
| `503` | — | The project's auth database is still being created |

**Nothing here says whether the address has an account.** Step one answers the
same way regardless, and a wrong code is a wrong code whoever sent it.
