# Company

Authorise a company by its domain, give out its addresses and run its admins.

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

## Claim your own company address

`POST /v1/addresses/self`, called with a key or a person signed in

A person (or their app) whose email is at an authorised company's domain claims their own address there.

| Field | Type | | |
| --- | --- | --- | --- |
| `owner` | string | optional | The company, when several are at your email's domain. |
| `local` | string | optional | The name before the @. Defaults to your email's. |
| `display_name` | string | optional | The name people see. It must say it is an agent. |

```bash
curl -X POST https://address.seleya.ai/api/v1/addresses/self \
  -H "Authorization: Bearer $SELEYA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "display_name": "Ada'\''s agent"
}'
```

```python
import os, requests

r = requests.post(
    "https://address.seleya.ai/api/v1/addresses/self",
    headers={"Authorization": f"Bearer {os.environ['SELEYA_KEY']}"},
    json={
      "display_name": "Ada's agent"
    },
)
print(r.status_code, r.json())
```

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/addresses/self", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "display_name": "Ada's agent"
  }),
});
console.log(r.status, await r.json());
```

Answers:

- 201: Made
- 401: No valid key, session or token
- 403: Not allowed for this caller
- 409: Conflicts with the current state
- 422: A value is refused; the message says which

| Field (201) | 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
{
  "id": "7f3c9a2e-0000-4000-8000-000000000001",
  "address": "desk@acme.seleya.email",
  "owner_id": "7f3c9a2e-0000-4000-8000-000000000002",
  "display_name": "Desk",
  "status": "active",
  "holder_emails": [
    "ada@acme.example"
  ],
  "holder_person_id": "7f3c9a2e-0000-4000-8000-000000000003",
  "screening": {}
}
```

## Change the holder

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

Moves the address to another person. The address and its history stay. From a key or app it waits for the company's admins.

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

| Field | Type | | |
| --- | --- | --- | --- |
| `email` | string | required | The new holder's email address. They get an email saying so. |

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

```python
import os, requests

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

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/holder", {
  method: "PUT",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "email": "lee@acme.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
- 403: Not allowed for this caller
- 404: Not found, or not yours to see
- 422: A value is refused; the message says which
- 502: Done, but an email could not be sent

| 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
{
  "id": "7f3c9a2e-0000-4000-8000-000000000001",
  "address": "desk@acme.seleya.email",
  "owner_id": "7f3c9a2e-0000-4000-8000-000000000002",
  "display_name": "Desk",
  "status": "active",
  "holder_emails": [
    "ada@acme.example"
  ],
  "holder_person_id": "7f3c9a2e-0000-4000-8000-000000000003",
  "screening": {}
}
```

## Retire an address

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

Retires an address for good: it stops receiving and sending, its keys end, and its name is never given out again. From a key or app it waits for the admins.

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

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

```python
import os, requests

r = requests.post(
    "https://address.seleya.ai/api/v1/addresses/desk@acme.seleya.email/retire",
    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/retire", {
  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 |  |
| `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": "second@acme.seleya.email",
  "display_name": "Second agent",
  "holder_emails": [],
  "holder_person_id": null,
  "id": "7f3c9a2e-0000-4000-8000-00000000002a",
  "owner_id": "7f3c9a2e-0000-4000-8000-000000000020",
  "screening": {},
  "status": "retired"
}
```

## Start authorising a company

`POST /v1/companies`, called with a key or a person signed in

Starts authorising a company: by a DNS record, or by the company's own mail records for agents.<domain>. The legal name and country are screened against sanctions lists.

| Field | Type | | |
| --- | --- | --- | --- |
| `name` | string | required | The company's legal name. It is screened against sanctions lists. |
| `domain` | string | required | The domain its people's email is on, not a subdomain. |
| `country` | string | required | Where it is registered, as a two-letter code such as ZA. |
| `method` | dns \| mail | optional | dns: one text record we give you. mail: the mail records for agents.<domain>, which also prove the domain. Defaults to dns. |

```bash
curl -X POST https://address.seleya.ai/api/v1/companies \
  -H "Authorization: Bearer $SELEYA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Acme Robotics Ltd",
  "domain": "acme.com",
  "country": "ZA",
  "method": "dns"
}'
```

```python
import os, requests

r = requests.post(
    "https://address.seleya.ai/api/v1/companies",
    headers={"Authorization": f"Bearer {os.environ['SELEYA_KEY']}"},
    json={
      "name": "Acme Robotics Ltd",
      "domain": "acme.com",
      "country": "ZA",
      "method": "dns"
    },
)
print(r.status_code, r.json())
```

```typescript
const r = await fetch("https://address.seleya.ai/api/v1/companies", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SELEYA_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "name": "Acme Robotics Ltd",
    "domain": "acme.com",
    "country": "ZA",
    "method": "dns"
  }),
});
console.log(r.status, await r.json());
```

Answers:

- 201: Made
- 401: No valid key, session or token
- 403: Not allowed for this caller
- 409: Conflicts with the current state
- 422: A value is refused; the message says which
- 429: A limit is reached

| Field (201) | Type | | |
| --- | --- | --- | --- |
| `id` | string | required |  |
| `name` | string | required |  |
| `domain` | string | required |  |
| `method` | dns \| mail | required |  |
| `status` | string | required |  |
| `country` | string | optional |  |
| `records` | object | optional |  |
| `check` | object | optional |  |
| `error` | object | optional |  |
| `company` | object | optional |  |
| `created_at` | string | optional |  |

```json
{
  "check": null,
  "company": null,
  "country": "ZA",
  "created_at": "2026-10-22T00:02:08.456527+00:00",
  "domain": "beta.com",
  "error": null,
  "id": "7f3c9a2e-0000-4000-8000-000000000021",
  "method": "dns",
  "name": "Pay Co 10220001",
  "own_domain": "",
  "records": [
    {
      "name": "_beta.com",
      "type": "TXT",
      "value": "seleya-address-verify=30G_rSH4qrMhjsufRafp1550yWlpbQon"
    }
  ],
  "status": "pending",
  "subdomain": "acme.seleya.email"
}
```

## Your authorisations

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

The company authorisations you started, open and done.

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

```python
import os, requests

r = requests.get(
    "https://address.seleya.ai/api/v1/companies/authorisations",
    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/companies/authorisations", {
  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

| Field (200) | Type | | |
| --- | --- | --- | --- |
| `authorisations` | array of Authorisation | required |  |

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

## One authorisation

`GET /v1/companies/authorisations/{id}`, called with a key or a person signed in

One authorisation: the records to publish, the last check, and the company once it is set up.

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

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

```python
import os, requests

r = requests.get(
    "https://address.seleya.ai/api/v1/companies/authorisations/ID",
    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/companies/authorisations/ID", {
  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 | | |
| --- | --- | --- | --- |
| `id` | string | required |  |
| `name` | string | required |  |
| `domain` | string | required |  |
| `method` | dns \| mail | required |  |
| `status` | string | required |  |
| `country` | string | optional |  |
| `records` | object | optional |  |
| `check` | object | optional |  |
| `error` | object | optional |  |
| `company` | object | optional |  |
| `created_at` | string | optional |  |

```json
{
  "check": "no text record at _beta.com yet; DNS can take a few minutes",
  "company": null,
  "country": "ZA",
  "created_at": "2026-10-22T00:02:08.456527+00:00",
  "domain": "beta.com",
  "error": null,
  "id": "7f3c9a2e-0000-4000-8000-000000000021",
  "method": "dns",
  "name": "Pay Co 10220001",
  "own_domain": "",
  "records": [
    {
      "name": "_beta.com",
      "type": "TXT",
      "value": "seleya-address-verify=30G_rSH4qrMhjsufRafp1550yWlpbQon"
    }
  ],
  "status": "pending"
}
```

## Check its proof

`POST /v1/companies/authorisations/{id}/check`, called with a key or a person signed in

Checks the proof now.

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

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

```python
import os, requests

r = requests.post(
    "https://address.seleya.ai/api/v1/companies/authorisations/ID/check",
    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/companies/authorisations/ID/check", {
  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
- 409: Conflicts with the current state

| Field (200) | Type | | |
| --- | --- | --- | --- |
| `id` | string | required |  |
| `name` | string | required |  |
| `domain` | string | required |  |
| `method` | dns \| mail | required |  |
| `status` | string | required |  |
| `country` | string | optional |  |
| `records` | object | optional |  |
| `check` | object | optional |  |
| `error` | object | optional |  |
| `company` | object | optional |  |
| `created_at` | string | optional |  |

```json
{
  "check": "no text record at _beta.com yet; DNS can take a few minutes",
  "company": null,
  "country": "ZA",
  "created_at": "2026-10-22T00:02:08.456527+00:00",
  "domain": "beta.com",
  "error": null,
  "id": "7f3c9a2e-0000-4000-8000-000000000021",
  "method": "dns",
  "name": "Pay Co 10220001",
  "own_domain": "",
  "records": [
    {
      "name": "_beta.com",
      "type": "TXT",
      "value": "seleya-address-verify=30G_rSH4qrMhjsufRafp1550yWlpbQon"
    }
  ],
  "status": "pending"
}
```

## A company

`GET /v1/companies/{o}`, called with a key or a person signed in

A company, for its admins: its domains, status, addresses and admins.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `o` | string, in path | required | The company's owner id. |

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

```python
import os, requests

r = requests.get(
    "https://address.seleya.ai/api/v1/companies/OWNER_ID",
    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/companies/OWNER_ID", {
  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 | | |
| --- | --- | --- | --- |
| `owner_id` | string | required |  |
| `name` | string | required |  |
| `proven_domain` | string | optional |  |
| `mail_domain` | string | required |  |
| `own_domain` | string | optional |  |
| `address_domains` | array of strings | optional |  |
| `status` | string | required |  |
| `addresses` | integer | optional |  |
| `members` | array of objects | optional |  |
| `capabilities` | Capabilities | optional |  |

```json
{
  "owner_id": "7f3c9a2e-0000-4000-8000-000000000002",
  "name": "Acme",
  "proven_domain": "acme.example",
  "mail_domain": "acme.seleya.email",
  "own_domain": "",
  "address_domains": [
    "acme.seleya.email"
  ],
  "status": "active",
  "addresses": 1,
  "members": [
    {
      "person_id": "7f3c9a2e-0000-4000-8000-000000000003",
      "email": "ada@acme.example",
      "role": "admin"
    }
  ]
}
```

## Add an admin

`POST /v1/companies/{o}/members`, called with a key or a person signed in

Adds an admin. It grants authority, so from a key or app it waits for the admins.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `o` | string, in path | required | The company's owner id. |

| Field | Type | | |
| --- | --- | --- | --- |
| `email` | string | required | The new admin's email address. They get an email saying so. |

```bash
curl -X POST https://address.seleya.ai/api/v1/companies/OWNER_ID/members \
  -H "Authorization: Bearer $SELEYA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "lee@acme.com"
}'
```

```python
import os, requests

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

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

Answers:

- 201: Made
- 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
- 502: Done, but an email could not be sent

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

```json
{
  "person_id": "7f3c9a2e-0000-4000-8000-000000000003",
  "email": "ada@acme.example",
  "role": "admin"
}
```

## Remove an admin

`DELETE /v1/companies/{o}/members/{p}`, called with a key or a person signed in

Removes an admin. From a key or app it waits for the admins.

| Parameter | Type | | |
| --- | --- | --- | --- |
| `o` | string, in path | required | The company's owner id. |
| `p` | string, in path | required | The admin's person id. |

```bash
curl -X DELETE https://address.seleya.ai/api/v1/companies/OWNER_ID/members/PERSON_ID \
  -H "Authorization: Bearer $SELEYA_KEY"
```

```python
import os, requests

r = requests.delete(
    "https://address.seleya.ai/api/v1/companies/OWNER_ID/members/PERSON_ID",
    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/companies/OWNER_ID/members/PERSON_ID", {
  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
- 403: Not allowed for this caller
- 404: Not found, or not yours to see
- 409: Conflicts with the current state

| 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": "remove_admin",
    "summary": "remove ada@acme.example as admin",
    "waits_for": "a person, who was sent a link by email"
  }
}
```

