# SMS

Envie SMS pela API e veja o histórico, as estatísticas, o saldo, os modelos, os remetentes e os números que não recebem.

Actualizado em 10/11/2026

## O que precisa

- Uma conta revendedora de SMS activa. Sem ela, a API responde `403` com o código `sms_reseller_required`.
- Uma chave com a permissão `sms:write` para enviar, e `sms:read` para consultar. Veja [Permissões](https://docs.interweb.ao/pt/permissions.md).

## O envio

Use [`POST /v1/sms/messages`](https://docs.interweb.ao/pt/sms.md) com o número e o texto:

**curl**

```bash
curl -X POST "https://api.interweb.ao/v1/sms/messages" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"number": "+258841234567", "text": "Your order is ready.", "send_at": "2026-11-20T09:30:00+02:00"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/messages');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"number": "+258841234567", "text": "Your order is ready.", "send_at": "2026-11-20T09:30: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 = {"number": "+258841234567", "text": "Your order is ready.", "send_at": "2026-11-20T09:30:00+02:00"}
r = requests.post("https://api.interweb.ao/v1/sms/messages", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.ao/v1/sms/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({"number": "+258841234567", "text": "Your order is ready.", "send_at": "2026-11-20T09:30:00+02:00"}),
});
console.log(response.status);
console.log(await response.text());
```


O número não precisa de indicativo, porque o sistema reconhece o país pelo primeiro algarismo: `841234567` vai para Moçambique e `923456789` para Angola. Com o indicativo também funciona, com ou sem o `+` e com espaços: `+258 84 123 4567` e `258841234567` são o mesmo número. Se o número não for válido, a API responde `422` com `validation_failed` e o motivo `invalid_number` em `details.reason`.

Sem `send_at`, a mensagem sai logo e a resposta traz o estado real: `sent` se saiu, `failed` se falhou, ou `queued` se tiver de esperar a vez.

Envie o cabeçalho `Idempotency-Key` para uma repetição nunca enviar duas vezes. Veja [Repetições](https://docs.interweb.ao/pt/api-rules.md).

## O remetente

O campo `sender` é opcional. Se não o enviar, a mensagem sai com o remetente grátis, **NETMSG**, que todas as contas podem usar sem pedido.

Para usar outro nome, indique um remetente aprovado. A lista está em [`GET /v1/sms/senders`](https://docs.interweb.ao/pt/sms.md), com o remetente grátis sempre em primeiro e o campo `free` a `true`.

## Sandbox e Produção

- Com uma chave `sandbox_`, ou enquanto a API está em preparação, a mensagem é simulada: não sai nada e não se gastam créditos.
- Com uma chave `prod_`, a mensagem é enviada e os créditos são descontados do saldo de SMS. O limite de gastos da chave, em moeda, não se aplica a estes créditos.

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

## Envios agendados

Para a mensagem sair mais tarde, junte ao envio o campo `send_at`, com a data e a hora em ISO 8601 e o fuso horário, por exemplo `2026-11-20T09:30:00+02:00` (hora de Maputo). A data tem de ser no futuro e, no máximo, daqui a 365 dias. Caso contrário, a API responde `422`, com o motivo `invalid_send_at`, `send_at_in_past` ou `send_at_too_far` em `details.reason`.

- A mensagem fica com o estado `scheduled` e com a hora marcada em `scheduled_for`.
- Ao agendar, a API só confere se o saldo chega. Os créditos só são descontados quando a mensagem sair: até lá, `charged` fica `false`, e [`GET /v1/sms/balance`](https://docs.interweb.ao/pt/sms.md) mostra em `scheduled_credits` o valor agendado que ainda não foi descontado.
- Se, na hora marcada, o saldo não chegar, a mensagem não sai e fica `failed`, com `error` a `insufficient_credits`.

Para desistir do envio, use [`POST /v1/sms/messages/{id}/cancel`](https://docs.interweb.ao/pt/sms.md) antes da hora marcada:

**curl**

```bash
curl -X POST "https://api.interweb.ao/v1/sms/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.ao/v1/sms/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.ao/v1/sms/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.ao/v1/sms/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());
```


Só se cancelam as mensagens da própria conta que ainda estejam `scheduled`. Se os créditos já tiverem sido descontados, voltam ao saldo, e `credits_returned` diz quantos. Se a mensagem já saiu, está a sair ou já foi cancelada, a API responde `409`, com o motivo `not_cancellable` em `details.reason`. Com uma chave `sandbox_`, a API confere tudo e não muda nada.

## Ver uma mensagem

Use [`GET /v1/sms/messages/{id}`](https://docs.interweb.ao/pt/sms.md) com o identificador devolvido no envio. A resposta traz o estado, a hora marcada (se a mensagem estiver agendada) e o campo `charged`, que diz se os créditos já foram descontados. As mensagens simuladas só se vêem com a chave que as criou.

## O saldo

Use [`GET /v1/sms/balance`](https://docs.interweb.ao/pt/sms.md). É o mesmo saldo em Sandbox e em Produção.

## Ver as mensagens enviadas

[`GET /v1/sms/messages`](https://docs.interweb.ao/pt/sms.md) devolve as mensagens que a conta enviou, em [páginas](https://docs.interweb.ao/pt/api-rules.md), com os filtros `status`, `from`, `to` e `number`. O número vem tapado. Os avisos da conta, os códigos de verificação e as mensagens simuladas não aparecem.

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/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.ao/v1/sms/messages", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


## Estatísticas

[`GET /v1/sms/stats`](https://docs.interweb.ao/pt/sms.md) dá os totais por estado, os créditos gastos e uma linha por cada dia com mensagens. Sem datas, conta os últimos 30 dias; no máximo 366.

## Modelos

- [`GET /v1/sms/templates`](https://docs.interweb.ao/pt/sms.md) lista os modelos da conta, e [`GET /v1/sms/templates/{id}`](https://docs.interweb.ao/pt/sms.md) mostra um.
- [`POST /v1/sms/templates`](https://docs.interweb.ao/pt/sms.md) cria, [`PATCH /v1/sms/templates/{id}`](https://docs.interweb.ao/pt/sms.md) altera e [`DELETE /v1/sms/templates/{id}`](https://docs.interweb.ao/pt/sms.md) apaga.

São os mesmos modelos da página de SMS do portal. Um modelo não envia nada: serve para guardar o texto. O texto aceita as variáveis `{$client_name}`, `{$client_first_name}`, `{$client_last_name}`, `{$client_company_name}`, `{$remetente}` e `{$data}`; outra variável responde `422`.

## Remetentes novos

[`POST /v1/sms/senders`](https://docs.interweb.ao/pt/sms.md) faz o pedido, com o nome, a entidade, o país, o número fiscal, a finalidade e um exemplo de mensagem. [`GET /v1/sms/senders/{sender}`](https://docs.interweb.ao/pt/sms.md) diz em que ponto está, pelo `id` ou pelo nome.

**curl**

```bash
curl -X POST "https://api.interweb.ao/v1/sms/senders" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"sender": "AMINHAMARCA", "entity": "A Minha Empresa, Lda", "country": "Moçambique", "tax_id": "123456789", "purpose": "Avisos de encomenda aos nossos clientes.", "sample": "A sua encomenda está pronta."}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/senders');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"sender": "AMINHAMARCA", "entity": "A Minha Empresa, Lda", "country": "Moçambique", "tax_id": "123456789", "purpose": "Avisos de encomenda aos nossos clientes.", "sample": "A sua encomenda está pronta."}',
    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 = {"sender": "AMINHAMARCA", "entity": "A Minha Empresa, Lda", "country": "Moçambique", "tax_id": "123456789", "purpose": "Avisos de encomenda aos nossos clientes.", "sample": "A sua encomenda está pronta."}
r = requests.post("https://api.interweb.ao/v1/sms/senders", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.ao/v1/sms/senders", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"sender": "AMINHAMARCA", "entity": "A Minha Empresa, Lda", "country": "Moçambique", "tax_id": "123456789", "purpose": "Avisos de encomenda aos nossos clientes.", "sample": "A sua encomenda está pronta."}),
});
console.log(response.status);
console.log(await response.text());
```


A nossa equipa analisa o pedido e trata do registo junto das operadoras; a aprovação depende delas. Se for recusado, `note` explica porquê e pode pedir de novo com os dados corrigidos. Para juntar documentos, faça o pedido no portal, em `documents_url`.

## Números que não recebem

[`POST /v1/sms/opt-outs`](https://docs.interweb.ao/pt/sms.md) acrescenta um número à lista da conta: a partir daí, enviar para ele responde `422` com `number_opted_out`. [`GET /v1/sms/opt-outs`](https://docs.interweb.ao/pt/sms.md) mostra a lista. Para retirar um número, fale com o suporte.

## Ajuda

Para outras dúvidas, veja a [Base de Conhecimento](https://www.meu.interweb.ao/knowledgebase?language=portuguese&currency=1).


## Operações

16 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 as mensagens enviadas

`GET https://api.interweb.ao/v1/sms/messages`

Precisa de `sms:read`. As mensagens que esta conta enviou, as mais recentes primeiro. Os avisos da conta e os códigos de verificação não aparecem, nem as mensagens simuladas com uma chave de Sandbox. O número vem tapado. Se uma mensagem falhar, `error` é `delivery_failed`. `charged` diz se os créditos já saíram do saldo.

#### Autenticação

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

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `status` | consulta | string: scheduled, queued, sending, sent, simulated, failed, cancelled | não | Só este estado. |
| `from` | consulta | string (date) | não | Primeiro dia, `AAAA-MM-DD`. |
| `to` | consulta | string (date) | não | Último dia, `AAAA-MM-DD`. |
| `number` | consulta | string | não | Só este número, com ou sem indicativo. |
| `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.ao/v1/sms/messages" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/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.ao/v1/sms/messages", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Respostas

##### `200`

Listar as mensagens enviadas.

```json
{
  "data": [
    {
      "id": "128",
      "status": "sent",
      "simulated": false,
      "to": "25884****567",
      "sender": "NETMSG",
      "text": "A sua encomenda está pronta.",
      "parts": 1,
      "encoding": "GSM",
      "credits": 1.0,
      "campaign_id": null,
      "error": null,
      "scheduled_for": "2026-11-05T09:30:00+02:00",
      "created_at": "2026-11-05T09:30:00+02:00",
      "sent_at": "2026-11-05T09:30:04+02:00",
      "charged": true
    }
  ],
  "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 pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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`, `ip_not_allowed` | A conta não pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`). |
| 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.ao/pt/api-rules.md).


### Enviar um SMS

`POST https://api.interweb.ao/v1/sms/messages`

Precisa de `sms:write` e de uma conta revendedora de SMS activa. Com uma chave de Sandbox, ou enquanto a API está em preparação, a mensagem é simulada: não sai nada e não se gastam créditos. Com uma chave de Produção, a mensagem entra na fila e os créditos saem do saldo de SMS (o limite de gastos da chave, em moeda, não se aplica a estes créditos). Envie `Idempotency-Key` para uma repetição nunca enviar duas vezes. Sem `send_at` a mensagem sai logo e a resposta traz o estado real: `sent`, `failed`, ou `queued` se tiver de esperar a vez. Com `send_at` fica `scheduled`: agora só se confere o saldo, e os créditos saem dele quando a mensagem sair (`charged` fica `false` até lá); se nessa hora o saldo não chegar, a mensagem não sai e fica `failed`, com `error` a `insufficient_credits`. Uma mensagem agendada pode ser cancelada com POST /v1/sms/messages/{id}/cancel.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `sms: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 |
|---|---|---|---|
| `number` | string | sim | Com ou sem indicativo, e pode ter espaços: +258841234567, 258 84 123 4567 ou 84 123 4567. Os telemóveis de Moçambique e de Angola reconhecem-se sozinhos; qualquer outro sem indicativo segue o país da sua conta. |
| `text` | string | sim |  |
| `sender` | string | não | Opcional. Um remetente aprovado; se faltar, o remetente grátis (veja GET /v1/sms/senders). |
| `send_at` | string (date-time) | não | Opcional. Agenda a mensagem: data e hora em ISO 8601 com o fuso horário, como `2026-11-20T09:30:00+02:00`. Tem de ser no futuro e, no máximo, daqui a 365 dias. Sem este campo, a mensagem sai logo. |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.ao/v1/sms/messages" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"number": "+258841234567", "text": "Your order is ready.", "send_at": "2026-11-20T09:30:00+02:00"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/messages');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"number": "+258841234567", "text": "Your order is ready.", "send_at": "2026-11-20T09:30: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 = {"number": "+258841234567", "text": "Your order is ready.", "send_at": "2026-11-20T09:30:00+02:00"}
r = requests.post("https://api.interweb.ao/v1/sms/messages", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.ao/v1/sms/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({"number": "+258841234567", "text": "Your order is ready.", "send_at": "2026-11-20T09:30:00+02:00"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

A mensagem: enviada, simulada, agendada ou à espera na fila.

```json
{
  "data": {
    "id": "412",
    "status": "scheduled",
    "simulated": false,
    "to": "25884****567",
    "sender": "NETMSG",
    "parts": 1,
    "encoding": "GSM",
    "credits": 1.0,
    "charged": false,
    "mode": "production",
    "error": null,
    "scheduled_for": "2026-11-20T09:30:00+02:00",
    "created_at": "2026-11-10T12:00:00+02:00",
    "sent_at": null
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.id` | string | Um número nas mensagens que entraram na fila, um UUID nas simuladas. |
| `data.status` | string: scheduled, queued, sending, sent, simulated, failed, cancelled | `scheduled`: à espera da hora em `scheduled_for`, e ainda se pode cancelar. `queued`: à espera da vez para sair. |
| `data.simulated` | boolean | Verdadeiro quando o SMS é simulado e não sai nenhuma mensagem. |
| `data.to` | string | O número, em parte tapado. |
| `data.credits` | number | Quanto custa a mensagem, em créditos. Veja `charged` para saber se já saíram do saldo de SMS. Sempre 0 quando a API simula. |
| `data.would_cost` | number | Só nas simuladas: quanto custaria o envio em Produção. |
| `data.error` | string, null | `delivery_failed` quando não foi possível entregar; `insufficient_credits` quando uma mensagem agendada não tinha saldo suficiente na hora de sair. |
| `data.charged` | boolean | Verdadeiro quando os créditos saíram do saldo de SMS. Uma mensagem agendada só se cobra quando sai; as falhadas, canceladas e simuladas devolvem os créditos. |
| `data.scheduled_for` | string, null (date-time) | A hora escolhida em `send_at`, enquanto a mensagem está agendada ou depois de cancelada; nos outros casos, `null`. |

| 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`. |
| `X-API-Simulated` | Presente e `true` quando esta escrita foi simulada: nada mudou e nada foi cobrado. |

##### `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`

Esta conta não tem revenda de SMS activa (`sms_reseller_required`), a chave não tem a permissão (`insufficient_scope`), ou a conta não pode usar a API (`forbidden`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

A Idempotency-Key foi usada com outro pedido, ou o primeiro pedido ainda está em curso.

```json
{
  "error": {
    "code": "idempotency_conflict",
    "message": "Esta Idempotency-Key já foi usada com outro pedido, ou o primeiro pedido ainda está em curso.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "different_request"
    }
  }
}
```

##### `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 | `sms_reseller_required`, `insufficient_scope`, `forbidden` | Esta conta não tem revenda de SMS activa (`sms_reseller_required`), a chave não tem a permissão (`insufficient_scope`), ou a conta não pode usar a API (`forbidden`). |
| 409 | `idempotency_conflict` | A Idempotency-Key foi usada com outro pedido, ou o primeiro pedido ainda está em curso. |
| 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.ao/pt/api-rules.md).


### Ver um SMS

`GET https://api.interweb.ao/v1/sms/messages/{id}`

Precisa de `sms:read`. As mensagens simuladas só se vêem com a chave que as criou.

#### Autenticação

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

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` devolvido ao enviar. |
| `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.ao/v1/sms/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.ao/v1/sms/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.ao/v1/sms/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.ao/v1/sms/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`

A mensagem.

```json
{
  "data": {
    "id": "7f0c9d2e-3a51-4c7e-9b1a-2d4f6a8b0c11",
    "status": "simulated",
    "simulated": true,
    "to": "25884****567",
    "sender": "NETMSG",
    "parts": 1,
    "encoding": "GSM",
    "would_cost": 1,
    "mode": "sandbox",
    "credits": 0,
    "created_at": "2026-11-04T10:00:00+02:00",
    "error": null,
    "sent_at": null,
    "charged": false,
    "scheduled_for": null
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.id` | string | Um número nas mensagens que entraram na fila, um UUID nas simuladas. |
| `data.status` | string: scheduled, queued, sending, sent, simulated, failed, cancelled | `scheduled`: à espera da hora em `scheduled_for`, e ainda se pode cancelar. `queued`: à espera da vez para sair. |
| `data.simulated` | boolean | Verdadeiro quando o SMS é simulado e não sai nenhuma mensagem. |
| `data.to` | string | O número, em parte tapado. |
| `data.credits` | number | Quanto custa a mensagem, em créditos. Veja `charged` para saber se já saíram do saldo de SMS. Sempre 0 quando a API simula. |
| `data.would_cost` | number | Só nas simuladas: quanto custaria o envio em Produção. |
| `data.error` | string, null | `delivery_failed` quando não foi possível entregar; `insufficient_credits` quando uma mensagem agendada não tinha saldo suficiente na hora de sair. |
| `data.charged` | boolean | Verdadeiro quando os créditos saíram do saldo de SMS. Uma mensagem agendada só se cobra quando sai; as falhadas, canceladas e simuladas devolvem os créditos. |
| `data.scheduled_for` | string, null (date-time) | A hora escolhida em `send_at`, enquanto a mensagem está agendada ou depois de cancelada; nos outros casos, `null`. |

| 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`

Esta conta não tem revenda de SMS activa (`sms_reseller_required`), a chave não tem a permissão (`insufficient_scope`), ou a conta não pode usar a API (`forbidden`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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 | `sms_reseller_required`, `insufficient_scope`, `forbidden` | Esta conta não tem revenda de SMS activa (`sms_reseller_required`), a chave não tem a permissão (`insufficient_scope`), ou a conta não pode usar a API (`forbidden`). |
| 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.ao/pt/api-rules.md).


### Ver o saldo de SMS

`GET https://api.interweb.ao/v1/sms/balance`

Precisa de `sms:read`. O saldo é o verdadeiro nos dois modos.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `sms: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.ao/v1/sms/balance" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/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.ao/v1/sms/balance", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Respostas

##### `200`

Saldo e limite diário.

```json
{
  "data": {
    "balance": 50,
    "unit": "credits",
    "daily_limit": 200,
    "sent_today": 3,
    "mode": "sandbox",
    "scheduled_credits": 0.0
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.scheduled_credits` | number | Créditos das mensagens agendadas que ainda não foram cobradas. Continuam em `balance` e saem quando cada mensagem sair. |

| 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`

Esta conta não tem revenda de SMS activa (`sms_reseller_required`), a chave não tem a permissão (`insufficient_scope`), ou a conta não pode usar a API (`forbidden`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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 | `sms_reseller_required`, `insufficient_scope`, `forbidden` | Esta conta não tem revenda de SMS activa (`sms_reseller_required`), a chave não tem a permissão (`insufficient_scope`), ou a conta não pode usar a API (`forbidden`). |
| 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.ao/pt/api-rules.md).


### Listar os remetentes de SMS

`GET https://api.interweb.ao/v1/sms/senders`

Precisa de `sms:read`. O remetente grátis vem sempre em primeiro, com `free: true`.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `sms: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.ao/v1/sms/senders" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/senders');
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.ao/v1/sms/senders", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Respostas

##### `200`

Os remetentes desta conta.

```json
{
  "data": [
    {
      "sender": "NETMSG",
      "status": "approved",
      "free": true
    },
    {
      "sender": "MINHAEMPRESA",
      "status": "pending",
      "free": 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`

Esta conta não tem revenda de SMS activa (`sms_reseller_required`), a chave não tem a permissão (`insufficient_scope`), ou a conta não pode usar a API (`forbidden`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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 | `sms_reseller_required`, `insufficient_scope`, `forbidden` | Esta conta não tem revenda de SMS activa (`sms_reseller_required`), a chave não tem a permissão (`insufficient_scope`), ou a conta não pode usar a API (`forbidden`). |
| 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.ao/pt/api-rules.md).


### Pedir um remetente

`POST https://api.interweb.ao/v1/sms/senders`

Precisa de `sms:write`. O nome tem até 11 caracteres, entre letras, números e espaços. Cada conta tem até 30 remetentes. A equipa analisa o pedido e regista-o junto das operadoras; a aprovação depende delas. Depois de uma recusa, envie de novo com os dados corrigidos. Os documentos enviam-se no portal da conta, quando o pedido é feito lá. Limite de 20 alterações por minuto.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `sms: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 |
|---|---|---|---|
| `sender` | string | sim |  |
| `entity` | string | sim |  |
| `country` | string | sim |  |
| `tax_id` | string | sim |  |
| `purpose` | string | sim |  |
| `sample` | string | sim |  |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.ao/v1/sms/senders" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"sender": "AMINHAMARCA", "entity": "A Minha Empresa, Lda", "country": "Moçambique", "tax_id": "123456789", "purpose": "Avisos de encomenda aos nossos clientes.", "sample": "A sua encomenda está pronta."}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/senders');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"sender": "AMINHAMARCA", "entity": "A Minha Empresa, Lda", "country": "Moçambique", "tax_id": "123456789", "purpose": "Avisos de encomenda aos nossos clientes.", "sample": "A sua encomenda está pronta."}',
    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 = {"sender": "AMINHAMARCA", "entity": "A Minha Empresa, Lda", "country": "Moçambique", "tax_id": "123456789", "purpose": "Avisos de encomenda aos nossos clientes.", "sample": "A sua encomenda está pronta."}
r = requests.post("https://api.interweb.ao/v1/sms/senders", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.ao/v1/sms/senders", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"sender": "AMINHAMARCA", "entity": "A Minha Empresa, Lda", "country": "Moçambique", "tax_id": "123456789", "purpose": "Avisos de encomenda aos nossos clientes.", "sample": "A sua encomenda está pronta."}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

Pedir um remetente.

```json
{
  "data": {
    "id": 7,
    "sender": "AMINHAMARCA",
    "status": "pending",
    "free": false,
    "entity": "A Minha Empresa, Lda",
    "country": "Moçambique",
    "purpose": "Avisos de encomenda aos nossos clientes.",
    "sample": "A sua encomenda está pronta.",
    "note": null,
    "documents": 0,
    "documents_url": "https://my.example.com/sms-center.php?view=senders&language=portuguese&currency=5",
    "created_at": "2026-11-05T09:30:00+02:00",
    "updated_at": "2026-11-05T09:30:00+02:00",
    "simulated": true
  },
  "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 pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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`, `ip_not_allowed` | A conta não pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`). |
| 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.ao/pt/api-rules.md).


### Ver as estatísticas de envio

`GET https://api.interweb.ao/v1/sms/stats`

Precisa de `sms:read`. Totais e uma linha por cada dia com mensagens. Sem datas, os últimos 30 dias; no máximo 366 dias. As mensagens falhadas, canceladas e simuladas não contam em `credits_spent`. Também não contam as mensagens agendadas que ainda não foram cobradas.

#### Autenticação

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

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `from` | consulta | string (date) | não | Primeiro dia, `AAAA-MM-DD`. |
| `to` | consulta | string (date) | não | Último dia, `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.ao/v1/sms/stats" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/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.ao/v1/sms/stats", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.ao/v1/sms/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 envio.

```json
{
  "data": {
    "from": "2026-10-07",
    "to": "2026-11-05",
    "messages": 12,
    "parts": 14,
    "credits_spent": 13.0,
    "by_status": {
      "scheduled": 0,
      "queued": 0,
      "sending": 0,
      "sent": 11,
      "simulated": 0,
      "failed": 1,
      "cancelled": 0
    },
    "by_day": [
      {
        "date": "2026-11-05",
        "messages": 12,
        "sent": 11,
        "failed": 1,
        "credits": 13.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 pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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`, `ip_not_allowed` | A conta não pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`). |
| 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.ao/pt/api-rules.md).


### Listar os modelos

`GET https://api.interweb.ao/v1/sms/templates`

Precisa de `sms:read`. Os modelos desta conta, por nome.

#### Autenticação

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

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `category` | consulta | string: general, notice, promotion, billing, confirmation | não | Só esta categoria. |
| `language` | consulta | string: pt, en | não | Só esta língua. |
| `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.ao/v1/sms/templates" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/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.ao/v1/sms/templates", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.ao/v1/sms/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.

```json
{
  "data": [
    {
      "id": 41,
      "name": "Aviso de factura",
      "text": "Olá {$client_first_name}, a sua factura vence hoje.",
      "category": "notice",
      "language": "pt",
      "created_at": "2026-11-05T09:30:00+02:00",
      "updated_at": "2026-11-05T09:30: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 pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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`, `ip_not_allowed` | A conta não pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`). |
| 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.ao/pt/api-rules.md).


### Criar um modelo

`POST https://api.interweb.ao/v1/sms/templates`

Precisa de `sms:write`. O nome não se pode repetir na conta. O texto aceita `{$client_name}`, `{$client_first_name}`, `{$client_last_name}`, `{$client_company_name}`, `{$remetente}` e `{$data}`. Limite de 20 alterações por minuto.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `sms: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 |
|---|---|---|---|
| `name` | string | sim |  |
| `text` | string | sim |  |
| `category` | string: general, notice, promotion, billing, confirmation | não |  |
| `language` | string: pt, en | não |  |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.ao/v1/sms/templates" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"name": "Aviso de factura", "text": "Olá {$client_first_name}, a sua factura vence hoje.", "category": "notice", "language": "pt"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/templates');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"name": "Aviso de factura", "text": "Olá {$client_first_name}, a sua factura vence hoje.", "category": "notice", "language": "pt"}',
    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": "Aviso de factura", "text": "Olá {$client_first_name}, a sua factura vence hoje.", "category": "notice", "language": "pt"}
r = requests.post("https://api.interweb.ao/v1/sms/templates", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.ao/v1/sms/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": "Aviso de factura", "text": "Olá {$client_first_name}, a sua factura vence hoje.", "category": "notice", "language": "pt"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

Criar um modelo.

```json
{
  "data": {
    "id": 41,
    "name": "Aviso de factura",
    "text": "Olá {$client_first_name}, a sua factura vence hoje.",
    "category": "notice",
    "language": "pt",
    "created_at": "2026-11-05T09:30:00+02:00",
    "updated_at": "2026-11-05T09:30:00+02:00",
    "simulated": true
  },
  "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 pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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`, `ip_not_allowed` | A conta não pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`). |
| 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.ao/pt/api-rules.md).


### Ver um modelo

`GET https://api.interweb.ao/v1/sms/templates/{id}`

Precisa de `sms:read`. Um modelo desta conta.

#### Autenticação

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

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` do modelo. |
| `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.ao/v1/sms/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.ao/v1/sms/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.ao/v1/sms/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.ao/v1/sms/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.

```json
{
  "data": {
    "id": 41,
    "name": "Aviso de factura",
    "text": "Olá {$client_first_name}, a sua factura vence hoje.",
    "category": "notice",
    "language": "pt",
    "created_at": "2026-11-05T09:30:00+02:00",
    "updated_at": "2026-11-05T09:30: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 pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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`, `ip_not_allowed` | A conta não pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`). |
| 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.ao/pt/api-rules.md).


### Alterar um modelo

`PATCH https://api.interweb.ao/v1/sms/templates/{id}`

Precisa de `sms:write`. Envie só os campos a alterar. Limite de 20 alterações por minuto.

#### Autenticação

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

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` do modelo. |
| `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 |  |
| `text` | string | não |  |
| `category` | string: general, notice, promotion, billing, confirmation | não |  |
| `language` | string: pt, en | não |  |

#### Exemplos

**curl**

```bash
curl -X PATCH "https://api.interweb.ao/v1/sms/templates/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Content-Type: application/json" \
  -d '{"text": "Olá {$client_first_name}, a sua factura vence amanhã."}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/templates/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_POSTFIELDS => '{"text": "Olá {$client_first_name}, a sua factura vence amanhã."}',
    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 = {"text": "Olá {$client_first_name}, a sua factura vence amanhã."}
r = requests.patch("https://api.interweb.ao/v1/sms/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.ao/v1/sms/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({"text": "Olá {$client_first_name}, a sua factura vence amanhã."}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `200`

Alterar um modelo.

```json
{
  "data": {
    "id": 41,
    "name": "Aviso de factura",
    "text": "Olá {$client_first_name}, a sua factura vence hoje.",
    "category": "notice",
    "language": "pt",
    "created_at": "2026-11-05T09:30:00+02:00",
    "updated_at": "2026-11-05T09:30:00+02:00",
    "simulated": true
  },
  "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 pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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`, `ip_not_allowed` | A conta não pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`). |
| 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.ao/pt/api-rules.md).


### Apagar um modelo

`DELETE https://api.interweb.ao/v1/sms/templates/{id}`

Precisa de `sms:write`. Os modelos padrão que forem apagados não voltam. Limite de 20 alterações por minuto.

#### Autenticação

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

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` do modelo. |
| `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.ao/v1/sms/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.ao/v1/sms/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.ao/v1/sms/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.ao/v1/sms/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.

```json
{
  "data": {
    "id": 41,
    "deleted": false,
    "simulated": true
  },
  "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 pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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`, `ip_not_allowed` | A conta não pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`). |
| 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.ao/pt/api-rules.md).


### Ver um pedido de remetente

`GET https://api.interweb.ao/v1/sms/senders/{sender}`

Precisa de `sms:read`. Pelo `id` ou pelo nome. `note` é a nota da nossa equipa. O remetente grátis responde sempre `approved`.

#### Autenticação

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

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `sender` | caminho | string | sim | O `id` do pedido ou o nome do remetente. |
| `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.ao/v1/sms/senders/VALUE" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/senders/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.ao/v1/sms/senders/VALUE", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.ao/v1/sms/senders/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 pedido de remetente.

```json
{
  "data": {
    "id": 7,
    "sender": "AMINHAMARCA",
    "status": "pending",
    "free": false,
    "entity": "A Minha Empresa, Lda",
    "country": "Moçambique",
    "purpose": "Avisos de encomenda aos nossos clientes.",
    "sample": "A sua encomenda está pronta.",
    "note": null,
    "documents": 0,
    "documents_url": "https://my.example.com/sms-center.php?view=senders&language=portuguese&currency=5",
    "created_at": "2026-11-05T09:30:00+02:00",
    "updated_at": "2026-11-05T09:30: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 pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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`, `ip_not_allowed` | A conta não pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`). |
| 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.ao/pt/api-rules.md).


### Listar os números que não recebem

`GET https://api.interweb.ao/v1/sms/opt-outs`

Precisa de `sms:read`. Os números que esta conta bloqueou, tapados. Use `number` para confirmar um.

#### Autenticação

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

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `number` | consulta | string | não | Só este número, com ou sem indicativo. |
| `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.ao/v1/sms/opt-outs" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/opt-outs');
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.ao/v1/sms/opt-outs", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Respostas

##### `200`

Listar os números que não recebem.

```json
{
  "data": [
    {
      "number": "25884****567",
      "created_at": "2026-11-05T09:30: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 pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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`, `ip_not_allowed` | A conta não pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`). |
| 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.ao/pt/api-rules.md).


### Deixar de enviar a um número

`POST https://api.interweb.ao/v1/sms/opt-outs`

Precisa de `sms:write`. A partir daqui, enviar para este número responde `422` com `number_opted_out`. Para retirar um número da lista, fale com o suporte. Limite de 20 alterações por minuto.

#### Autenticação

Precisa de uma chave válida no cabeçalho `Authorization: Bearer`. Permissão necessária: `sms: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 |
|---|---|---|---|
| `number` | string | sim |  |

#### Exemplos

**curl**

```bash
curl -X POST "https://api.interweb.ao/v1/sms/opt-outs" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: pt" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"number": "+258841234567"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.ao/v1/sms/opt-outs');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"number": "+258841234567"}',
    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 = {"number": "+258841234567"}
r = requests.post("https://api.interweb.ao/v1/sms/opt-outs", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.ao/v1/sms/opt-outs", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "pt",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"number": "+258841234567"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Respostas

##### `201`

Deixar de enviar a um número.

```json
{
  "data": {
    "number": "25884****567",
    "created_at": "2026-11-05T09:30:00+02:00",
    "already_listed": false,
    "simulated": true
  },
  "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 pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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`, `ip_not_allowed` | A conta não pode usar a API (`forbidden`), a chave não tem a permissão necessária (`insufficient_scope`), ou o endereço IP não está na lista da chave (`ip_not_allowed`). |
| 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.ao/pt/api-rules.md).


### Cancelar um SMS agendado

`POST https://api.interweb.ao/v1/sms/messages/{id}/cancel`

Precisa de `sms:write`. Só se cancelam mensagens desta conta que ainda estejam `scheduled`. Se os créditos já tiverem sido cobrados, voltam ao saldo, e `credits_returned` diz quantos; normalmente uma mensagem agendada só se cobra quando sai, e por isso muitas vezes não há nada a devolver. Uma mensagem que já saiu, que está a sair ou que já foi cancelada responde `409`, com `details.reason` a `not_cancellable`. Com uma chave de Sandbox, ou enquanto a API está em preparação, confere tudo e não muda nada. Envie `Idempotency-Key` para uma repetição dar a mesma resposta.

#### Autenticação

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

#### Parâmetros

| Nome | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
| `id` | caminho | string | sim | O `id` devolvido ao enviar. |
| `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.ao/v1/sms/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.ao/v1/sms/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.ao/v1/sms/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.ao/v1/sms/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 SMS agendado.

```json
{
  "data": {
    "id": "412",
    "status": "cancelled",
    "simulated": false,
    "to": "25884****567",
    "sender": "NETMSG",
    "parts": 1,
    "encoding": "GSM",
    "credits": 1.0,
    "charged": false,
    "mode": "production",
    "error": null,
    "scheduled_for": "2026-11-20T09:30:00+02:00",
    "created_at": "2026-11-10T12:00:00+02:00",
    "sent_at": null,
    "credits_returned": 0.0
  },
  "meta": {}
}
```

| Campo | Tipo | Descrição |
|---|---|---|
| `data.id` | string | Um número nas mensagens que entraram na fila, um UUID nas simuladas. |
| `data.status` | string: scheduled, queued, sending, sent, simulated, failed, cancelled | `scheduled`: à espera da hora em `scheduled_for`, e ainda se pode cancelar. `queued`: à espera da vez para sair. |
| `data.simulated` | boolean | Verdadeiro quando o SMS é simulado e não sai nenhuma mensagem. |
| `data.to` | string | O número, em parte tapado. |
| `data.credits` | number | Quanto custa a mensagem, em créditos. Veja `charged` para saber se já saíram do saldo de SMS. Sempre 0 quando a API simula. |
| `data.would_cost` | number | Só nas simuladas: quanto custaria o envio em Produção. |
| `data.error` | string, null | `delivery_failed` quando não foi possível entregar; `insufficient_credits` quando uma mensagem agendada não tinha saldo suficiente na hora de sair. |
| `data.charged` | boolean | Verdadeiro quando os créditos saíram do saldo de SMS. Uma mensagem agendada só se cobra quando sai; as falhadas, canceladas e simuladas devolvem os créditos. |
| `data.scheduled_for` | string, null (date-time) | A hora escolhida em `send_at`, enquanto a mensagem está agendada ou depois de cancelada; nos outros casos, `null`. |
| `data.credits_returned` | number | Créditos que voltaram ao saldo de SMS com este cancelamento. |

| 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`. |
| `X-API-Simulated` | Presente e `true` quando esta escrita foi simulada: nada mudou e nada foi cobrado. |

##### `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`

Esta conta não tem revenda de SMS activa (`sms_reseller_required`), a chave não tem a permissão (`insufficient_scope`), ou a conta não pode usar a API (`forbidden`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta chave não tem a permissão necessária.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `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 | `sms_reseller_required`, `insufficient_scope`, `forbidden` | Esta conta não tem revenda de SMS activa (`sms_reseller_required`), a chave não tem a permissão (`insufficient_scope`), ou a conta não pode usar a API (`forbidden`). |
| 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.ao/pt/api-rules.md).
