# Protections

Screening per kind of sender, allow and block lists, contacts, held mail and approvals for sends.

Calls go to https://address.seleya.ai/api. Errors are `{"error": {"code", "message"}}` with the HTTP status: https://address.seleya.ai/docs/reference/errors.md

## Approval mode

`PUT /v1/addresses/{a}/approvals`, called with a key or a person signed in

Which of the address's sends wait for a person: off, unknown_recipients or everything. A key or app may make it stricter at once; lowering it waits for a person.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `a` | string, in path | required | The address, such as desk@acme.seleya.email. |

| Field | Type | | |
| --- | --- | --- | --- |
| `mode` | off \| unknown_recipients \| everything | required | off: the agent sends on its own. unknown_recipients: a send to anyone it has not written to before waits. everything: every send waits. |

```bash
curl -X PUT https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/approvals \
  -H "Authorization: Bearer $SELEYA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "mode": "unknown_recipients"
}'
```

```python
import os, requests

r = requests.put(
    "https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/approvals",
    headers={"Authorization": f"Bearer {os.environ['SELEYA_KEY']}"},
    json={
      "mode": "unknown_recipients"
    },
)
print(r.status_code, r.json())
```

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/approvals", {
  method: "PUT",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "mode": "unknown_recipients"
  }),
});
console.log(r.status, await r.json());
```

Answers:

- 200: Done
- 202: Waits for a person, who was emailed a link
- 401: No valid key, session or token
- 404: Not found, or not yours to see
- 422: A value is refused; the message says which

| Field (200) | Type | | |
| --- | --- | --- | --- |
| `address` | string | optional |  |
| `approvals` | string | optional |  |

```json
{
  "address": "desk@acme.seleya.email",
  "approvals": "unknown_recipients"
}
```

## Add contacts

`POST /v1/addresses/{a}/contacts`, called with a key or a person signed in

A person saves trusted contacts directly. An agent requests human approval, because a contact bypasses unknown-recipient screening and send approval.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `a` | string, in path | required | The address, such as desk@acme.seleya.email. |

| Field | Type | | |
| --- | --- | --- | --- |
| `contacts` | array of strings | required | 1 to 100 email addresses. |

```bash
curl -X POST https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/contacts \
  -H "Authorization: Bearer $SELEYA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "contacts": ["sam@beta.com"]
}'
```

```python
import os, requests

r = requests.post(
    "https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/contacts",
    headers={"Authorization": f"Bearer {os.environ['SELEYA_KEY']}"},
    json={
      "contacts": ["sam@beta.com"]
    },
)
print(r.status_code, r.json())
```

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/contacts", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "contacts": ["sam@beta.com"]
  }),
});
console.log(r.status, await r.json());
```

Answers:

- 200: Done
- 202: Waits for a person, who was emailed a link
- 401: No valid key, session or token
- 404: Not found, or not yours to see
- 422: A value is refused; the message says which

| Field (200) | Type | | |
| --- | --- | --- | --- |
| `added` | integer | required |  |

```json
{
  "added": 1
}
```

## Screening per class

`PUT /v1/addresses/{a}/screening`, called with a key or a person signed in

What happens to mail from each kind of sender: delivered, held or rejected. A key or app may make it stricter at once; a looser setting waits for a person.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `a` | string, in path | required | The address, such as desk@acme.seleya.email. |

```bash
curl -X PUT https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/screening \
  -H "Authorization: Bearer $SELEYA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "stranger": "held",
  "unverified": "rejected"
}'
```

```python
import os, requests

r = requests.put(
    "https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/screening",
    headers={"Authorization": f"Bearer {os.environ['SELEYA_KEY']}"},
    json={
      "stranger": "held",
      "unverified": "rejected"
    },
)
print(r.status_code, r.json())
```

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/screening", {
  method: "PUT",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "stranger": "held",
    "unverified": "rejected"
  }),
});
console.log(r.status, await r.json());
```

Answers:

- 200: Done
- 202: Waits for a person, who was emailed a link
- 401: No valid key, session or token
- 403: Not allowed for this caller
- 404: Not found, or not yours to see
- 422: A value is refused; the message says which

| Field (200) | Type | | |
| --- | --- | --- | --- |
| `id` | string | required |  |
| `address` | string | required |  |
| `owner_id` | string | required |  |
| `display_name` | string | required |  |
| `status` | active \| retired | required |  |
| `holder_emails` | array of strings | optional |  |
| `holder_person_id` | string, or null | optional |  |
| `screening` | map of string | optional |  |
| `capabilities` | Capabilities | optional |  |
| `approvals` | off \| unknown_recipients \| everything | optional |  |

```json
{
  "address": "ct-c@beta.com",
  "display_name": "Contract agent C",
  "holder_emails": [],
  "holder_person_id": null,
  "id": "7f3c9a2e-0000-4000-8000-000000000033",
  "owner_id": "7f3c9a2e-0000-4000-8000-000000000034",
  "screening": {
    "stranger": "rejected"
  },
  "status": "active"
}
```

## Allow and block lists

`GET /v1/addresses/{a}/senders`, called with a key or a person signed in

The address's allow and block lists.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `a` | string, in path | required | The address, such as desk@acme.seleya.email. |

```bash
curl https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/senders \
  -H "Authorization: Bearer $SELEYA_KEY"
```

```python
import os, requests

r = requests.get(
    "https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/senders",
    headers={"Authorization": f"Bearer {os.environ['SELEYA_KEY']}"},
)
print(r.status_code, r.json())
```

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/senders", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}` },
});
console.log(r.status, await r.json());
```

Answers:

- 200: Done
- 401: No valid key, session or token
- 404: Not found, or not yours to see

| Field (200) | Type | | |
| --- | --- | --- | --- |
| `senders` | array of objects | required |  |

```json
{
  "senders": [
    {
      "entry": "spam.example",
      "action": "block"
    }
  ]
}
```

## Allow or block a sender

`PUT /v1/addresses/{a}/senders`, called with a key or a person signed in

Allows or blocks a sender (an address or @domain). Blocking happens at once; allowing waits for a person when a key or app asks.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `a` | string, in path | required | The address, such as desk@acme.seleya.email. |

| Field | Type | | |
| --- | --- | --- | --- |
| `entry` | string | required | A sender's address, or @domain for everyone at it. An address beats its domain. |
| `action` | allow \| block | required | allow delivers its mail whatever its class; block drops it. |

```bash
curl -X PUT https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/senders \
  -H "Authorization: Bearer $SELEYA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "entry": "@beta.com",
  "action": "block"
}'
```

```python
import os, requests

r = requests.put(
    "https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/senders",
    headers={"Authorization": f"Bearer {os.environ['SELEYA_KEY']}"},
    json={
      "entry": "@beta.com",
      "action": "block"
    },
)
print(r.status_code, r.json())
```

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/senders", {
  method: "PUT",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "entry": "@beta.com",
    "action": "block"
  }),
});
console.log(r.status, await r.json());
```

Answers:

- 200: Done
- 202: Waits for a person, who was emailed a link
- 401: No valid key, session or token
- 404: Not found, or not yours to see
- 422: A value is refused; the message says which

| Field (200) | Type | | |
| --- | --- | --- | --- |
| `entry` | string | optional |  |
| `action` | string | optional |  |

```json
{
  "entry": "spam.example",
  "action": "block"
}
```

## Remove a list entry

`DELETE /v1/addresses/{a}/senders/{e}`, called with a key or a person signed in

Removes a list entry. Unblocking waits for a person when a key or app asks.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `a` | string, in path | required | The address, such as desk@acme.seleya.email. |
| `e` | string, in path | required | The list entry: an address or @domain. |

```bash
curl -X DELETE https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/senders/@beta.com \
  -H "Authorization: Bearer $SELEYA_KEY"
```

```python
import os, requests

r = requests.delete(
    "https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/senders/@beta.com",
    headers={"Authorization": f"Bearer {os.environ['SELEYA_KEY']}"},
)
print(r.status_code, r.text)
```

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/senders/@beta.com", {
  method: "DELETE",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}` },
});
console.log(r.status);
```

Answers:

- 202: Waits for a person, who was emailed a link
- 204: Done, with no body
- 401: No valid key, session or token
- 404: Not found, or not yours to see
- 422: A value is refused; the message says which

| Field (202) | Type | | |
| --- | --- | --- | --- |
| `status` | awaiting_approval | required | awaiting_approval. |
| `approval` | object | required | The ask: its id, kind and who decides. |

```json
{
  "status": "awaiting_approval",
  "approval": {
    "id": "7f3c9a2e-0000-4000-8000-000000000001",
    "kind": "unblock_sender",
    "summary": "unblock spam.example",
    "waits_for": "a person, who was sent a link by email"
  }
}
```

## Block a held message's sender

`POST /v1/deliveries/{id}/block`, called with a key or a person signed in

Blocks a held message's sender and drops the message.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `id` | string, in path | required | Its id. |

```bash
curl -X POST https://address.seleya.ai/api/v1/deliveries/ID/block \
  -H "Authorization: Bearer $SELEYA_KEY"
```

```python
import os, requests

r = requests.post(
    "https://address.seleya.ai/api/v1/deliveries/ID/block",
    headers={"Authorization": f"Bearer {os.environ['SELEYA_KEY']}"},
)
print(r.status_code, r.json())
```

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/deliveries/ID/block", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}` },
});
console.log(r.status, await r.json());
```

Answers:

- 200: Done
- 401: No valid key, session or token
- 404: Not found, or not yours to see

| Field (200) | Type | | |
| --- | --- | --- | --- |
| `blocked` | string | optional |  |
| `delivery` | string | optional |  |

```json
{
  "blocked": "spam@beta.example",
  "delivery": "7f3c9a2e-0000-4000-8000-000000000001"
}
```

## Release held mail

`POST /v1/deliveries/{id}/release`, called with a key or a person signed in

Releases held mail and makes its sender a contact. A person releases at once; a key or app's request waits for one.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `id` | string, in path | required | Its id. |

```bash
curl -X POST https://address.seleya.ai/api/v1/deliveries/ID/release \
  -H "Authorization: Bearer $SELEYA_KEY"
```

```python
import os, requests

r = requests.post(
    "https://address.seleya.ai/api/v1/deliveries/ID/release",
    headers={"Authorization": f"Bearer {os.environ['SELEYA_KEY']}"},
)
print(r.status_code, r.json())
```

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/deliveries/ID/release", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}` },
});
console.log(r.status, await r.json());
```

Answers:

- 200: Done
- 202: Waits for a person, who was emailed a link
- 401: No valid key, session or token
- 403: Not allowed for this caller
- 404: Not found, or not yours to see

| Field (200) | Type | | |
| --- | --- | --- | --- |
| `id` | string | required | The delivery's id: pass it as in_reply_to to reply. |
| `seq` | integer, or null | optional | Its place in the address's stream; read after it to go on. |
| `address` | string | required | The address that received it. |
| `conversation_id` | string | optional | The thread it belongs to. |
| `screening` | string | required | delivered, held or rejected. |
| `trust` | Trust | required | The verdict on its sender. |
| `message` | object | required | The message itself. |
| `received_at` | string | required | When it arrived. |

```json
{
  "address": "mx-b@beta.com",
  "conversation_id": "7f3c9a2e-0000-4000-8000-000000000022",
  "id": "7f3c9a2e-0000-4000-8000-000000000023",
  "message": {
    "cc": [],
    "from": "mx-a@beta.com",
    "id": "7f3c9a2e-0000-4000-8000-000000000024",
    "in_reply_to": "",
    "message_id_header": "<010701a161094821-7f3c9a2e-0000-4000-8000-000000000025@eu-central-1.amazonses.com>",
    "parts": [
      {
        "kind": "text",
        "text": "Shall we meet on Tuesday at 10:00?"
      }
    ],
    "references": [],
    "subject": "Mixed 10220035",
    "to": [
      "person@beta.com",
      "mx-b@beta.com"
    ],
    "transport": "mixed"
  },
  "received_at": "2026-10-22T00:36:00.900113+00:00",
  "screening": "delivered",
  "seq": 1,
  "trust": {
    "class": "known",
    "evidence": {
      "dkim": "PASS",
      "dmarc": "PASS",
      "dmarc_policy": "",
      "spam": "PASS",
      "spf": "PASS",
      "transport": "native",
      "virus": "PASS",
      "why": "a contact of this address; sent natively by an address on the service"
    },
    "untrusted": false
  }
}
```

## Held mail

`GET /v1/held`, called with a key or a person signed in

Mail held for a person. A key or app sees who sent it and why, never the text.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `address` | string, in query | optional | One address. |

```bash
curl https://address.seleya.ai/api/v1/held \
  -H "Authorization: Bearer $SELEYA_KEY"
```

```python
import os, requests

r = requests.get(
    "https://address.seleya.ai/api/v1/held",
    headers={"Authorization": f"Bearer {os.environ['SELEYA_KEY']}"},
)
print(r.status_code, r.json())
```

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/held", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}` },
});
console.log(r.status, await r.json());
```

Answers:

- 200: Done
- 401: No valid key, session or token
- 403: Not allowed for this caller
- 404: Not found, or not yours to see

| Field (200) | Type | | |
| --- | --- | --- | --- |
| `held` | array of Message | required |  |

```json
{
  "held": []
}
```

