# Email alerts API

> Read and change your email alert preferences, and the unsubscribe link every alert email carries.

- [Get alert preferences](#get-alert-preferences): `GET /v1/notifications/preferences`
- [Change alert preferences](#change-alert-preferences): `PUT /v1/notifications/preferences`
- [Show the unsubscribe confirmation](#show-the-unsubscribe-confirmation): `GET /v1/notifications/unsubscribe`
- [Unsubscribe from one alert type](#unsubscribe-from-one-alert-type): `POST /v1/notifications/unsubscribe`

## Get alert preferences

`GET /v1/notifications/preferences`

- **API key:** `Authorization: Bearer vmy_…` with preset `read_only`, `use_existing`, `create_and_use`, `full_access`.
- **Browser session:** the `vmyard_session` cookie.
- **Who:** Any member, for their own preferences.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Organization-ID` | header | `string (uuid)` | No | Required with a browser session. Optional with an API key, which fixes the organization; when sent it must match. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`NotificationPreferencesDto`](/docs/api/schemas#notificationpreferencesdto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl "$VMYARD_API_URL/v1/notifications/preferences" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "alerts": [
    {
      "available": false,
      "customized": false,
      "default_enabled": false,
      "description": "A machine could not be stopped.",
      "enabled": false,
      "type": "machine_stop_failed"
    }
  ],
  "email": "ada@example.com",
  "organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "user_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```

## Change alert preferences

`PUT /v1/notifications/preferences`

- **API key:** `Authorization: Bearer vmy_…` with preset `full_access`.
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** Any member, for their own preferences.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Organization-ID` | header | `string (uuid)` | No | Required with a browser session. Optional with an API key, which fixes the organization; when sent it must match. |

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `alerts` | `map of string` | Yes | Alert type to on (`true`) or off (`false`); unlisted types keep their current setting. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`NotificationPreferencesDto`](/docs/api/schemas#notificationpreferencesdto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | Refused for this caller: role, key preset, CSRF token, or organization (`forbidden`, `insufficient_scope`, `csrf_invalid`, `organization_mismatch`, `session_required`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X PUT "$VMYARD_API_URL/v1/notifications/preferences" \
  -H "Authorization: Bearer $VMYARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "alerts": {
    "operation_failed": true,
    "auto_stop": false
  }
}'
```

```json title="Response 200"
{
  "alerts": [
    {
      "available": false,
      "customized": false,
      "default_enabled": false,
      "description": "A machine could not be stopped.",
      "enabled": false,
      "type": "machine_stop_failed"
    }
  ],
  "email": "ada@example.com",
  "organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "user_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```

## Show the unsubscribe confirmation

`GET /v1/notifications/unsubscribe`

Shows a confirmation page and changes nothing, so link scanners are harmless.

- **Authentication:** none on this route.
- **Who:** Anyone with an alert email's link.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `token` | query | `string` | Yes | The token from the alert email's unsubscribe link. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | A confirmation page whose button POSTs the same URL; nothing changes on GET | `text/html` |
| `400` | Bad request. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl "$VMYARD_API_URL/v1/notifications/unsubscribe?token=token"
```

## Unsubscribe from one alert type

`POST /v1/notifications/unsubscribe`

Turns off the one alert type the token names for its member. One-click unsubscribe (RFC 8058) posts here.

- **Authentication:** none on this route.
- **Who:** Anyone with an alert email's link.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `token` | query | `string` | Yes | The token from the alert email's unsubscribe link. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | The alert is off for that member. One-click (RFC 8058) and form posts need no session; `Accept: text/html` answers with a page. | [`UnsubscribedDto`](/docs/api/schemas#unsubscribeddto) |
| `400` | Bad request. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/notifications/unsubscribe?token=token"
```

```json title="Response 200"
{
  "enabled": false,
  "organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "type": "operation_failed"
}
```
