# E-mail transaccional

Envie e-mails da sua aplicação pela API, com domínios verificados, modelos, registo de cada entrega, lista de bloqueados, estatísticas e webhooks.

Actualizado em 12/11/2026

## O que precisa

- O Email Center activo na sua conta. Activa-se no portal, na página E-mail transaccional. Sem ele, a API responde `403` com `forbidden` e o motivo `email_account_required` em `details.reason`.
- Uma chave com a permissão `email_center:write` para enviar e alterar, e `email_center:read` para consultar. Veja [Permissões](https://docs.interweb.co.mz/permissions.md). As permissões `email:read` e `email:write` são outras: servem para as caixas de correio da hospedagem.
- Um domínio remetente verificado. Só se envia de endereços desse domínio ou de um subdomínio dele.

## O domínio remetente

Junte o domínio com [`POST /v1/email/domains`](https://docs.interweb.co.mz/email-center.md):

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/domains" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/domains');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"domain": "example.com"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"domain": "example.com"}
r = requests.post("https://api.interweb.co.mz/v1/email/domains", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/domains", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"domain": "example.com"}),
});
console.log(response.status);
console.log(await response.text());
```


A resposta traz em `dns_records` os registos a pôr no DNS do domínio:

- três registos `CNAME` de DKIM, que assinam os seus e-mails (obrigatórios);
- um `TXT` de SPF, que autoriza o nosso envio (recomendado);
- um `TXT` de DMARC, a política do domínio (recomendado).

Depois de os pôr, chame [`POST /v1/email/domains/{domain}/verify`](https://docs.interweb.co.mz/email-center.md). O DNS pode levar algumas horas a propagar. Quando os registos DKIM estiverem no ar, o domínio passa a `verified`. O campo `found` de cada registo diz se ele já foi encontrado.

Se quiser que o endereço de retorno também seja seu, defina um subdomínio com [`PUT /v1/email/domains/{domain}/mail-from`](https://docs.interweb.co.mz/email-center.md), por exemplo `bounce`. Aparecem então mais um `MX` e um `TXT` para pôr no DNS.

## O envio

Use [`POST /v1/email/messages`](https://docs.interweb.co.mz/email-center.md) com o remetente, os destinatários, o assunto e o conteúdo em `html`, em `text` ou nos dois:

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/messages" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"from": "Example Ltd <notifications@example.com>", "to": ["ana@example.org"], "reply_to": "support@example.com", "template_id": 7, "variables": {"name": "Ana", "order": "1234"}}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/messages');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"from": "Example Ltd <notifications@example.com>", "to": ["ana@example.org"], "reply_to": "support@example.com", "template_id": 7, "variables": {"name": "Ana", "order": "1234"}}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"from": "Example Ltd <notifications@example.com>", "to": ["ana@example.org"], "reply_to": "support@example.com", "template_id": 7, "variables": {"name": "Ana", "order": "1234"}}
r = requests.post("https://api.interweb.co.mz/v1/email/messages", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"from": "Example Ltd <notifications@example.com>", "to": ["ana@example.org"], "reply_to": "support@example.com", "template_id": 7, "variables": {"name": "Ana", "order": "1234"}}),
});
console.log(response.status);
console.log(await response.text());
```


- `to`, `cc` e `bcc` aceitam um endereço, uma lista separada por vírgulas ou um array. No máximo 50 destinatários por e-mail.
- Cada destinatário conta como um e-mail e gasta um crédito.
- Sem `send_at`, o e-mail sai logo e a resposta traz o estado real.
- Os anexos vão em `attachments`, em base64, até 5 MB no total. Os executáveis são recusados.

Envie o cabeçalho `Idempotency-Key` para que uma repetição nunca envie o e-mail duas vezes. Veja [Repetições](https://docs.interweb.co.mz/api-rules.md).

## Modelos e variáveis

Crie modelos com [`POST /v1/email/templates`](https://docs.interweb.co.mz/email-center.md), com o assunto e o HTML ou o texto. Escreva as variáveis como `{{name}}`.

No envio, indique o `template_id` e os valores em `variables`. O assunto, o `html` e o `text` que não enviar vêm do modelo. No HTML, os valores entram escapados, por isso um nome com `<` ou `&` aparece como texto.

## Envios agendados e em lote

Para o e-mail sair mais tarde, junte `send_at` em ISO 8601 com o fuso horário, por exemplo `2026-11-20T09:30:00+02:00`, até 30 dias à frente. Os créditos só são descontados quando o e-mail sair. Para desistir, use [`POST /v1/email/messages/{id}/cancel`](https://docs.interweb.co.mz/email-center.md) antes da hora.

Para muitos e-mails de uma vez, use [`POST /v1/email/messages/batch`](https://docs.interweb.co.mz/email-center.md). Não há limite de mensagens, só de tamanho do pedido (8 MB). Os campos fora de `messages` valem para todas, e cada mensagem tem o seu `to` e as suas `variables`. Em `results` vem o que aconteceu a cada uma.

## Estados e eventos

[`GET /v1/email/messages/{id}`](https://docs.interweb.co.mz/email-center.md) mostra o estado e os eventos de cada destinatário:

- `sent`: saiu para os servidores de destino;
- `delivered`: o servidor do destinatário aceitou;
- `bounced`: devolvido. Se a devolução for permanente, o endereço entra na lista de bloqueados;
- `complained`: o destinatário marcou como spam, e o endereço entra na lista de bloqueados;
- `failed`: não saiu, e os créditos voltam ao saldo.

O conteúdo dos e-mails guarda-se durante 30 dias. Depois, `html` e `text` vêm a `null` e fica só o registo.

## Bloqueados

Os endereços da lista de bloqueados nunca recebem os seus e-mails: são retirados antes de o e-mail sair, não são cobrados e vêm em `suppressed` na resposta. Se não sobrar nenhum destinatário, a API responde `422` com o motivo `all_recipients_suppressed`.

Veja a lista com [`GET /v1/email/suppressions`](https://docs.interweb.co.mz/email-center.md), junte endereços com [`POST /v1/email/suppressions`](https://docs.interweb.co.mz/email-center.md) e tire-os com [`DELETE /v1/email/suppressions/{email}`](https://docs.interweb.co.mz/email-center.md), com o endereço codificado no caminho (`ana%40example.org`).

## Avisos por webhook

Grave um endereço HTTPS com [`PUT /v1/email/webhook`](https://docs.interweb.co.mz/email-center.md) e escolha os eventos: `email.sent`, `email.delivered`, `email.bounced`, `email.complained` e `email.failed`. Este webhook é diferente do webhook do SMS.

Cada aviso é assinado como os do SMS, no cabeçalho `X-Webhook-Signature`. Veja [o webhook do SMS](https://docs.interweb.co.mz/sms.md) para conferir a assinatura.

## Sandbox e Produção

- Com uma chave `sandbox_`, nada se envia nem se cobra. O e-mail fica `simulated`, com os eventos simulados. As outras alterações (domínios, modelos, bloqueados) só se simulam.
- Para ensaiar devoluções e queixas, envie para `bounce@example.com` ou `complaint@example.com`.
- Enquanto o Email Center estiver em modo de teste, também as chaves `prod_` simulam: `GET /v1/email/balance` diz-lhe isso em `test_mode`.

Veja [Sandbox e Produção](https://docs.interweb.co.mz/api-rules.md).

## SMTP Relay

No portal, na página SMTP, crie utilizadores e senhas para enviar a partir do seu programa ou servidor, nas portas 587 (STARTTLS) e 465 (TLS). A senha só se mostra uma vez. O servidor SMTP ainda está a ser ligado: até lá, use a API.

## E-mail marketing

Além do e-mail transaccional, o Email Center envia campanhas para os seus contactos: os do SMS Center que têm e-mail e os contactos só de e-mail. Cada destinatário gasta um crédito, como os outros e-mails.

1. Junte contactos com [`POST /v1/email/contacts`](https://docs.interweb.co.mz/email-center.md), com os grupos em `groups`. Os grupos são os mesmos dos contactos do SMS: veja-os em [`GET /v1/email/groups`](https://docs.interweb.co.mz/email-center.md).
2. Crie a campanha com [`POST /v1/email/campaigns`](https://docs.interweb.co.mz/email-center.md), com os blocos em `blocks` (`header`, `text`, `image`, `button`, `divider` e `footer`) ou com um modelo pronto em `template`. Se quiser, peça antes uma proposta à IA com [`POST /v1/email/campaigns/generate`](https://docs.interweb.co.mz/email-center.md).
3. Escolha o público em `target`: grupos, quem abriu ou clicou antes, e a data de entrada.
4. Confira o resultado de [`GET /v1/email/campaigns/{id}/spam-check`](https://docs.interweb.co.mz/email-center.md) e mande um teste com [`POST /v1/email/campaigns/{id}/test`](https://docs.interweb.co.mz/email-center.md).
5. Envie já com [`POST /v1/email/campaigns/{id}/send`](https://docs.interweb.co.mz/email-center.md) ou agende com [`POST /v1/email/campaigns/{id}/schedule`](https://docs.interweb.co.mz/email-center.md).

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/campaigns" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"name": "November news", "template": "comercio", "from": "news@example.com", "from_name": "Example Ltd", "target": {"groups": [3]}}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"name": "November news", "template": "comercio", "from": "news@example.com", "from_name": "Example Ltd", "target": {"groups": [3]}}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"name": "November news", "template": "comercio", "from": "news@example.com", "from_name": "Example Ltd", "target": {"groups": [3]}}
r = requests.post("https://api.interweb.co.mz/v1/email/campaigns", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"name": "November news", "template": "comercio", "from": "news@example.com", "from_name": "Example Ltd", "target": {"groups": [3]}}),
});
console.log(response.status);
console.log(await response.text());
```


Nos textos pode usar `{{first_name}}`, `{{name}}`, `{{company}}` e `{{email}}` de cada contacto.

### Subscrição e cancelamento

Todos os e-mails de campanha levam, no fim, a morada e uma ligação para cancelar a subscrição, e o cabeçalho `List-Unsubscribe`, para os programas de e-mail mostrarem o botão de cancelar. Quem cancela deixa de receber as suas campanhas, tal como quem está na lista de bloqueados. Veja e mude o estado com [`PUT /v1/email/contacts/{email}/subscription`](https://docs.interweb.co.mz/email-center.md).

### Estatísticas

[`GET /v1/email/campaigns/{id}/stats`](https://docs.interweb.co.mz/email-center.md) dá as entregas, as aberturas, os cliques, os cancelamentos e as devoluções. [`GET /v1/email/campaigns/{id}/recipients`](https://docs.interweb.co.mz/email-center.md) mostra o que cada contacto fez. Em modo de teste os números são de exemplo e vêm com `simulated` a `true`.


## Operações

43 operações nesta área. Clique numa operação para ver os parâmetros, os exemplos e as respostas, ou para a experimentar no Playground, sem sair da página.

### Listar os e-mails

`GET https://api.interweb.co.mz/v1/email/messages`

Precisa de `email_center:read`. Os mais recentes primeiro, sem o conteúdo (veja GET /v1/email/messages/{id}).

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `status` | consulta | string | não | Um de: queued, scheduled, sending, sent, delivered, bounced, complained, failed, simulated, cancelled. |
| `from` | consulta | string | não | Desde este dia, AAAA-MM-DD. |
| `to` | consulta | string | não | Até este dia, AAAA-MM-DD. |
| `recipient` | consulta | string | não | Só os e-mails para este endereço (to, cc ou bcc). |
| `page` | consulta | integer | não | O número da página. A primeira é a 1. |
| `per_page` | consulta | integer | não | Resultados por página, de 1 a 100. Por omissão, 10. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/messages" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/messages');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/messages", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/messages", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Listar os e-mails.

```json
{
  "data": [
    {
      "id": "5120",
      "status": "delivered",
      "simulated": false,
      "from": "notifications@example.com",
      "to": [
        "ana@example.org"
      ],
      "cc": [],
      "bcc": [],
      "reply_to": [
        "support@example.com"
      ],
      "subject": "Your order 1234 is ready",
      "recipients": 1,
      "credits": 1.0,
      "charged": true,
      "source": "api",
      "template_id": 7,
      "attachments": [],
      "error": null,
      "scheduled_for": null,
      "created_at": "2026-11-12T10:30:00+02:00",
      "sent_at": "2026-11-12T10:30:01+02:00",
      "from_name": "Example Ltd"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 10,
    "total": 1,
    "total_pages": 1
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Enviar um e-mail

`POST https://api.interweb.co.mz/v1/email/messages`

Precisa de `email_center:write`. Cada destinatário (to, cc e bcc, até 50) é um e-mail e um crédito. Os endereços da lista de bloqueados saem e vêm em `suppressed`; se não sobrar nenhum, a API responde 422 `all_recipients_suppressed`. Sem `send_at` o e-mail sai logo e a resposta traz o estado real. Com uma chave sandbox, ou com o Email Center em modo de teste, nada se envia nem se cobra: o e-mail fica `simulated` e os eventos da simulação ficam registados (`bounce@example.com` simula uma devolução, `complaint@example.com` uma queixa). Envie `Idempotency-Key` para uma repetição nunca enviar duas vezes.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `from` | string | sim | Remetente num domínio verificado (ou subdomínio dele): `notifications@example.com` ou `Empresa <notifications@example.com>`. |
| `from_name` | string | não | Nome a mostrar, opcional. |
| `to` | any | sim | Um endereço, uma lista separada por vírgulas ou um array. |
| `cc` | any | não |  |
| `bcc` | any | não |  |
| `reply_to` | any | não | Até 5 endereços. |
| `subject` | string | não | Até 998 caracteres. Pode usar {{variáveis}}. |
| `html` | string | não | Corpo em HTML. Os valores das {{variáveis}} entram escapados. |
| `text` | string | não | Corpo em texto simples. |
| `template_id` | integer | não | Um modelo desta conta; o assunto, o html e o text vazios vêm dele. |
| `variables` | object | não | Os valores das marcas {{nome}}, num objecto. |
| `attachments` | array<object> | não | Ficheiros em base64; 5 MB no total por omissão, executáveis recusados. |
| `send_at` | string | não | Opcional, ISO 8601 com fuso, até 30 dias por omissão. |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/messages" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"from": "Example Ltd <notifications@example.com>", "to": ["ana@example.org"], "reply_to": "support@example.com", "template_id": 7, "variables": {"name": "Ana", "order": "1234"}}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/messages');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"from": "Example Ltd <notifications@example.com>", "to": ["ana@example.org"], "reply_to": "support@example.com", "template_id": 7, "variables": {"name": "Ana", "order": "1234"}}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"from": "Example Ltd <notifications@example.com>", "to": ["ana@example.org"], "reply_to": "support@example.com", "template_id": 7, "variables": {"name": "Ana", "order": "1234"}}
r = requests.post("https://api.interweb.co.mz/v1/email/messages", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"from": "Example Ltd <notifications@example.com>", "to": ["ana@example.org"], "reply_to": "support@example.com", "template_id": 7, "variables": {"name": "Ana", "order": "1234"}}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

Enviar um e-mail.

```json
{
  "data": {
    "id": "5120",
    "status": "sent",
    "simulated": false,
    "from": "notifications@example.com",
    "to": [
      "ana@example.org"
    ],
    "cc": [],
    "bcc": [],
    "reply_to": [
      "support@example.com"
    ],
    "subject": "Your order 1234 is ready",
    "recipients": 1,
    "credits": 1.0,
    "charged": true,
    "source": "api",
    "template_id": 7,
    "attachments": [],
    "error": null,
    "scheduled_for": null,
    "created_at": "2026-11-12T10:30:00+02:00",
    "sent_at": "2026-11-12T10:30:01+02:00",
    "from_name": "Example Ltd",
    "suppressed": []
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.recipients` | integer | Destinatários depois da lista de bloqueados; cada um é um crédito. |
| `data.source` | string | portal, api ou smtp. |

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `400`

O pedido não é válido, por exemplo o corpo não é JSON ou a ligação não é HTTPS.

```json
{
  "error": {
    "code": "invalid_request",
    "message": "O pedido não é válido.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `409`

A operação não se pode fazer no estado actual. Veja `details.reason`.

```json
{
  "error": {
    "code": "conflict",
    "message": "This operation only works on an active domain.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "domain_not_active"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 400 | `invalid_request` | O pedido não é válido, por exemplo o corpo não é JSON ou a ligação não é HTTPS. |
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 409 | `conflict` | A operação não se pode fazer no estado actual. Veja `details.reason`. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Enviar e-mails em lote

`POST https://api.interweb.co.mz/v1/email/messages/batch`

Precisa de `email_center:write`. Sem limite de mensagens, só do tamanho do pedido (8 MB). Os campos fora de `messages` (from, subject, html, text, template_id, reply_to, send_at) valem para todas; cada mensagem pode mudá-los e tem o seu `to` e as suas `variables`. Cada uma segue as regras de POST /v1/email/messages e `results` diz o que lhe aconteceu (`error` usa as mesmas razões). Até 20 saem logo; as outras entram na fila (a cada minuto). Sem créditos ou no limite do dia, as restantes são recusadas.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `from` | string | não | Remetente num domínio verificado (ou subdomínio dele): `notifications@example.com` ou `Empresa <notifications@example.com>`. |
| `from_name` | string | não | Nome a mostrar, opcional. |
| `to` | any | não | Um endereço, uma lista separada por vírgulas ou um array. |
| `cc` | any | não |  |
| `bcc` | any | não |  |
| `reply_to` | any | não | Até 5 endereços. |
| `subject` | string | não | Até 998 caracteres. Pode usar {{variáveis}}. |
| `html` | string | não | Corpo em HTML. Os valores das {{variáveis}} entram escapados. |
| `text` | string | não | Corpo em texto simples. |
| `template_id` | integer | não | Um modelo desta conta; o assunto, o html e o text vazios vêm dele. |
| `variables` | object | não | Os valores das marcas {{nome}}, num objecto. |
| `attachments` | array<object> | não | Ficheiros em base64; 5 MB no total por omissão, executáveis recusados. |
| `send_at` | string | não | Opcional, ISO 8601 com fuso, até 30 dias por omissão. |
| `messages` | array<object> | sim |  |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/messages/batch" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"from": "Example Ltd <notifications@example.com>", "subject": "Hello {{name}}", "text": "Hi {{name}}, your invoice is ready.", "messages": [{"to": "ana@example.org", "variables": {"name": "Ana"}}, {"to": "rui@example.org", "variables": {"name": "Rui"}}, {"to": "not-an-email"}]}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/messages/batch');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"from": "Example Ltd <notifications@example.com>", "subject": "Hello {{name}}", "text": "Hi {{name}}, your invoice is ready.", "messages": [{"to": "ana@example.org", "variables": {"name": "Ana"}}, {"to": "rui@example.org", "variables": {"name": "Rui"}}, {"to": "not-an-email"}]}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"from": "Example Ltd <notifications@example.com>", "subject": "Hello {{name}}", "text": "Hi {{name}}, your invoice is ready.", "messages": [{"to": "ana@example.org", "variables": {"name": "Ana"}}, {"to": "rui@example.org", "variables": {"name": "Rui"}}, {"to": "not-an-email"}]}
r = requests.post("https://api.interweb.co.mz/v1/email/messages/batch", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/messages/batch", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"from": "Example Ltd <notifications@example.com>", "subject": "Hello {{name}}", "text": "Hi {{name}}, your invoice is ready.", "messages": [{"to": "ana@example.org", "variables": {"name": "Ana"}}, {"to": "rui@example.org", "variables": {"name": "Rui"}}, {"to": "not-an-email"}]}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

Enviar e-mails em lote.

```json
{
  "data": {
    "total": 3,
    "accepted": 2,
    "rejected": 1,
    "credits": 2.0,
    "simulated": false,
    "results": [
      {
        "index": 0,
        "id": "5121",
        "status": "sent",
        "recipients": 1,
        "suppressed": []
      },
      {
        "index": 1,
        "id": "5122",
        "status": "sent",
        "recipients": 1,
        "suppressed": []
      },
      {
        "index": 2,
        "id": null,
        "status": "rejected",
        "error": "invalid_recipient",
        "field": "to"
      }
    ]
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `400`

O pedido não é válido, por exemplo o corpo não é JSON ou a ligação não é HTTPS.

```json
{
  "error": {
    "code": "invalid_request",
    "message": "O pedido não é válido.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 400 | `invalid_request` | O pedido não é válido, por exemplo o corpo não é JSON ou a ligação não é HTTPS. |
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Ver um e-mail

`GET https://api.interweb.co.mz/v1/email/messages/{id}`

Precisa de `email_center:read`. Com os eventos de cada destinatário e o conteúdo (guardado 30 dias por omissão; depois `html` e `text` vêm a null).

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` devolvido no envio. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/messages/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/messages/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/messages/00000000-0000-4000-8000-000000000000", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/messages/00000000-0000-4000-8000-000000000000", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Ver um e-mail.

```json
{
  "data": {
    "id": "5120",
    "status": "delivered",
    "simulated": false,
    "from": "notifications@example.com",
    "to": [
      "ana@example.org"
    ],
    "cc": [],
    "bcc": [],
    "reply_to": [
      "support@example.com"
    ],
    "subject": "Your order 1234 is ready",
    "recipients": 1,
    "credits": 1.0,
    "charged": true,
    "source": "api",
    "template_id": 7,
    "attachments": [],
    "error": null,
    "scheduled_for": null,
    "created_at": "2026-11-12T10:30:00+02:00",
    "sent_at": "2026-11-12T10:30:01+02:00",
    "from_name": "Example Ltd",
    "html": "<p>Hello Ana, your order 1234 is ready.</p>",
    "text": "Hello Ana, your order 1234 is ready.",
    "events": [
      {
        "type": "sent",
        "recipient": null,
        "detail": null,
        "simulated": false,
        "created_at": "2026-11-12T10:30:01+02:00"
      },
      {
        "type": "delivered",
        "recipient": "ana@example.org",
        "detail": null,
        "simulated": false,
        "created_at": "2026-11-12T10:30:03+02:00"
      }
    ]
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.recipients` | integer | Destinatários depois da lista de bloqueados; cada um é um crédito. |
| `data.source` | string | portal, api ou smtp. |

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Cancelar um e-mail agendado

`POST https://api.interweb.co.mz/v1/email/messages/{id}/cancel`

Precisa de `email_center:write`. Só e-mails `scheduled` que ainda não saíram; os créditos já descontados voltam (`credits_returned`). Senão 409 `not_cancellable`.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` devolvido no envio. |
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/messages/00000000-0000-4000-8000-000000000000/cancel" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/messages/00000000-0000-4000-8000-000000000000/cancel');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
r = requests.post("https://api.interweb.co.mz/v1/email/messages/00000000-0000-4000-8000-000000000000/cancel", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/messages/00000000-0000-4000-8000-000000000000/cancel", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Cancelar um e-mail agendado.

```json
{
  "data": {
    "id": "5120",
    "status": "cancelled",
    "simulated": false,
    "from": "notifications@example.com",
    "to": [
      "ana@example.org"
    ],
    "cc": [],
    "bcc": [],
    "reply_to": [
      "support@example.com"
    ],
    "subject": "Your order 1234 is ready",
    "recipients": 1,
    "credits": 1.0,
    "charged": true,
    "source": "api",
    "template_id": 7,
    "attachments": [],
    "error": null,
    "scheduled_for": "2026-11-20T09:30:00+02:00",
    "created_at": "2026-11-12T10:30:00+02:00",
    "sent_at": null,
    "from_name": "Example Ltd",
    "credits_returned": 0.0
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.recipients` | integer | Destinatários depois da lista de bloqueados; cada um é um crédito. |
| `data.source` | string | portal, api ou smtp. |

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

A operação não se pode fazer no estado actual. Veja `details.reason`.

```json
{
  "error": {
    "code": "conflict",
    "message": "This operation only works on an active domain.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "domain_not_active"
    }
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 409 | `conflict` | A operação não se pode fazer no estado actual. Veja `details.reason`. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Ver o saldo de e-mail

`GET https://api.interweb.co.mz/v1/email/balance`

Precisa de `email_center:read`. Um crédito é um e-mail para um destinatário. `test_mode` é true enquanto o Email Center não envia a sério.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/balance" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/balance');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/balance", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/balance", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Ver o saldo de e-mail.

```json
{
  "data": {
    "balance": 9850.0,
    "unit": "emails",
    "scheduled_credits": 0.0,
    "daily_limit": 500,
    "sent_today": 42,
    "mode": "production",
    "test_mode": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Ver as estatísticas de e-mail

`GET https://api.interweb.co.mz/v1/email/stats`

Precisa de `email_center:read`. Por estado e por dia. Entregues, devolvidos e queixas contam destinatários; as taxas são sobre os destinatários. Por omissão, os últimos 30 dias; no máximo 366.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `from` | consulta | string | não | AAAA-MM-DD. |
| `to` | consulta | string | não | AAAA-MM-DD. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/stats" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/stats');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/stats", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/stats", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Ver as estatísticas de e-mail.

```json
{
  "data": {
    "from": "2026-10-14",
    "to": "2026-11-12",
    "messages": 320,
    "recipients": 340,
    "credits_spent": 338.0,
    "by_status": {
      "delivered": 300,
      "bounced": 4,
      "complained": 0,
      "sent": 14,
      "failed": 2
    },
    "events": {
      "delivered": 330,
      "bounced": 4,
      "complained": 0
    },
    "delivery_rate": 97.06,
    "bounce_rate": 1.18,
    "complaint_rate": 0.0,
    "by_day": [
      {
        "date": "2026-11-12",
        "messages": 12,
        "recipients": 12,
        "delivered": 12,
        "bounced": 0,
        "complained": 0,
        "credits": 12.0
      }
    ]
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Listar os domínios remetentes

`GET https://api.interweb.co.mz/v1/email/domains`

Precisa de `email_center:read`. Por ordem alfabética.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `page` | consulta | integer | não | O número da página. A primeira é a 1. |
| `per_page` | consulta | integer | não | Resultados por página, de 1 a 100. Por omissão, 10. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/domains" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/domains');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/domains", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/domains", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Listar os domínios remetentes.

```json
{
  "data": [
    {
      "id": 12,
      "domain": "example.com",
      "status": "verified",
      "mail_from": null,
      "verified_at": "2026-11-12T10:00:00+02:00",
      "last_checked_at": "2026-11-12T10:00:00+02:00",
      "created_at": "2026-11-12T09:40:00+02:00"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 10,
    "total": 1,
    "total_pages": 1
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Juntar um domínio remetente

`POST https://api.interweb.co.mz/v1/email/domains`

Precisa de `email_center:write`. Ponha no DNS do domínio os registos de `dns_records`: três CNAME de DKIM (obrigatórios), SPF e DMARC (recomendados) e, com MAIL FROM, um MX e um TXT. Depois chame verify; o DNS pode levar algumas horas. Só os domínios verificados enviam, de qualquer endereço do domínio ou de um subdomínio. Os domínios de caixas grátis (gmail.com e outros) são recusados. Com uma chave sandbox nada se guarda.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `domain` | string | sim |  |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/domains" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/domains');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"domain": "example.com"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"domain": "example.com"}
r = requests.post("https://api.interweb.co.mz/v1/email/domains", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/domains", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"domain": "example.com"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

Juntar um domínio remetente.

```json
{
  "data": {
    "id": 12,
    "domain": "example.com",
    "status": "pending",
    "mail_from": null,
    "verified_at": null,
    "last_checked_at": null,
    "created_at": "2026-11-12T09:40:00+02:00",
    "dns_records": [
      {
        "type": "CNAME",
        "name": "k3jd8s0a1b2c3d4e5f6a7b8c9d0e1f2a._domainkey.example.com",
        "value": "k3jd8s0a1b2c3d4e5f6a7b8c9d0e1f2a.dkim.example-brand.com",
        "priority": null,
        "purpose": "dkim",
        "required": true,
        "found": null
      },
      {
        "type": "TXT",
        "name": "example.com",
        "value": "v=spf1 include:_spf.example-brand.com ~all",
        "priority": null,
        "purpose": "spf",
        "required": false,
        "found": null
      },
      {
        "type": "TXT",
        "name": "_dmarc.example.com",
        "value": "v=DMARC1; p=none; adkim=r; aspf=r",
        "priority": null,
        "purpose": "dmarc",
        "required": false,
        "found": null
      }
    ],
    "test_mode_accepted": false,
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `409`

A operação não se pode fazer no estado actual. Veja `details.reason`.

```json
{
  "error": {
    "code": "conflict",
    "message": "This operation only works on an active domain.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "domain_not_active"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 409 | `conflict` | A operação não se pode fazer no estado actual. Veja `details.reason`. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Ver um domínio remetente

`GET https://api.interweb.co.mz/v1/email/domains/{domain}`

Precisa de `email_center:read`. Com os registos DNS a pôr e se cada um foi encontrado na última verificação.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `domain` | caminho | string | sim | O nome do domínio (example.com) ou o seu `id`. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/domains/VALUE" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/domains/VALUE');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/domains/VALUE", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/domains/VALUE", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Ver um domínio remetente.

```json
{
  "data": {
    "id": 12,
    "domain": "example.com",
    "status": "verified",
    "mail_from": null,
    "verified_at": "2026-11-12T10:00:00+02:00",
    "last_checked_at": "2026-11-12T10:00:00+02:00",
    "created_at": "2026-11-12T09:40:00+02:00",
    "dns_records": [
      {
        "type": "CNAME",
        "name": "k3jd8s0a1b2c3d4e5f6a7b8c9d0e1f2a._domainkey.example.com",
        "value": "k3jd8s0a1b2c3d4e5f6a7b8c9d0e1f2a.dkim.example-brand.com",
        "priority": null,
        "purpose": "dkim",
        "required": true,
        "found": true
      },
      {
        "type": "TXT",
        "name": "example.com",
        "value": "v=spf1 include:_spf.example-brand.com ~all",
        "priority": null,
        "purpose": "spf",
        "required": false,
        "found": true
      },
      {
        "type": "TXT",
        "name": "_dmarc.example.com",
        "value": "v=DMARC1; p=none; adkim=r; aspf=r",
        "priority": null,
        "purpose": "dmarc",
        "required": false,
        "found": false
      }
    ],
    "test_mode_accepted": false,
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Apagar um domínio remetente

`DELETE https://api.interweb.co.mz/v1/email/domains/{domain}`

Precisa de `email_center:write`. O domínio deixa de poder enviar. Os e-mails já enviados ficam no registo.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `domain` | caminho | string | sim | O nome do domínio (example.com) ou o seu `id`. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl -X DELETE "https://api.interweb.co.mz/v1/email/domains/VALUE" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/domains/VALUE');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.delete("https://api.interweb.co.mz/v1/email/domains/VALUE", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/domains/VALUE", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Apagar um domínio remetente.

```json
{
  "data": {
    "id": 12,
    "domain": "example.com",
    "deleted": true,
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Verificar um domínio remetente

`POST https://api.interweb.co.mz/v1/email/domains/{domain}/verify`

Precisa de `email_center:write`. Confere já os registos DKIM e devolve o estado novo. Com uma chave sandbox o resultado não se guarda.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `domain` | caminho | string | sim | O nome do domínio (example.com) ou o seu `id`. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |
| `Idempotency-Key` | cabeçalho | string | não | Identificador único deste pedido, para o repetir sem o duplicar. |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/domains/VALUE/verify" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/domains/VALUE/verify');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
r = requests.post("https://api.interweb.co.mz/v1/email/domains/VALUE/verify", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/domains/VALUE/verify", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Verificar um domínio remetente.

```json
{
  "data": {
    "id": 12,
    "domain": "example.com",
    "status": "verified",
    "mail_from": null,
    "verified_at": "2026-11-12T10:00:00+02:00",
    "last_checked_at": "2026-11-12T10:00:00+02:00",
    "created_at": "2026-11-12T09:40:00+02:00",
    "dns_records": [
      {
        "type": "CNAME",
        "name": "k3jd8s0a1b2c3d4e5f6a7b8c9d0e1f2a._domainkey.example.com",
        "value": "k3jd8s0a1b2c3d4e5f6a7b8c9d0e1f2a.dkim.example-brand.com",
        "priority": null,
        "purpose": "dkim",
        "required": true,
        "found": true
      },
      {
        "type": "TXT",
        "name": "example.com",
        "value": "v=spf1 include:_spf.example-brand.com ~all",
        "priority": null,
        "purpose": "spf",
        "required": false,
        "found": true
      },
      {
        "type": "TXT",
        "name": "_dmarc.example.com",
        "value": "v=DMARC1; p=none; adkim=r; aspf=r",
        "priority": null,
        "purpose": "dmarc",
        "required": false,
        "found": false
      }
    ],
    "test_mode_accepted": false,
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Definir o subdomínio do MAIL FROM

`PUT https://api.interweb.co.mz/v1/email/domains/{domain}/mail-from`

Precisa de `email_center:write`. Opcional: um subdomínio seu para o endereço de retorno (por exemplo `bounce`), para o SPF alinhar com o seu domínio no DMARC. Vazio volta ao nosso. Ponha o MX e o TXT que aparecem em `dns_records`.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `domain` | caminho | string | sim | O nome do domínio (example.com) ou o seu `id`. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `subdomain` | string, null | sim |  |

#### Exemplos

**curl**

```bash
curl -X PUT "https://api.interweb.co.mz/v1/email/domains/VALUE/mail-from" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Content-Type: application/json" \
  -d '{"subdomain": "bounce"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/domains/VALUE/mail-from');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_POSTFIELDS => '{"subdomain": "bounce"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
payload = {"subdomain": "bounce"}
r = requests.put("https://api.interweb.co.mz/v1/email/domains/VALUE/mail-from", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/domains/VALUE/mail-from", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"subdomain": "bounce"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Definir o subdomínio do MAIL FROM.

```json
{
  "data": {
    "id": 12,
    "domain": "example.com",
    "status": "verified",
    "mail_from": "bounce.example.com",
    "verified_at": "2026-11-12T10:00:00+02:00",
    "last_checked_at": "2026-11-12T10:00:00+02:00",
    "created_at": "2026-11-12T09:40:00+02:00",
    "dns_records": [
      {
        "type": "CNAME",
        "name": "k3jd8s0a1b2c3d4e5f6a7b8c9d0e1f2a._domainkey.example.com",
        "value": "k3jd8s0a1b2c3d4e5f6a7b8c9d0e1f2a.dkim.example-brand.com",
        "priority": null,
        "purpose": "dkim",
        "required": true,
        "found": true
      },
      {
        "type": "TXT",
        "name": "example.com",
        "value": "v=spf1 include:_spf.example-brand.com ~all",
        "priority": null,
        "purpose": "spf",
        "required": false,
        "found": true
      },
      {
        "type": "TXT",
        "name": "_dmarc.example.com",
        "value": "v=DMARC1; p=none; adkim=r; aspf=r",
        "priority": null,
        "purpose": "dmarc",
        "required": false,
        "found": false
      }
    ],
    "test_mode_accepted": false,
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Listar os modelos de e-mail

`GET https://api.interweb.co.mz/v1/email/templates`

Precisa de `email_center:read`. Por nome.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `page` | consulta | integer | não | O número da página. A primeira é a 1. |
| `per_page` | consulta | integer | não | Resultados por página, de 1 a 100. Por omissão, 10. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/templates" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/templates');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/templates", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/templates", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Listar os modelos de e-mail.

```json
{
  "data": [
    {
      "id": 7,
      "name": "Order ready",
      "subject": "Your order {{order}} is ready",
      "html": "<p>Hello {{name}}, your order {{order}} is ready.</p>",
      "text": "Hello {{name}}, your order {{order}} is ready.",
      "variables": [
        "order",
        "name"
      ],
      "created_at": "2026-11-12T09:00:00+02:00",
      "updated_at": "2026-11-12T09:00:00+02:00"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 10,
    "total": 1,
    "total_pages": 1
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Criar um modelo de e-mail

`POST https://api.interweb.co.mz/v1/email/templates`

Precisa de `email_center:write`. Assunto e html ou text, com marcas {{nome}} preenchidas pelas `variables` do envio.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `name` | string | sim | Até 100 caracteres, único na conta. |
| `subject` | string | sim |  |
| `html` | string | não |  |
| `text` | string | não |  |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/templates" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"name": "Order ready", "subject": "Your order {{order}} is ready", "html": "<p>Hello {{name}}, your order {{order}} is ready.</p>", "text": "Hello {{name}}, your order {{order}} is ready."}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/templates');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"name": "Order ready", "subject": "Your order {{order}} is ready", "html": "<p>Hello {{name}}, your order {{order}} is ready.</p>", "text": "Hello {{name}}, your order {{order}} is ready."}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"name": "Order ready", "subject": "Your order {{order}} is ready", "html": "<p>Hello {{name}}, your order {{order}} is ready.</p>", "text": "Hello {{name}}, your order {{order}} is ready."}
r = requests.post("https://api.interweb.co.mz/v1/email/templates", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/templates", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"name": "Order ready", "subject": "Your order {{order}} is ready", "html": "<p>Hello {{name}}, your order {{order}} is ready.</p>", "text": "Hello {{name}}, your order {{order}} is ready."}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

Criar um modelo de e-mail.

```json
{
  "data": {
    "id": 7,
    "name": "Order ready",
    "subject": "Your order {{order}} is ready",
    "html": "<p>Hello {{name}}, your order {{order}} is ready.</p>",
    "text": "Hello {{name}}, your order {{order}} is ready.",
    "variables": [
      "order",
      "name"
    ],
    "created_at": "2026-11-12T09:00:00+02:00",
    "updated_at": "2026-11-12T09:00:00+02:00"
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `409`

A operação não se pode fazer no estado actual. Veja `details.reason`.

```json
{
  "error": {
    "code": "conflict",
    "message": "This operation only works on an active domain.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "domain_not_active"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 409 | `conflict` | A operação não se pode fazer no estado actual. Veja `details.reason`. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Ver um modelo de e-mail

`GET https://api.interweb.co.mz/v1/email/templates/{id}`

Precisa de `email_center:read`. Com as variáveis que usa.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` do modelo (o mesmo `template_id` do envio). |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Ver um modelo de e-mail.

```json
{
  "data": {
    "id": 7,
    "name": "Order ready",
    "subject": "Your order {{order}} is ready",
    "html": "<p>Hello {{name}}, your order {{order}} is ready.</p>",
    "text": "Hello {{name}}, your order {{order}} is ready.",
    "variables": [
      "order",
      "name"
    ],
    "created_at": "2026-11-12T09:00:00+02:00",
    "updated_at": "2026-11-12T09:00:00+02:00"
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Alterar um modelo de e-mail

`PATCH https://api.interweb.co.mz/v1/email/templates/{id}`

Precisa de `email_center:write`. Só mudam os campos enviados.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` do modelo (o mesmo `template_id` do envio). |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `name` | string | não | Até 100 caracteres, único na conta. |
| `subject` | string | não |  |
| `html` | string | não |  |
| `text` | string | não |  |

#### Exemplos

**curl**

```bash
curl -X PATCH "https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Content-Type: application/json" \
  -d '{"subject": "Order {{order}}: ready for pickup"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_POSTFIELDS => '{"subject": "Order {{order}}: ready for pickup"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
payload = {"subject": "Order {{order}}: ready for pickup"}
r = requests.patch("https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"subject": "Order {{order}}: ready for pickup"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Alterar um modelo de e-mail.

```json
{
  "data": {
    "id": 7,
    "name": "Order ready",
    "subject": "Your order {{order}} is ready",
    "html": "<p>Hello {{name}}, your order {{order}} is ready.</p>",
    "text": "Hello {{name}}, your order {{order}} is ready.",
    "variables": [
      "order",
      "name"
    ],
    "created_at": "2026-11-12T09:00:00+02:00",
    "updated_at": "2026-11-12T09:00:00+02:00"
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

A operação não se pode fazer no estado actual. Veja `details.reason`.

```json
{
  "error": {
    "code": "conflict",
    "message": "This operation only works on an active domain.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "domain_not_active"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 409 | `conflict` | A operação não se pode fazer no estado actual. Veja `details.reason`. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Apagar um modelo de e-mail

`DELETE https://api.interweb.co.mz/v1/email/templates/{id}`

Precisa de `email_center:write`. Os e-mails já enviados não mudam.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` do modelo (o mesmo `template_id` do envio). |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl -X DELETE "https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.delete("https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/templates/00000000-0000-4000-8000-000000000000", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Apagar um modelo de e-mail.

```json
{
  "data": {
    "id": 7,
    "deleted": true,
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Listar os endereços bloqueados

`GET https://api.interweb.co.mz/v1/email/suppressions`

Precisa de `email_center:read`. Endereços que nunca recebem e-mails desta conta: as devoluções permanentes e as queixas entram sozinhas; pode juntar outros.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `reason` | consulta | string | não | bounce, complaint ou manual. |
| `email` | consulta | string | não | Um endereço. |
| `page` | consulta | integer | não | O número da página. A primeira é a 1. |
| `per_page` | consulta | integer | não | Resultados por página, de 1 a 100. Por omissão, 10. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/suppressions" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/suppressions');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/suppressions", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/suppressions", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Listar os endereços bloqueados.

```json
{
  "data": [
    {
      "email": "ana@example.org",
      "reason": "manual",
      "note": "Asked to stop",
      "created_at": "2026-11-12T10:00:00+02:00"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 10,
    "total": 1,
    "total_pages": 1
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Bloquear um endereço

`POST https://api.interweb.co.mz/v1/email/suppressions`

Precisa de `email_center:write`. Os envios para ele saem antes de enviar e nunca se cobram.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `email` | string | sim |  |
| `note` | string | não |  |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/suppressions" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"email": "ana@example.org", "note": "Asked to stop"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/suppressions');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"email": "ana@example.org", "note": "Asked to stop"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"email": "ana@example.org", "note": "Asked to stop"}
r = requests.post("https://api.interweb.co.mz/v1/email/suppressions", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/suppressions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"email": "ana@example.org", "note": "Asked to stop"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

Bloquear um endereço.

```json
{
  "data": {
    "email": "ana@example.org",
    "reason": "manual",
    "note": "Asked to stop",
    "created_at": "2026-11-12T10:00:00+02:00",
    "already_listed": false,
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Desbloquear um endereço

`DELETE https://api.interweb.co.mz/v1/email/suppressions/{email}`

Precisa de `email_center:write`. O endereço vai no caminho, codificado (`ana%40example.org`).

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `email` | caminho | string | sim | O endereço, codificado. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl -X DELETE "https://api.interweb.co.mz/v1/email/suppressions/VALUE" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/suppressions/VALUE');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.delete("https://api.interweb.co.mz/v1/email/suppressions/VALUE", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/suppressions/VALUE", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Desbloquear um endereço.

```json
{
  "data": {
    "email": "ana@example.org",
    "deleted": true,
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Ver o webhook de e-mail

`GET https://api.interweb.co.mz/v1/email/webhook`

Precisa de `email_center:read`. O endereço, os eventos e a última entrega; nunca o segredo. Fazemos POST de `{"event": "email.delivered", "created_at", "data": {"id", "event_id", "recipient", "detail", "from", "subject", "simulated"}}` para o seu endereço HTTPS na porta 443, uma chamada por evento e destinatário, só para eventos depois de o webhook ser gravado. Confira `X-Webhook-Signature: t=<hora unix>,v1=<hex>`: v1 é o HMAC-SHA256 de `t + "." + corpo cru` com o seu segredo. Responda 2xx em 5 segundos; senão tentamos de novo após 1 min, 5 min, 30 min, 2 h e 6 h. Este webhook é separado do do SMS.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/webhook" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/webhook');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/webhook", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/webhook", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Ver o webhook de e-mail.

```json
{
  "data": {
    "url": "https://example.com/webhooks/email",
    "events": [
      "email.delivered",
      "email.bounced",
      "email.complained"
    ],
    "created_at": "2026-11-12T10:30:00+02:00",
    "updated_at": "2026-11-12T10:30:00+02:00",
    "last_delivery_at": null,
    "last_status_code": null,
    "last_error": null
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Gravar o webhook de e-mail

`PUT https://api.interweb.co.mz/v1/email/webhook`

Precisa de `email_center:write`. O `secret` só aparece quando o webhook nasce ou com `rotate_secret: true`. Fazemos POST de `{"event": "email.delivered", "created_at", "data": {"id", "event_id", "recipient", "detail", "from", "subject", "simulated"}}` para o seu endereço HTTPS na porta 443, uma chamada por evento e destinatário, só para eventos depois de o webhook ser gravado. Confira `X-Webhook-Signature: t=<hora unix>,v1=<hex>`: v1 é o HMAC-SHA256 de `t + "." + corpo cru` com o seu segredo. Responda 2xx em 5 segundos; senão tentamos de novo após 1 min, 5 min, 30 min, 2 h e 6 h. Este webhook é separado do do SMS.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `url` | string | sim |  |
| `events` | array<string: email.sent, email.delivered, email.bounced, email.complained, email.failed> | não |  |
| `rotate_secret` | boolean | não |  |

#### Exemplos

**curl**

```bash
curl -X PUT "https://api.interweb.co.mz/v1/email/webhook" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/webhooks/email", "events": ["email.delivered", "email.bounced", "email.complained"]}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/webhook');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_POSTFIELDS => '{"url": "https://example.com/webhooks/email", "events": ["email.delivered", "email.bounced", "email.complained"]}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
payload = {"url": "https://example.com/webhooks/email", "events": ["email.delivered", "email.bounced", "email.complained"]}
r = requests.put("https://api.interweb.co.mz/v1/email/webhook", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/webhook", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"url": "https://example.com/webhooks/email", "events": ["email.delivered", "email.bounced", "email.complained"]}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Gravar o webhook de e-mail.

```json
{
  "data": {
    "url": "https://example.com/webhooks/email",
    "events": [
      "email.delivered",
      "email.bounced",
      "email.complained"
    ],
    "created_at": "2026-11-12T10:30:00+02:00",
    "updated_at": "2026-11-12T10:30:00+02:00",
    "last_delivery_at": null,
    "last_status_code": null,
    "last_error": null,
    "secret": "whsec_8c1f0e2d4b6a7c9e1f3a5b7d9e0c2a4b6d8f0a1c3e5b7d9f",
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Apagar o webhook de e-mail

`DELETE https://api.interweb.co.mz/v1/email/webhook`

Precisa de `email_center:write`. As entregas por fazer são canceladas.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl -X DELETE "https://api.interweb.co.mz/v1/email/webhook" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/webhook');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.delete("https://api.interweb.co.mz/v1/email/webhook", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/webhook", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Apagar o webhook de e-mail.

```json
{
  "data": {
    "deleted": true,
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Testar o webhook de e-mail

`POST https://api.interweb.co.mz/v1/email/webhook/test`

Precisa de `email_center:write`. Envia já um evento `webhook.test` assinado para o endereço gravado e devolve o que o seu servidor respondeu.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |
| `Idempotency-Key` | cabeçalho | string | não | Identificador único deste pedido, para o repetir sem o duplicar. |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/webhook/test" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/webhook/test');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
r = requests.post("https://api.interweb.co.mz/v1/email/webhook/test", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/webhook/test", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Testar o webhook de e-mail.

```json
{
  "data": {
    "delivered": true,
    "status_code": 200,
    "error": null
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Listar as campanhas

`GET https://api.interweb.co.mz/v1/email/campaigns`

Precisa de `email_center:read`. As mais recentes primeiro, sem os blocos.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `status` | consulta | string | não | draft, scheduled, sending, sent, cancelled ou automation. |
| `page` | consulta | integer | não | O número da página. A primeira é a 1. |
| `per_page` | consulta | integer | não | Resultados por página, de 1 a 100. Por omissão, 10. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/campaigns" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/campaigns", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Listar as campanhas.

```json
{
  "data": [
    {
      "id": 41,
      "name": "November news",
      "type": "campaign",
      "status": "draft",
      "subject": "New arrivals for you",
      "preheader": "Special prices until the end of the month",
      "from": "news@example.com",
      "from_name": "Example Ltd",
      "reply_to": null,
      "recipients": 0,
      "credits": 0.0,
      "simulated": false,
      "scheduled_for": null,
      "sent_at": null,
      "created_at": "2026-11-12T10:00:00+02:00",
      "updated_at": "2026-11-12T10:05:00+02:00",
      "blocks": null,
      "target": null
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 10,
    "total": 1,
    "total_pages": 1
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Criar uma campanha

`POST https://api.interweb.co.mz/v1/email/campaigns`

Precisa de `email_center:write`. Cria um rascunho. Envie `blocks`, ou `template` para um modelo pronto. Variáveis: {{first_name}}, {{name}}, {{company}}, {{email}}. Com uma chave sandbox nada se guarda.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `name` | string | não |  |
| `subject` | string | não |  |
| `preheader` | string | não | Texto que aparece a seguir ao assunto na caixa de entrada. |
| `from` | string | não | Um endereço de um domínio verificado. |
| `from_name` | string | não |  |
| `reply_to` | string | não |  |
| `blocks` | array<object> | não |  |
| `template` | string | não | Em vez de blocks: um modelo pronto por sector (restauracao, comercio, servicos, educacao, saude, eventos, imobiliario, tecnologia). |
| `target` | object | não |  |
| `target.groups` | array<integer> | não | Ids dos grupos; vazio quer dizer todos os contactos. |
| `target.opened` | string, null | não | yes ou no: já abriu alguma campanha. |
| `target.clicked` | string, null | não | yes ou no. |
| `target.joined_from` | string, null | não | AAAA-MM-DD. |
| `target.joined_to` | string, null | não | AAAA-MM-DD. |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/campaigns" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"name": "November news", "template": "comercio", "from": "news@example.com", "from_name": "Example Ltd", "target": {"groups": [3]}}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"name": "November news", "template": "comercio", "from": "news@example.com", "from_name": "Example Ltd", "target": {"groups": [3]}}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"name": "November news", "template": "comercio", "from": "news@example.com", "from_name": "Example Ltd", "target": {"groups": [3]}}
r = requests.post("https://api.interweb.co.mz/v1/email/campaigns", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"name": "November news", "template": "comercio", "from": "news@example.com", "from_name": "Example Ltd", "target": {"groups": [3]}}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

Criar uma campanha.

```json
{
  "data": {
    "id": 41,
    "name": "November news",
    "type": "campaign",
    "status": "draft",
    "subject": "New arrivals for you",
    "preheader": "Special prices until the end of the month",
    "from": "news@example.com",
    "from_name": "Example Ltd",
    "reply_to": null,
    "recipients": 0,
    "credits": 0.0,
    "simulated": false,
    "scheduled_for": null,
    "sent_at": null,
    "created_at": "2026-11-12T10:00:00+02:00",
    "updated_at": "2026-11-12T10:05:00+02:00",
    "blocks": [
      {
        "type": "header",
        "title": "New arrivals for you",
        "logo": "",
        "color": "#af2835"
      },
      {
        "type": "text",
        "text": "Hi {{first_name}},\n\nNew products have arrived, with **special prices** until the end of the month."
      },
      {
        "type": "button",
        "text": "See what is new",
        "url": "https://example.com",
        "color": "#af2835"
      },
      {
        "type": "divider"
      },
      {
        "type": "footer",
        "text": "You are receiving this email because you subscribed to our news."
      }
    ],
    "target": {
      "groups": [
        3
      ],
      "opened": null,
      "clicked": null,
      "joined_from": null,
      "joined_to": null
    }
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.target.groups` | array<integer> | Ids dos grupos; vazio quer dizer todos os contactos. |
| `data.target.opened` | string, null | yes ou no: já abriu alguma campanha. |
| `data.target.clicked` | string, null | yes ou no. |
| `data.target.joined_from` | string, null | AAAA-MM-DD. |
| `data.target.joined_to` | string, null | AAAA-MM-DD. |

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `409`

A operação não se pode fazer no estado actual. Veja `details.reason`.

```json
{
  "error": {
    "code": "conflict",
    "message": "This operation only works on an active domain.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "domain_not_active"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 409 | `conflict` | A operação não se pode fazer no estado actual. Veja `details.reason`. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Gerar uma campanha com IA

`POST https://api.interweb.co.mz/v1/email/campaigns/generate`

Precisa de `email_center:write`. Descreva o que quer enviar; a resposta traz o assunto, a pré-visualização e os blocos, que depois pode mandar para POST /v1/email/campaigns. Nada se guarda. 503 `ai_unavailable` se a IA estiver desligada; conta para o limite diário da IA.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |
| `Idempotency-Key` | cabeçalho | string | não | Identificador único deste pedido, para o repetir sem o duplicar. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `description` | string | sim | 10 a 1500 caracteres. |
| `sector` | string | não |  |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/campaigns/generate" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"description": "20% off all plans until Friday, for existing customers.", "sector": "tecnologia"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns/generate');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"description": "20% off all plans until Friday, for existing customers.", "sector": "tecnologia"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"description": "20% off all plans until Friday, for existing customers.", "sector": "tecnologia"}
r = requests.post("https://api.interweb.co.mz/v1/email/campaigns/generate", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns/generate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"description": "20% off all plans until Friday, for existing customers.", "sector": "tecnologia"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Gerar uma campanha com IA.

```json
{
  "data": {
    "subject": "Our November offers",
    "preheader": "20% off until Friday",
    "blocks": [
      {
        "type": "header",
        "title": "New arrivals for you",
        "logo": "",
        "color": "#af2835"
      },
      {
        "type": "text",
        "text": "Hi {{first_name}},\n\nNew products have arrived, with **special prices** until the end of the month."
      },
      {
        "type": "button",
        "text": "See what is new",
        "url": "https://example.com",
        "color": "#af2835"
      },
      {
        "type": "divider"
      },
      {
        "type": "footer",
        "text": "You are receiving this email because you subscribed to our news."
      }
    ]
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `502`

O serviço por trás da API não respondeu como esperado.

```json
{
  "error": {
    "code": "upstream_error",
    "message": "The service did not respond as expected. Please try again later.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 502 | `upstream_error` | O serviço por trás da API não respondeu como esperado. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Ver uma campanha

`GET https://api.interweb.co.mz/v1/email/campaigns/{id}`

Precisa de `email_center:read`. Com os blocos e o público.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` da campanha. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Ver uma campanha.

```json
{
  "data": {
    "id": 41,
    "name": "November news",
    "type": "campaign",
    "status": "draft",
    "subject": "New arrivals for you",
    "preheader": "Special prices until the end of the month",
    "from": "news@example.com",
    "from_name": "Example Ltd",
    "reply_to": null,
    "recipients": 0,
    "credits": 0.0,
    "simulated": false,
    "scheduled_for": null,
    "sent_at": null,
    "created_at": "2026-11-12T10:00:00+02:00",
    "updated_at": "2026-11-12T10:05:00+02:00",
    "blocks": [
      {
        "type": "header",
        "title": "New arrivals for you",
        "logo": "",
        "color": "#af2835"
      },
      {
        "type": "text",
        "text": "Hi {{first_name}},\n\nNew products have arrived, with **special prices** until the end of the month."
      },
      {
        "type": "button",
        "text": "See what is new",
        "url": "https://example.com",
        "color": "#af2835"
      },
      {
        "type": "divider"
      },
      {
        "type": "footer",
        "text": "You are receiving this email because you subscribed to our news."
      }
    ],
    "target": {
      "groups": [
        3
      ],
      "opened": null,
      "clicked": null,
      "joined_from": null,
      "joined_to": null
    }
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.target.groups` | array<integer> | Ids dos grupos; vazio quer dizer todos os contactos. |
| `data.target.opened` | string, null | yes ou no: já abriu alguma campanha. |
| `data.target.clicked` | string, null | yes ou no. |
| `data.target.joined_from` | string, null | AAAA-MM-DD. |
| `data.target.joined_to` | string, null | AAAA-MM-DD. |

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Alterar uma campanha

`PATCH https://api.interweb.co.mz/v1/email/campaigns/{id}`

Precisa de `email_center:write`. Só rascunhos e e-mails de automação; só mudam os campos enviados.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` da campanha. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `name` | string | não |  |
| `subject` | string | não |  |
| `preheader` | string | não | Texto que aparece a seguir ao assunto na caixa de entrada. |
| `from` | string | não | Um endereço de um domínio verificado. |
| `from_name` | string | não |  |
| `reply_to` | string | não |  |
| `blocks` | array<object> | não |  |
| `template` | string | não | Em vez de blocks: um modelo pronto por sector (restauracao, comercio, servicos, educacao, saude, eventos, imobiliario, tecnologia). |
| `target` | object | não |  |
| `target.groups` | array<integer> | não | Ids dos grupos; vazio quer dizer todos os contactos. |
| `target.opened` | string, null | não | yes ou no: já abriu alguma campanha. |
| `target.clicked` | string, null | não | yes ou no. |
| `target.joined_from` | string, null | não | AAAA-MM-DD. |
| `target.joined_to` | string, null | não | AAAA-MM-DD. |

#### Exemplos

**curl**

```bash
curl -X PATCH "https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Content-Type: application/json" \
  -d '{"subject": "Last days of our offers"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_POSTFIELDS => '{"subject": "Last days of our offers"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
payload = {"subject": "Last days of our offers"}
r = requests.patch("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"subject": "Last days of our offers"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Alterar uma campanha.

```json
{
  "data": {
    "id": 41,
    "name": "November news",
    "type": "campaign",
    "status": "draft",
    "subject": "New arrivals for you",
    "preheader": "Special prices until the end of the month",
    "from": "news@example.com",
    "from_name": "Example Ltd",
    "reply_to": null,
    "recipients": 0,
    "credits": 0.0,
    "simulated": false,
    "scheduled_for": null,
    "sent_at": null,
    "created_at": "2026-11-12T10:00:00+02:00",
    "updated_at": "2026-11-12T10:05:00+02:00",
    "blocks": [
      {
        "type": "header",
        "title": "New arrivals for you",
        "logo": "",
        "color": "#af2835"
      },
      {
        "type": "text",
        "text": "Hi {{first_name}},\n\nNew products have arrived, with **special prices** until the end of the month."
      },
      {
        "type": "button",
        "text": "See what is new",
        "url": "https://example.com",
        "color": "#af2835"
      },
      {
        "type": "divider"
      },
      {
        "type": "footer",
        "text": "You are receiving this email because you subscribed to our news."
      }
    ],
    "target": {
      "groups": [
        3
      ],
      "opened": null,
      "clicked": null,
      "joined_from": null,
      "joined_to": null
    }
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.target.groups` | array<integer> | Ids dos grupos; vazio quer dizer todos os contactos. |
| `data.target.opened` | string, null | yes ou no: já abriu alguma campanha. |
| `data.target.clicked` | string, null | yes ou no. |
| `data.target.joined_from` | string, null | AAAA-MM-DD. |
| `data.target.joined_to` | string, null | AAAA-MM-DD. |

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

A operação não se pode fazer no estado actual. Veja `details.reason`.

```json
{
  "error": {
    "code": "conflict",
    "message": "This operation only works on an active domain.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "domain_not_active"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 409 | `conflict` | A operação não se pode fazer no estado actual. Veja `details.reason`. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Apagar uma campanha

`DELETE https://api.interweb.co.mz/v1/email/campaigns/{id}`

Precisa de `email_center:write`. Só rascunhos e e-mails de automação.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` da campanha. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl -X DELETE "https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.delete("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Apagar uma campanha.

```json
{
  "data": {
    "id": 41,
    "deleted": true,
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

A operação não se pode fazer no estado actual. Veja `details.reason`.

```json
{
  "error": {
    "code": "conflict",
    "message": "This operation only works on an active domain.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "domain_not_active"
    }
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 409 | `conflict` | A operação não se pode fazer no estado actual. Veja `details.reason`. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Verificar a campanha contra spam

`GET https://api.interweb.co.mz/v1/email/campaigns/{id}/spam-check`

Precisa de `email_center:read`. Regras simples (assunto em maiúsculas, palavras de spam, pouco texto, só imagens, ligações encurtadas, remetente não verificado). A partir de `blocks_sending_at` pontos a campanha não se envia.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` da campanha. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/spam-check" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/spam-check');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/spam-check", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/spam-check", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Verificar a campanha contra spam.

```json
{
  "data": {
    "score": 1,
    "max": 10,
    "blocks_sending_at": 8,
    "warnings": [
      {
        "code": "preheader_missing"
      }
    ]
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Enviar a campanha já

`POST https://api.interweb.co.mz/v1/email/campaigns/{id}/send`

Precisa de `email_center:write`. Só recebem os contactos subscritos com e-mail e fora da lista de bloqueados; cada um é um crédito (sai já e volta para os que não saírem). Cada e-mail leva a ligação de cancelar a subscrição e o cabeçalho `List-Unsubscribe`, mais um pixel e ligações com redireccionamento assinado para as estatísticas. Uma campanha que chegue ao limite do verificador de spam é recusada (422 `spam_score_too_high`). Com uma chave sandbox, ou com o Email Center em modo de teste, nada se envia nem se cobra e os números são simulados (`simulated: true`).

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` da campanha. |
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/send" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/send');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
r = requests.post("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/send", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/send", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Enviar a campanha já.

```json
{
  "data": {
    "id": 41,
    "name": "November news",
    "type": "campaign",
    "status": "sending",
    "subject": "New arrivals for you",
    "preheader": "Special prices until the end of the month",
    "from": "news@example.com",
    "from_name": "Example Ltd",
    "reply_to": null,
    "recipients": 1200,
    "credits": 1200.0,
    "simulated": false,
    "scheduled_for": null,
    "sent_at": null,
    "created_at": "2026-11-12T10:00:00+02:00",
    "updated_at": "2026-11-12T10:05:00+02:00",
    "blocks": null,
    "target": null,
    "stats": {
      "recipients": 1200,
      "sent": 0,
      "delivered": 0,
      "bounced": 12,
      "opened": 0,
      "clicked": 0,
      "unsubscribed": 4,
      "open_rate": 43.77,
      "click_rate": 11.03,
      "unsubscribe_rate": 0.34,
      "bounce_rate": 1.0,
      "by_status": {
        "enviado": 1200
      },
      "top_links": [
        {
          "url": "https://example.com",
          "clicks": 131
        }
      ],
      "simulated": false
    }
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.target.groups` | array<integer> | Ids dos grupos; vazio quer dizer todos os contactos. |
| `data.target.opened` | string, null | yes ou no: já abriu alguma campanha. |
| `data.target.clicked` | string, null | yes ou no. |
| `data.target.joined_from` | string, null | AAAA-MM-DD. |
| `data.target.joined_to` | string, null | AAAA-MM-DD. |

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

A operação não se pode fazer no estado actual. Veja `details.reason`.

```json
{
  "error": {
    "code": "conflict",
    "message": "This operation only works on an active domain.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "domain_not_active"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 409 | `conflict` | A operação não se pode fazer no estado actual. Veja `details.reason`. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Agendar a campanha

`POST https://api.interweb.co.mz/v1/email/campaigns/{id}/schedule`

Precisa de `email_center:write`. `send_at` em ISO 8601 com o fuso, até 30 dias. Só recebem os contactos subscritos com e-mail e fora da lista de bloqueados; cada um é um crédito (sai já e volta para os que não saírem). Cada e-mail leva a ligação de cancelar a subscrição e o cabeçalho `List-Unsubscribe`, mais um pixel e ligações com redireccionamento assinado para as estatísticas. Uma campanha que chegue ao limite do verificador de spam é recusada (422 `spam_score_too_high`). Com uma chave sandbox, ou com o Email Center em modo de teste, nada se envia nem se cobra e os números são simulados (`simulated: true`).

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` da campanha. |
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `send_at` | string | sim |  |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/schedule" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"send_at": "2026-11-20T09:00:00+02:00"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/schedule');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"send_at": "2026-11-20T09:00:00+02:00"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"send_at": "2026-11-20T09:00:00+02:00"}
r = requests.post("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/schedule", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/schedule", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"send_at": "2026-11-20T09:00:00+02:00"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Agendar a campanha.

```json
{
  "data": {
    "id": 41,
    "name": "November news",
    "type": "campaign",
    "status": "scheduled",
    "subject": "New arrivals for you",
    "preheader": "Special prices until the end of the month",
    "from": "news@example.com",
    "from_name": "Example Ltd",
    "reply_to": null,
    "recipients": 1200,
    "credits": 1200.0,
    "simulated": false,
    "scheduled_for": "2026-11-20T09:00:00+02:00",
    "sent_at": null,
    "created_at": "2026-11-12T10:00:00+02:00",
    "updated_at": "2026-11-12T10:05:00+02:00",
    "blocks": null,
    "target": null,
    "stats": {
      "recipients": 1200,
      "sent": 0,
      "delivered": 0,
      "bounced": 12,
      "opened": 0,
      "clicked": 0,
      "unsubscribed": 4,
      "open_rate": 43.77,
      "click_rate": 11.03,
      "unsubscribe_rate": 0.34,
      "bounce_rate": 1.0,
      "by_status": {
        "enviado": 1200
      },
      "top_links": [
        {
          "url": "https://example.com",
          "clicks": 131
        }
      ],
      "simulated": false
    }
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.target.groups` | array<integer> | Ids dos grupos; vazio quer dizer todos os contactos. |
| `data.target.opened` | string, null | yes ou no: já abriu alguma campanha. |
| `data.target.clicked` | string, null | yes ou no. |
| `data.target.joined_from` | string, null | AAAA-MM-DD. |
| `data.target.joined_to` | string, null | AAAA-MM-DD. |

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

A operação não se pode fazer no estado actual. Veja `details.reason`.

```json
{
  "error": {
    "code": "conflict",
    "message": "This operation only works on an active domain.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "domain_not_active"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 409 | `conflict` | A operação não se pode fazer no estado actual. Veja `details.reason`. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Cancelar a campanha

`POST https://api.interweb.co.mz/v1/email/campaigns/{id}/cancel`

Precisa de `email_center:write`. Agendada ou ainda a enviar: o que não saiu já não sai, e os créditos voltam (`credits_returned`).

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` da campanha. |
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/cancel" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/cancel');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
r = requests.post("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/cancel", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/cancel", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Cancelar a campanha.

```json
{
  "data": {
    "id": 41,
    "name": "November news",
    "type": "campaign",
    "status": "cancelled",
    "subject": "New arrivals for you",
    "preheader": "Special prices until the end of the month",
    "from": "news@example.com",
    "from_name": "Example Ltd",
    "reply_to": null,
    "recipients": 0,
    "credits": 0.0,
    "simulated": false,
    "scheduled_for": null,
    "sent_at": null,
    "created_at": "2026-11-12T10:00:00+02:00",
    "updated_at": "2026-11-12T10:05:00+02:00",
    "blocks": null,
    "target": null,
    "credits_returned": 1200.0
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.target.groups` | array<integer> | Ids dos grupos; vazio quer dizer todos os contactos. |
| `data.target.opened` | string, null | yes ou no: já abriu alguma campanha. |
| `data.target.clicked` | string, null | yes ou no. |
| `data.target.joined_from` | string, null | AAAA-MM-DD. |
| `data.target.joined_to` | string, null | AAAA-MM-DD. |

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

A operação não se pode fazer no estado actual. Veja `details.reason`.

```json
{
  "error": {
    "code": "conflict",
    "message": "This operation only works on an active domain.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "domain_not_active"
    }
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 409 | `conflict` | A operação não se pode fazer no estado actual. Veja `details.reason`. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Ver as estatísticas da campanha

`GET https://api.interweb.co.mz/v1/email/campaigns/{id}/stats`

Precisa de `email_center:read`. Entregues, aberturas (pixel), cliques (ligações assinadas), cancelamentos e devoluções; taxas sobre os entregues. `simulated` marca os números de exemplo do modo de teste.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` da campanha. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/stats" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/stats');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/stats", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/stats", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Ver as estatísticas da campanha.

```json
{
  "data": {
    "recipients": 1200,
    "sent": 1200,
    "delivered": 1188,
    "bounced": 12,
    "opened": 520,
    "clicked": 131,
    "unsubscribed": 4,
    "open_rate": 43.77,
    "click_rate": 11.03,
    "unsubscribe_rate": 0.34,
    "bounce_rate": 1.0,
    "by_status": {
      "enviado": 1200
    },
    "top_links": [
      {
        "url": "https://example.com",
        "clicks": 131
      }
    ],
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Listar os destinatários da campanha

`GET https://api.interweb.co.mz/v1/email/campaigns/{id}/recipients`

Precisa de `email_center:read`. O que cada contacto fez: abriu, clicou, cancelou ou devolveu.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` da campanha. |
| `page` | consulta | integer | não | O número da página. A primeira é a 1. |
| `per_page` | consulta | integer | não | Resultados por página, de 1 a 100. Por omissão, 10. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/recipients" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/recipients');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/recipients", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/recipients", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Listar os destinatários da campanha.

```json
{
  "data": [
    {
      "email": "ana@example.org",
      "status": "sent",
      "simulated": false,
      "sent_at": "2026-11-12T10:30:00+02:00",
      "opened_at": "2026-11-12T11:02:00+02:00",
      "opens": 2,
      "clicked_at": null,
      "clicks": 0,
      "unsubscribed_at": null,
      "bounced_at": null
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 10,
    "total": 1,
    "total_pages": 1
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Enviar um teste da campanha

`POST https://api.interweb.co.mz/v1/email/campaigns/{id}/test`

Precisa de `email_center:write`. Um e-mail para um endereço, com variáveis de exemplo e «[Teste]» no assunto; custa um crédito, como qualquer e-mail.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` da campanha. |
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `to` | string | sim |  |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/test" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"to": "ana@example.org"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/test');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"to": "ana@example.org"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"to": "ana@example.org"}
r = requests.post("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/test", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/campaigns/00000000-0000-4000-8000-000000000000/test", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"to": "ana@example.org"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

Enviar um teste da campanha.

```json
{
  "data": {
    "id": "5130",
    "status": "sent"
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.recipients` | integer | Destinatários depois da lista de bloqueados; cada um é um crédito. |
| `data.source` | string | portal, api ou smtp. |

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Listar os contactos de e-mail

`GET https://api.interweb.co.mz/v1/email/contacts`

Precisa de `email_center:read`. Os contactos do SMS com e-mail e os contactos só de e-mail, sem endereços repetidos, com o estado da subscrição.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `group` | consulta | string | não | Id do grupo. |
| `subscription` | consulta | string | não | subscribed, unsubscribed ou suppressed. |
| `opened` | consulta | string | não | yes ou no. |
| `clicked` | consulta | string | não | yes ou no. |
| `joined_from` | consulta | string | não | AAAA-MM-DD. |
| `joined_to` | consulta | string | não | AAAA-MM-DD. |
| `q` | consulta | string | não | Procura. |
| `page` | consulta | integer | não | O número da página. A primeira é a 1. |
| `per_page` | consulta | integer | não | Resultados por página, de 1 a 100. Por omissão, 10. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/contacts" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/contacts');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/contacts", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/contacts", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Listar os contactos de e-mail.

```json
{
  "data": [
    {
      "email": "ana@example.org",
      "name": "Ana",
      "company": "Example Ltd",
      "source": "email_contact",
      "subscription": "subscribed",
      "created_at": "2026-11-12T09:00:00+02:00"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 10,
    "total": 1,
    "total_pages": 1
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Juntar um contacto de e-mail

`POST https://api.interweb.co.mz/v1/email/contacts`

Precisa de `email_center:write`. Se o endereço já for de um contacto do SMS, usa-se esse contacto e junta-se aos grupos. Confirma que a pessoa aceitou receber os seus e-mails.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `Idempotency-Key` | cabeçalho | string | não | Um valor único qualquer, até 255 caracteres visíveis. Vale 24 horas por chave. Repetir o mesmo pedido devolve a resposta guardada; outro corpo, ou um primeiro pedido ainda em curso, devolve 409. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `email` | string | sim |  |
| `name` | string | não |  |
| `company` | string | não |  |
| `groups` | array<integer> | não |  |
| `subscription` | string | não | subscribed (por omissão) ou unsubscribed. |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/email/contacts" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"email": "ana@example.org", "name": "Ana", "groups": [3]}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/contacts');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"email": "ana@example.org", "name": "Ana", "groups": [3]}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Idempotency-Key: 9f1c2d3e-example',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"email": "ana@example.org", "name": "Ana", "groups": [3]}
r = requests.post("https://api.interweb.co.mz/v1/email/contacts", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/contacts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"email": "ana@example.org", "name": "Ana", "groups": [3]}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

Juntar um contacto de e-mail.

```json
{
  "data": {
    "email": "ana@example.org",
    "name": "Ana",
    "company": "Example Ltd",
    "source": "email_contact",
    "subscription": "subscribed",
    "created_at": "2026-11-12T09:00:00+02:00",
    "created": true,
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Ver um contacto de e-mail

`GET https://api.interweb.co.mz/v1/email/contacts/{email}`

Precisa de `email_center:read`. Com as últimas campanhas que recebeu e o que fez.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `email` | caminho | string | sim | O endereço, codificado. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/contacts/VALUE" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/contacts/VALUE');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/contacts/VALUE", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/contacts/VALUE", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Ver um contacto de e-mail.

```json
{
  "data": {
    "email": "ana@example.org",
    "name": "Ana",
    "company": "Example Ltd",
    "source": "email_contact",
    "subscription": "subscribed",
    "created_at": "2026-11-12T09:00:00+02:00",
    "campaigns": [
      {
        "campaign_id": 41,
        "sent_at": "2026-11-12T10:30:00+02:00",
        "opened": true,
        "clicked": false,
        "unsubscribed": false,
        "simulated": false
      }
    ]
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Mudar a subscrição

`PUT https://api.interweb.co.mz/v1/email/contacts/{email}/subscription`

Precisa de `email_center:write`. `subscribed` ou `unsubscribed`. Quem cancelou pela ligação só deve voltar a ser subscrito com o seu consentimento.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:write`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `email` | caminho | string | sim | O endereço, codificado. |
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Corpo do pedido

| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `status` | string | sim |  |

#### Exemplos

**curl**

```bash
curl -X PUT "https://api.interweb.co.mz/v1/email/contacts/VALUE/subscription" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Content-Type: application/json" \
  -d '{"status": "unsubscribed"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/contacts/VALUE/subscription');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_POSTFIELDS => '{"status": "unsubscribed"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
        'Content-Type: application/json',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
payload = {"status": "unsubscribed"}
r = requests.put("https://api.interweb.co.mz/v1/email/contacts/VALUE/subscription", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/contacts/VALUE/subscription", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"status": "unsubscribed"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Mudar a subscrição.

```json
{
  "data": {
    "email": "ana@example.org",
    "subscription": "unsubscribed",
    "simulated": false
  },
  "meta": {}
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `404`

Não encontrado.

```json
{
  "error": {
    "code": "not_found",
    "message": "Não encontrado.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `422`

Alguns dados não são válidos.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Alguns dados não são válidos.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 404 | `not_found` | Não encontrado. |
| 422 | `validation_failed` | Alguns dados não são válidos. |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).


### Listar os grupos de contactos

`GET https://api.interweb.co.mz/v1/email/groups`

Precisa de `email_center:read`. Os mesmos grupos dos contactos do SMS, com quantos têm e-mail.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `email_center:read`.

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `X-API-Mode` | cabeçalho | string: sandbox, production | não | Opcional. Se não coincidir com o modo da chave, o pedido é recusado com `invalid_request` e a razão `mode_mismatch`. Serve para garantir que um programa nunca corre com a chave errada. |

#### Exemplos

**curl**

```bash
curl "https://api.interweb.co.mz/v1/email/groups" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/email/groups');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: pt',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $status, PHP_EOL, $body, PHP_EOL;
```

**Python**

```python
import os
import requests

headers = {
    "Authorization": "Bearer " + os.environ["RLX_API_KEY"],
    "Accept-Language": "pt",
}
r = requests.get("https://api.interweb.co.mz/v1/email/groups", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/email/groups", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Listar os grupos de contactos.

```json
{
  "data": [
    {
      "id": 3,
      "name": "Customers",
      "contacts": 1200
    }
  ],
  "meta": {
    "total": 1
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `X-Correlation-ID` | Identificador deste pedido. Indique-o ao suporte se comunicar um problema. |
| `X-RateLimit-Limit` | Pedidos permitidos por minuto. |
| `X-RateLimit-Remaining` | Pedidos que restam no minuto actual. |
| `X-RateLimit-Reset` | Quando acaba o minuto, em segundos Unix. |
| `X-API-Mode` | O modo da chave que fez o pedido: `sandbox` ou `production`. |

##### `401`

A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Chave da API em falta ou inválida.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`).

```json
{
  "error": {
    "code": "forbidden",
    "message": "This account does not have Email Center active. Turn it on in the portal.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "email_account_required"
    }
  }
}
```

##### `429`

Demasiados pedidos. Espere o tempo indicado em `Retry-After`.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiados pedidos. Tente de novo daqui a pouco.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Cabeçalho | Descrição |
|---|---|
| `Retry-After` | Segundos a esperar antes de tentar de novo. |

##### `500`

Ocorreu um erro interno. Indique o `correlation_id` ao suporte.

```json
{
  "error": {
    "code": "internal_error",
    "message": "Ocorreu um erro interno. Indique o correlation_id ao suporte.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

A API está desligada.

```json
{
  "error": {
    "code": "disabled",
    "message": "A API está desligada de momento.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Erros

| Estado | Código | Significado |
|---|---|---|
| 401 | `unauthorized` | A chave está em falta, não é válida, foi revogada ou passou da validade, ou quem a criou já não tem acesso à conta. |
| 403 | `forbidden`, `insufficient_scope` | A conta não tem o Email Center activo (`forbidden`, `details.reason` = `email_account_required`), ou a chave não tem a permissão (`insufficient_scope`). |
| 429 | `rate_limited` | Demasiados pedidos. Espere o tempo indicado em `Retry-After`. |
| 500 | `internal_error` | Ocorreu um erro interno. Indique o `correlation_id` ao suporte. |
| 503 | `disabled` | A API está desligada. |

O formato dos erros está explicado em [Erros](https://docs.interweb.co.mz/api-rules.md).
