# Email sign-in API

> Sign in with an emailed 6-digit code or magic link.

- [Send a sign-in code](#send-a-sign-in-code): `POST /v1/auth/email/start`
- [Verify a code](#verify-a-code): `POST /v1/auth/email/verify`
- [Complete a magic link](#complete-a-magic-link): `GET /v1/auth/email/callback`

## Send a sign-in code

`POST /v1/auth/email/start`

- **Authentication:** none on this route.
- **Who:** No authentication.

**Request body** (`application/json`, [`EmailStartRequest`](/docs/api/schemas#emailstartrequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | `string` | Yes |  |
| `return_to` | `string \| null` | No | Same-origin UI path to open after sign-in; invalid values become `/`. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `202` | Sent if the address can receive email; the answer never depends on whether an account exists | [`EmailStarted`](/docs/api/schemas#emailstarted) |
| `404` | `unsupported_provider`: email sign-in is not configured | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `429` | `rate_limited` per address or client IP, with Retry-After | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `503` | `email_unavailable` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/auth/email/start" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "ada@example.com",
  "return_to": "/"
}'
```

```json title="Response 202"
{
  "message": "If the address can receive email, a code is on its way."
}
```

## Verify a code

`POST /v1/auth/email/verify`

Signs in with an emailed code, creating the user on the first verified
sign-in. A code requested through `POST /v1/auth/link/email` instead
links the address to the signed-in user and needs that session's cookie
and CSRF token.

- **Authentication:** none on this route.
- **Who:** No authentication.

**Request body** (`application/json`, [`EmailVerifyRequest`](/docs/api/schemas#emailverifyrequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | `string` | Yes | The 6-digit code from the email. |
| `email` | `string` | Yes |  |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | Signed in (session and CSRF cookies set) or address linked | [`EmailVerified`](/docs/api/schemas#emailverified) |
| `400` | `invalid_code`: wrong, expired, used, or superseded | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `401` | A linking code without a session | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | A linking code with another user's session, a missing CSRF token, or an API key | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | `unsupported_provider` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `409` | `identity_in_use`: the address is another user's sign-in method | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `429` | Too many requests; wait for `Retry-After`. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/auth/email/verify" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "ada@example.com",
  "code": "123456"
}'
```

```json title="Response 200"
{
  "return_to": "string"
}
```

## Complete a magic link

`GET /v1/auth/email/callback`

Completes a magic link. It works once, within ten minutes, and only in
the browser that requested it (`vmyard_email_state`); on another device
type the code instead. A linking link also needs the requesting user's session.

- **Authentication:** none on this route.
- **Who:** No authentication.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `token` | query | `string` | No | The magic-link token from the email. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `303` | Success: redirect to the stored `return_to` on the UI origin. Failure: redirect to `<ui>/sign-in?error=session_expired\|identity_in_use\|unsupported_provider` | — |

```sh title="Request"
curl "$VMYARD_API_URL/v1/auth/email/callback"
```
