# SMS

Send SMS through the API and see the history, statistics, balance, templates, senders and numbers that do not receive.

Updated on 10 November 2026

## What you need

- An active SMS reseller account. Without it, the API answers `403` with the code `sms_reseller_required`.
- A key with the `sms:write` permission to send, and `sms:read` to look things up. See [Permissions](https://docs.interweb.co.mz/en/permissions.md).

## Sending

Use [`POST /v1/sms/messages`](https://docs.interweb.co.mz/en/sms.md) with the number and the text:

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/sms/messages" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -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.co.mz/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: en',
        '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": "en",
    "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.co.mz/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.co.mz/v1/sms/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "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());
```


The number does not need the country code: the system can tell the country from the first digit, so `841234567` goes to Mozambique and `923456789` to Angola. It also works with the country code, with or without the `+` and with spaces: `+258 84 123 4567` and `258841234567` are the same number. If the number is not valid, the API responds with `422`, the code `validation_failed` and the reason `invalid_number` in `details.reason`.

Without `send_at`, the message goes out right away and the answer brings its real status: `sent` if it went out, `failed` if it failed, or `queued` if it has to wait its turn.

Send the `Idempotency-Key` header so a retry never sends twice. See [Retries](https://docs.interweb.co.mz/en/api-rules.md).

## The sender

The `sender` field is optional. If you do not send it, the message goes out with the free sender, **NETMSG**, which every account can use without asking.

To use another name, give an approved sender. The list is at [`GET /v1/sms/senders`](https://docs.interweb.co.mz/en/sms.md), with the free sender always first and the `free` field set to `true`.

## Sandbox and Production

- With a `sandbox_` key, or while the API is in preparation, the message is simulated: nothing is sent and no credits are used.
- With a `prod_` key, the message is sent and credits are taken from the SMS balance. The spending limit of the key, in currency, does not apply to these credits.

See [Sandbox and Production](https://docs.interweb.co.mz/en/api-rules.md).

## Scheduled messages

To send the message later, add the `send_at` field, with the date and time in ISO 8601 and the time zone, for example `2026-11-20T09:30:00+02:00` (Maputo time). The date must be in the future and at most 365 days from now. Otherwise, the API responds with `422` and the reason `invalid_send_at`, `send_at_in_past` or `send_at_too_far` in `details.reason`.

- The message gets the status `scheduled`, with the chosen time in `scheduled_for`.
- When you schedule, the API only checks that the balance is enough. The credits are only taken when the message goes out: until then, `charged` stays `false`, and [`GET /v1/sms/balance`](https://docs.interweb.co.mz/en/sms.md) shows in `scheduled_credits` the scheduled amount that has not been taken yet.
- If the balance is not enough at the chosen time, the message is not sent and ends up `failed`, with `error` set to `insufficient_credits`.

To cancel the message, use [`POST /v1/sms/messages/{id}/cancel`](https://docs.interweb.co.mz/en/sms.md) before the chosen time:

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/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: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
r = requests.post("https://api.interweb.co.mz/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.co.mz/v1/sms/messages/00000000-0000-4000-8000-000000000000/cancel", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Idempotency-Key": "9f1c2d3e-example",
  },
});
console.log(response.status);
console.log(await response.text());
```


Only messages from your own account that are still `scheduled` can be cancelled. If the credits were already taken, they go back to the balance, and `credits_returned` says how many. If the message already went out, is going out or was already cancelled, the API responds with `409` and the reason `not_cancellable` in `details.reason`. With a `sandbox_` key, the API checks everything and changes nothing.

## See a message

Use [`GET /v1/sms/messages/{id}`](https://docs.interweb.co.mz/en/sms.md) with the identifier returned when sending. The response includes the status, the chosen time if the message is scheduled, and the `charged` field, which says whether the credits have already been taken. Simulated messages can only be seen with the key that created them.

## See the balance

Use [`GET /v1/sms/balance`](https://docs.interweb.co.mz/en/sms.md). The balance is real in both modes.

## See sent messages

[`GET /v1/sms/messages`](https://docs.interweb.co.mz/en/sms.md) returns the messages the account sent, in [pages](https://docs.interweb.co.mz/en/api-rules.md), with the `status`, `from`, `to` and `number` filters. The number comes masked. Account notices, verification codes and simulated messages are not listed.

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/messages');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/messages", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


## Statistics

[`GET /v1/sms/stats`](https://docs.interweb.co.mz/en/sms.md) gives the totals by status, the credits spent and one line per day with messages. Without dates, it counts the last 30 days; at most 366.

## Templates

- [`GET /v1/sms/templates`](https://docs.interweb.co.mz/en/sms.md) lists the templates of the account, and [`GET /v1/sms/templates/{id}`](https://docs.interweb.co.mz/en/sms.md) shows one.
- [`POST /v1/sms/templates`](https://docs.interweb.co.mz/en/sms.md) creates, [`PATCH /v1/sms/templates/{id}`](https://docs.interweb.co.mz/en/sms.md) changes and [`DELETE /v1/sms/templates/{id}`](https://docs.interweb.co.mz/en/sms.md) deletes.

They are the same templates as on the SMS page of the portal. A template sends nothing: it only keeps the text. The text accepts the variables `{$client_name}`, `{$client_first_name}`, `{$client_last_name}`, `{$client_company_name}`, `{$remetente}` and `{$data}`; any other variable answers `422`.

## New senders

[`POST /v1/sms/senders`](https://docs.interweb.co.mz/en/sms.md) makes the request, with the name, the entity, the country, the tax number, the purpose and a sample message. [`GET /v1/sms/senders/{sender}`](https://docs.interweb.co.mz/en/sms.md) says where it stands, by `id` or by name.

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/sms/senders" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"sender": "AMINHAMARCA", "entity": "My Company Ltd", "country": "MZ", "tax_id": "123456789", "purpose": "Order updates for our customers.", "sample": "Your order is ready."}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/senders');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"sender": "AMINHAMARCA", "entity": "My Company Ltd", "country": "MZ", "tax_id": "123456789", "purpose": "Order updates for our customers.", "sample": "Your order is ready."}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"sender": "AMINHAMARCA", "entity": "My Company Ltd", "country": "MZ", "tax_id": "123456789", "purpose": "Order updates for our customers.", "sample": "Your order is ready."}
r = requests.post("https://api.interweb.co.mz/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.co.mz/v1/sms/senders", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"sender": "AMINHAMARCA", "entity": "My Company Ltd", "country": "MZ", "tax_id": "123456789", "purpose": "Order updates for our customers.", "sample": "Your order is ready."}),
});
console.log(response.status);
console.log(await response.text());
```


Our team reviews the request and handles the registration with the operators; approval depends on them. If it is rejected, `note` explains why and you can request it again with the data corrected. To add documents, make the request in the portal, at `documents_url`.

## Numbers that do not receive

[`POST /v1/sms/opt-outs`](https://docs.interweb.co.mz/en/sms.md) adds a number to the list of the account: from then on, sending to it answers `422` with `number_opted_out`. [`GET /v1/sms/opt-outs`](https://docs.interweb.co.mz/en/sms.md) shows the list. To take a number off, ask support.

## Help

For other questions, see the [Knowledge Base](https://www.meu.interweb.co.mz/knowledgebase?language=english&currency=2).


## Operations

38 operations in this area. Click an operation to see its parameters, examples and responses, or to try it in the Playground, without leaving the page.

### List sent messages

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

Needs `sms:read`. The messages this account sent, newest first. Account notices and verification codes are not listed, and messages simulated with a Sandbox key are not either. The number comes masked. If a message fails, `error` is `delivery_failed`. `charged` says whether the credits already left the balance.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `status` | query | string: scheduled, queued, sending, sent, simulated, failed, cancelled | no | Only this status. |
| `from` | query | string (date) | no | First day, `YYYY-MM-DD`. |
| `to` | query | string (date) | no | Last day, `YYYY-MM-DD`. |
| `number` | query | string | no | Only this number, with or without the country code. |
| `page` | query | integer | no | The page number. The first is 1. |
| `per_page` | query | integer | no | Results per page, from 1 to 100. The default is 10. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/messages');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/messages", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

List sent messages.

```json
{
  "data": [
    {
      "id": "128",
      "status": "sent",
      "simulated": false,
      "to": "25884****567",
      "sender": "NETMSG",
      "text": "Your order is ready.",
      "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
  }
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `422`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Send an SMS

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

Needs `sms:write` and an active SMS reseller account. With a Sandbox key, or while the API is in preparation, the message is simulated: nothing is sent and no credits are used. With a Production key the message enters the queue and credits are taken from the SMS balance (the key spending limit, in currency, does not apply to these credits). Send `Idempotency-Key` so a retry never sends twice. Without `send_at` the message goes out right away and the answer brings its real status: `sent`, `failed`, or `queued` if it has to wait its turn. With `send_at` it stays `scheduled`: the balance is only checked now and the credits leave it when the message goes out (`charged` is `false` until then); if the balance is short at that time, the message is not sent and ends up `failed` with `error` set to `insufficient_credits`. A scheduled message can be cancelled with POST /v1/sms/messages/{id}/cancel.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `Idempotency-Key` | header | string | no | Any unique value, up to 255 visible characters. Valid for 24 hours per key. Repeating the same request returns the saved answer; a different body, or a first request still in progress, returns 409. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `number` | string | yes | With or without the country code, spaces allowed: +258841234567, 258 84 123 4567 or 84 123 4567. Mozambican and Angolan mobile numbers are recognised on their own; any other number without a code uses the country of your account. |
| `text` | string | yes |  |
| `sender` | string | no | Optional. An approved sender; if omitted, the free sender (see GET /v1/sms/senders). |
| `send_at` | string (date-time) | no | Optional. Schedules the message: ISO 8601 date and time with the time zone, such as `2026-11-20T09:30:00+02:00`. It must be in the future and at most 365 days from now. Without it, the message goes out right away. |

#### Examples

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/sms/messages" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -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.co.mz/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: en',
        '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": "en",
    "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.co.mz/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.co.mz/v1/sms/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "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());
```


#### Responses

##### `201`

The message: sent, simulated, scheduled or waiting in the queue.

```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": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.id` | string | A number for messages in the queue, a UUID for simulated ones. |
| `data.status` | string: scheduled, queued, sending, sent, simulated, failed, cancelled | `scheduled`: waiting for the time in `scheduled_for`, and it can still be cancelled. `queued`: waiting its turn to go out. |
| `data.simulated` | boolean | True when no real SMS goes out. |
| `data.to` | string | The number, partly hidden. |
| `data.credits` | number | What the message costs in credits. See `charged` to know whether they already left the SMS balance. Always 0 when simulated by the API. |
| `data.would_cost` | number | Only on simulated messages: what a real send would cost. |
| `data.error` | string, null | `delivery_failed` when it could not be delivered; `insufficient_credits` when a scheduled message did not have enough balance when it was due to go out. |
| `data.charged` | boolean | True when the credits have left the SMS balance. A scheduled message is only charged when it goes out; failed, cancelled and simulated messages give the credits back. |
| `data.scheduled_for` | string, null (date-time) | The time chosen in `send_at`, while the message is scheduled or after it was cancelled; otherwise `null`. |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |
| `X-API-Simulated` | Present and `true` when this write was simulated: nothing changed and nothing was charged. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

This account does not have an active SMS reseller plan (`sms_reseller_required`), the key lacks the permission (`insufficient_scope`), or the account cannot use the API (`forbidden`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

The Idempotency-Key was used with a different request, or the first request is still in progress.

```json
{
  "error": {
    "code": "idempotency_conflict",
    "message": "This Idempotency-Key was already used with a different request, or the first request is still in progress.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "reason": "different_request"
    }
  }
}
```

##### `422`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `sms_reseller_required`, `insufficient_scope`, `forbidden` | This account does not have an active SMS reseller plan (`sms_reseller_required`), the key lacks the permission (`insufficient_scope`), or the account cannot use the API (`forbidden`). |
| 409 | `idempotency_conflict` | The Idempotency-Key was used with a different request, or the first request is still in progress. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### See one SMS

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

Needs `sms:read`. Simulated messages can only be seen with the key that created them.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` returned when sending. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/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: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/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.co.mz/v1/sms/messages/00000000-0000-4000-8000-000000000000", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `200`

The message.

```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": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.id` | string | A number for messages in the queue, a UUID for simulated ones. |
| `data.status` | string: scheduled, queued, sending, sent, simulated, failed, cancelled | `scheduled`: waiting for the time in `scheduled_for`, and it can still be cancelled. `queued`: waiting its turn to go out. |
| `data.simulated` | boolean | True when no real SMS goes out. |
| `data.to` | string | The number, partly hidden. |
| `data.credits` | number | What the message costs in credits. See `charged` to know whether they already left the SMS balance. Always 0 when simulated by the API. |
| `data.would_cost` | number | Only on simulated messages: what a real send would cost. |
| `data.error` | string, null | `delivery_failed` when it could not be delivered; `insufficient_credits` when a scheduled message did not have enough balance when it was due to go out. |
| `data.charged` | boolean | True when the credits have left the SMS balance. A scheduled message is only charged when it goes out; failed, cancelled and simulated messages give the credits back. |
| `data.scheduled_for` | string, null (date-time) | The time chosen in `send_at`, while the message is scheduled or after it was cancelled; otherwise `null`. |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

This account does not have an active SMS reseller plan (`sms_reseller_required`), the key lacks the permission (`insufficient_scope`), or the account cannot use the API (`forbidden`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `sms_reseller_required`, `insufficient_scope`, `forbidden` | This account does not have an active SMS reseller plan (`sms_reseller_required`), the key lacks the permission (`insufficient_scope`), or the account cannot use the API (`forbidden`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### See the SMS balance

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

Needs `sms:read`. The balance is real in both modes.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/balance');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/balance", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Balance and daily limit.

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

| Field | Type | Description |
|---|---|---|
| `data.scheduled_credits` | number | Credits of scheduled messages that were not charged yet. They are still in `balance` and leave it when each message goes out. |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

This account does not have an active SMS reseller plan (`sms_reseller_required`), the key lacks the permission (`insufficient_scope`), or the account cannot use the API (`forbidden`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `sms_reseller_required`, `insufficient_scope`, `forbidden` | This account does not have an active SMS reseller plan (`sms_reseller_required`), the key lacks the permission (`insufficient_scope`), or the account cannot use the API (`forbidden`). |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### List the SMS senders

`GET https://api.interweb.co.mz/v1/sms/senders`

Needs `sms:read`. The free sender always comes first, with `free: true`.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

```bash
curl "https://api.interweb.co.mz/v1/sms/senders" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/senders');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/senders", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

The senders of this account.

```json
{
  "data": [
    {
      "sender": "NETMSG",
      "status": "approved",
      "free": true
    },
    {
      "sender": "MINHAEMPRESA",
      "status": "pending",
      "free": false
    }
  ],
  "meta": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

This account does not have an active SMS reseller plan (`sms_reseller_required`), the key lacks the permission (`insufficient_scope`), or the account cannot use the API (`forbidden`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `sms_reseller_required`, `insufficient_scope`, `forbidden` | This account does not have an active SMS reseller plan (`sms_reseller_required`), the key lacks the permission (`insufficient_scope`), or the account cannot use the API (`forbidden`). |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Request a sender

`POST https://api.interweb.co.mz/v1/sms/senders`

Needs `sms:write`. Up to 11 letters, numbers and spaces, at most 30 in the account, one request per name and country. The registration fee is paid as in the account portal: the request is created as `payment_pending` and the response brings `payment_url`, the page where it is paid. Once paid, it moves to `documents_needed` and the entity documents are sent in the account portal. If the fee was already paid (a request rejected before refund), it goes straight to `documents_needed` and `payment_url` is null. The team then reviews it and registers it with the operators; approval depends on them. Limited to 20 changes per minute.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |
| `Idempotency-Key` | header | string | no | A unique identifier for this request, so it can be repeated without duplicating it. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `sender` | string | yes |  |
| `entity` | string | yes |  |
| `country` | string: MZ, AO, Mozambique, Angola, Moçambique | yes | Mozambique (`MZ`) or Angola (`AO`), by code or name. |
| `tax_id` | string | yes |  |
| `purpose` | string | yes |  |
| `sample` | string | yes |  |

#### Examples

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/sms/senders" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"sender": "AMINHAMARCA", "entity": "My Company Ltd", "country": "MZ", "tax_id": "123456789", "purpose": "Order updates for our customers.", "sample": "Your order is ready."}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/senders');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"sender": "AMINHAMARCA", "entity": "My Company Ltd", "country": "MZ", "tax_id": "123456789", "purpose": "Order updates for our customers.", "sample": "Your order is ready."}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"sender": "AMINHAMARCA", "entity": "My Company Ltd", "country": "MZ", "tax_id": "123456789", "purpose": "Order updates for our customers.", "sample": "Your order is ready."}
r = requests.post("https://api.interweb.co.mz/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.co.mz/v1/sms/senders", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"sender": "AMINHAMARCA", "entity": "My Company Ltd", "country": "MZ", "tax_id": "123456789", "purpose": "Order updates for our customers.", "sample": "Your order is ready."}),
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `201`

Request a sender.

```json
{
  "data": {
    "id": 7,
    "sender": "AMINHAMARCA",
    "status": "payment_pending",
    "free": false,
    "entity": "My Company Ltd",
    "country": "Moçambique",
    "purpose": "Order updates for our customers.",
    "sample": "Your order is ready.",
    "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": false,
    "payment_url": "https://my.example.com/onestep/checkout?v=3f9a1c2b7d4e5f60718293a4b5c6d7e8&language=english&currency=1"
  },
  "meta": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.status` | string | `payment_pending` (fee to pay), `documents_needed`, `name_needed`, `pending`, `in_review`, `approved` or `rejected`. |
| `data.payment_url` | string, null | The page where the fee is paid; null when there is nothing to pay. |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### See sending statistics

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

Needs `sms:read`. Totals and one line per day with messages. Without dates, the last 30 days; at most 366 days. Failed, cancelled and simulated messages do not count in `credits_spent`. Scheduled messages that were not charged yet do not count either.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `from` | query | string (date) | no | First day, `YYYY-MM-DD`. |
| `to` | query | string (date) | no | Last day, `YYYY-MM-DD`. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/stats');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/stats", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

See sending statistics.

```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": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `422`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### List templates

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

Needs `sms:read`. The templates of this account, by name.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `category` | query | string: general, notice, promotion, billing, confirmation | no | Only this category. |
| `language` | query | string: pt, en | no | Only this language. |
| `page` | query | integer | no | The page number. The first is 1. |
| `per_page` | query | integer | no | Results per page, from 1 to 100. The default is 10. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/templates');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/templates", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

List templates.

```json
{
  "data": [
    {
      "id": 41,
      "name": "Invoice reminder",
      "text": "Hi {$client_first_name}, your invoice is due today.",
      "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
  }
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `422`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Create a template

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

Needs `sms:write`. The name must be unique in the account. The text accepts `{$client_name}`, `{$client_first_name}`, `{$client_last_name}`, `{$client_company_name}`, `{$remetente}` and `{$data}`. Limited to 20 changes per minute.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |
| `Idempotency-Key` | header | string | no | A unique identifier for this request, so it can be repeated without duplicating it. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes |  |
| `text` | string | yes |  |
| `category` | string: general, notice, promotion, billing, confirmation | no |  |
| `language` | string: pt, en | no |  |

#### Examples

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/sms/templates" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"name": "Invoice reminder", "text": "Hi {$client_first_name}, your invoice is due today.", "category": "notice", "language": "pt"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/templates');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"name": "Invoice reminder", "text": "Hi {$client_first_name}, your invoice is due today.", "category": "notice", "language": "pt"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"name": "Invoice reminder", "text": "Hi {$client_first_name}, your invoice is due today.", "category": "notice", "language": "pt"}
r = requests.post("https://api.interweb.co.mz/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.co.mz/v1/sms/templates", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"name": "Invoice reminder", "text": "Hi {$client_first_name}, your invoice is due today.", "category": "notice", "language": "pt"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `201`

Create a template.

```json
{
  "data": {
    "id": 41,
    "name": "Invoice reminder",
    "text": "Hi {$client_first_name}, your invoice is due today.",
    "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": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### See a template

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

Needs `sms:read`. A template of this account.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` of the template. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/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: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/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.co.mz/v1/sms/templates/00000000-0000-4000-8000-000000000000", {
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `200`

See a template.

```json
{
  "data": {
    "id": 41,
    "name": "Invoice reminder",
    "text": "Hi {$client_first_name}, your invoice is due today.",
    "category": "notice",
    "language": "pt",
    "created_at": "2026-11-05T09:30:00+02:00",
    "updated_at": "2026-11-05T09:30:00+02:00"
  },
  "meta": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Change a template

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

Needs `sms:write`. Send only the fields to change. Limited to 20 changes per minute.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` of the template. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | no |  |
| `text` | string | no |  |
| `category` | string: general, notice, promotion, billing, confirmation | no |  |
| `language` | string: pt, en | no |  |

#### Examples

**curl**

```bash
curl -X PATCH "https://api.interweb.co.mz/v1/sms/templates/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hi {$client_first_name}, your invoice is due tomorrow."}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/templates/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_POSTFIELDS => '{"text": "Hi {$client_first_name}, your invoice is due tomorrow."}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
}
payload = {"text": "Hi {$client_first_name}, your invoice is due tomorrow."}
r = requests.patch("https://api.interweb.co.mz/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.co.mz/v1/sms/templates/00000000-0000-4000-8000-000000000000", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"text": "Hi {$client_first_name}, your invoice is due tomorrow."}),
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `200`

Change a template.

```json
{
  "data": {
    "id": 41,
    "name": "Invoice reminder",
    "text": "Hi {$client_first_name}, your invoice is due today.",
    "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": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Delete a template

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

Needs `sms:write`. Standard templates that are deleted do not come back. Limited to 20 changes per minute.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` of the template. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/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: en',
    ],
]);
$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": "en",
}
r = requests.delete("https://api.interweb.co.mz/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.co.mz/v1/sms/templates/00000000-0000-4000-8000-000000000000", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `200`

Delete a template.

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

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### See a sender request

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

Needs `sms:read`. By `id` or by name. `note` is the note from our team. The free sender always answers as `approved`.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `sender` | path | string | yes | The `id` of the request or the name of the sender. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

```bash
curl "https://api.interweb.co.mz/v1/sms/senders/VALUE" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/senders/VALUE');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/senders/VALUE", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

See a sender request.

```json
{
  "data": {
    "id": 7,
    "sender": "AMINHAMARCA",
    "status": "pending",
    "free": false,
    "entity": "My Company Ltd",
    "country": "Mozambique",
    "purpose": "Order updates for our customers.",
    "sample": "Your order is ready.",
    "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": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.status` | string | `payment_pending` (fee to pay), `documents_needed`, `name_needed`, `pending`, `in_review`, `approved` or `rejected`. |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### List numbers that do not receive

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

Needs `sms:read`. The numbers this account blocked, masked. Use `number` to check one.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `number` | query | string | no | Only this number, with or without the country code. |
| `page` | query | integer | no | The page number. The first is 1. |
| `per_page` | query | integer | no | Results per page, from 1 to 100. The default is 10. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

```bash
curl "https://api.interweb.co.mz/v1/sms/opt-outs" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/opt-outs');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/opt-outs", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

List numbers that do not receive.

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

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `422`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Stop sending to a number

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

Needs `sms:write`. From now on, sending to this number answers `422` with `number_opted_out`. To take a number off the list, ask support. Limited to 20 changes per minute.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |
| `Idempotency-Key` | header | string | no | A unique identifier for this request, so it can be repeated without duplicating it. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `number` | string | yes |  |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/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: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"number": "+258841234567"}
r = requests.post("https://api.interweb.co.mz/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.co.mz/v1/sms/opt-outs", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"number": "+258841234567"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `201`

Stop sending to a number.

```json
{
  "data": {
    "number": "25884****567",
    "created_at": "2026-11-05T09:30:00+02:00",
    "already_listed": false,
    "simulated": true
  },
  "meta": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `422`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Cancel a scheduled SMS

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

Needs `sms:write`. Only messages of this account that are still `scheduled` can be cancelled. If the credits were already charged they go back to the balance, and `credits_returned` says how many; a scheduled message is usually only charged when it goes out, so there is often nothing to return. A message that already went out, is going out, or was already cancelled returns `409` with `details.reason` set to `not_cancellable`. With a Sandbox key, or while the API is in preparation, it checks everything and changes nothing. Send `Idempotency-Key` so a retry gives the same answer.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` returned when sending. |
| `Idempotency-Key` | header | string | no | Any unique value, up to 255 visible characters. Valid for 24 hours per key. Repeating the same request returns the saved answer; a different body, or a first request still in progress, returns 409. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/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: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
r = requests.post("https://api.interweb.co.mz/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.co.mz/v1/sms/messages/00000000-0000-4000-8000-000000000000/cancel", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Idempotency-Key": "9f1c2d3e-example",
  },
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `200`

Cancel a scheduled SMS.

```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": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.id` | string | A number for messages in the queue, a UUID for simulated ones. |
| `data.status` | string: scheduled, queued, sending, sent, simulated, failed, cancelled | `scheduled`: waiting for the time in `scheduled_for`, and it can still be cancelled. `queued`: waiting its turn to go out. |
| `data.simulated` | boolean | True when no real SMS goes out. |
| `data.to` | string | The number, partly hidden. |
| `data.credits` | number | What the message costs in credits. See `charged` to know whether they already left the SMS balance. Always 0 when simulated by the API. |
| `data.would_cost` | number | Only on simulated messages: what a real send would cost. |
| `data.error` | string, null | `delivery_failed` when it could not be delivered; `insufficient_credits` when a scheduled message did not have enough balance when it was due to go out. |
| `data.charged` | boolean | True when the credits have left the SMS balance. A scheduled message is only charged when it goes out; failed, cancelled and simulated messages give the credits back. |
| `data.scheduled_for` | string, null (date-time) | The time chosen in `send_at`, while the message is scheduled or after it was cancelled; otherwise `null`. |
| `data.credits_returned` | number | Credits that went back to the SMS balance with this cancellation. |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |
| `X-API-Simulated` | Present and `true` when this write was simulated: nothing changed and nothing was charged. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

This account does not have an active SMS reseller plan (`sms_reseller_required`), the key lacks the permission (`insufficient_scope`), or the account cannot use the API (`forbidden`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `sms_reseller_required`, `insufficient_scope`, `forbidden` | This account does not have an active SMS reseller plan (`sms_reseller_required`), the key lacks the permission (`insufficient_scope`), or the account cannot use the API (`forbidden`). |
| 404 | `not_found` | Not found. |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### List contacts

`GET https://api.interweb.co.mz/v1/sms/contacts`

Needs `sms:read`. The SMS contacts of this account, by name, with their groups.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `group` | query | string | no | Only the contacts in this group (its id). |
| `search` | query | string | no | Name, company, email or part of the number. |
| `page` | query | integer | no | The page number. The first is 1. |
| `per_page` | query | integer | no | Results per page, from 1 to 100. The default is 10. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

```bash
curl "https://api.interweb.co.mz/v1/sms/contacts" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/contacts');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/contacts", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

List contacts.

```json
{
  "data": [
    {
      "id": 512,
      "name": "Ana Silva",
      "company": "Example Ltd",
      "phone": "+258841234567",
      "email": "ana@example.com",
      "notes": "Prefers mornings",
      "groups": [
        {
          "id": 9,
          "name": "Customers"
        }
      ],
      "created_at": "2026-11-11T09:30:00+02:00",
      "updated_at": null
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 10,
    "total": 1,
    "total_pages": 1
  }
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `422`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Create a contact

`POST https://api.interweb.co.mz/v1/sms/contacts`

Needs `sms:write`. The phone number must be unique in the account. Up to 20,000 contacts. Limited to 20 changes per minute.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |
| `Idempotency-Key` | header | string | no | A unique identifier for this request, so it can be repeated without duplicating it. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | no | The contact name. Name or company is required. |
| `company` | string | no | Optional. |
| `phone` | string | yes | With or without the country code; unique in the account. |
| `email` | string | no | Optional. |
| `notes` | string | no | Optional. |
| `groups` | array<integer> | no | Optional. Group ids from GET /v1/sms/groups; replaces the current groups. |

#### Examples

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/sms/contacts" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"name": "Ana Silva", "company": "Example Ltd", "phone": "+258841234567", "email": "ana@example.com", "groups": [9]}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/contacts');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"name": "Ana Silva", "company": "Example Ltd", "phone": "+258841234567", "email": "ana@example.com", "groups": [9]}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"name": "Ana Silva", "company": "Example Ltd", "phone": "+258841234567", "email": "ana@example.com", "groups": [9]}
r = requests.post("https://api.interweb.co.mz/v1/sms/contacts", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/sms/contacts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"name": "Ana Silva", "company": "Example Ltd", "phone": "+258841234567", "email": "ana@example.com", "groups": [9]}),
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `201`

Create a contact.

```json
{
  "data": {
    "id": 512,
    "name": "Ana Silva",
    "company": "Example Ltd",
    "phone": "+258841234567",
    "email": "ana@example.com",
    "notes": "Prefers mornings",
    "groups": [
      {
        "id": 9,
        "name": "Customers"
      }
    ],
    "created_at": "2026-11-11T09:30:00+02:00",
    "updated_at": null,
    "simulated": false
  },
  "meta": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Get a contact

`GET https://api.interweb.co.mz/v1/sms/contacts/{id}`

Needs `sms:read`.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` of the contact. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/contacts/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/contacts/00000000-0000-4000-8000-000000000000", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Get a contact.

```json
{
  "data": {
    "id": 512,
    "name": "Ana Silva",
    "company": "Example Ltd",
    "phone": "+258841234567",
    "email": "ana@example.com",
    "notes": "Prefers mornings",
    "groups": [
      {
        "id": 9,
        "name": "Customers"
      }
    ],
    "created_at": "2026-11-11T09:30:00+02:00",
    "updated_at": null
  },
  "meta": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Update a contact

`PATCH https://api.interweb.co.mz/v1/sms/contacts/{id}`

Needs `sms:write`. Send only the fields to change; `groups` replaces the current groups. Limited to 20 changes per minute.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` of the contact. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | no | The contact name. Name or company is required. |
| `company` | string | no | Optional. |
| `phone` | string | no | With or without the country code; unique in the account. |
| `email` | string | no | Optional. |
| `notes` | string | no | Optional. |
| `groups` | array<integer> | no | Optional. Group ids from GET /v1/sms/groups; replaces the current groups. |

#### Examples

**curl**

```bash
curl -X PATCH "https://api.interweb.co.mz/v1/sms/contacts/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Content-Type: application/json" \
  -d '{"notes": "Call after 10am", "groups": [9, 12]}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/contacts/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_POSTFIELDS => '{"notes": "Call after 10am", "groups": [9, 12]}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
}
payload = {"notes": "Call after 10am", "groups": [9, 12]}
r = requests.patch("https://api.interweb.co.mz/v1/sms/contacts/00000000-0000-4000-8000-000000000000", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/sms/contacts/00000000-0000-4000-8000-000000000000", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"notes": "Call after 10am", "groups": [9, 12]}),
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `200`

Update a contact.

```json
{
  "data": {
    "id": 512,
    "name": "Ana Silva",
    "company": "Example Ltd",
    "phone": "+258841234567",
    "email": "ana@example.com",
    "notes": "Call after 10am",
    "groups": [
      {
        "id": 9,
        "name": "Customers"
      }
    ],
    "created_at": "2026-11-11T09:30:00+02:00",
    "updated_at": null,
    "simulated": false
  },
  "meta": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Delete a contact

`DELETE https://api.interweb.co.mz/v1/sms/contacts/{id}`

Needs `sms:write`. Messages already sent stay in the history. Limited to 20 changes per minute.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` of the contact. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/contacts/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: en',
    ],
]);
$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": "en",
}
r = requests.delete("https://api.interweb.co.mz/v1/sms/contacts/00000000-0000-4000-8000-000000000000", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Delete a contact.

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

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### List contact groups

`GET https://api.interweb.co.mz/v1/sms/groups`

Needs `sms:read`. The groups of this account, by name, with how many contacts each one has.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `page` | query | integer | no | The page number. The first is 1. |
| `per_page` | query | integer | no | Results per page, from 1 to 100. The default is 10. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

```bash
curl "https://api.interweb.co.mz/v1/sms/groups" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/groups');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/groups", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

List contact groups.

```json
{
  "data": [
    {
      "id": 9,
      "name": "Customers",
      "contacts": 128,
      "created_at": "2026-11-11T09:30:00+02:00"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 10,
    "total": 1,
    "total_pages": 1
  }
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `422`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Create a contact group

`POST https://api.interweb.co.mz/v1/sms/groups`

Needs `sms:write`. The name must be unique in the account. Up to 100 groups. Limited to 20 changes per minute.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |
| `Idempotency-Key` | header | string | no | A unique identifier for this request, so it can be repeated without duplicating it. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Unique in the account. |

#### Examples

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/sms/groups" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"name": "Customers"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/groups');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"name": "Customers"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"name": "Customers"}
r = requests.post("https://api.interweb.co.mz/v1/sms/groups", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `201`

Create a contact group.

```json
{
  "data": {
    "id": 9,
    "name": "Customers",
    "contacts": 0,
    "created_at": "2026-11-11T09:30:00+02:00",
    "simulated": false
  },
  "meta": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Get a contact group

`GET https://api.interweb.co.mz/v1/sms/groups/{id}`

Needs `sms:read`.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` of the group. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/groups/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/groups/00000000-0000-4000-8000-000000000000", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Get a contact group.

```json
{
  "data": {
    "id": 9,
    "name": "Customers",
    "contacts": 128,
    "created_at": "2026-11-11T09:30:00+02:00"
  },
  "meta": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Rename a contact group

`PATCH https://api.interweb.co.mz/v1/sms/groups/{id}`

Needs `sms:write`. Limited to 20 changes per minute.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` of the group. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Unique in the account. |

#### Examples

**curl**

```bash
curl -X PATCH "https://api.interweb.co.mz/v1/sms/groups/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Content-Type: application/json" \
  -d '{"name": "VIP customers"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/groups/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_POSTFIELDS => '{"name": "VIP customers"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
}
payload = {"name": "VIP customers"}
r = requests.patch("https://api.interweb.co.mz/v1/sms/groups/00000000-0000-4000-8000-000000000000", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/sms/groups/00000000-0000-4000-8000-000000000000", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"name": "VIP customers"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `200`

Rename a contact group.

```json
{
  "data": {
    "id": 9,
    "name": "VIP customers",
    "contacts": 128,
    "created_at": "2026-11-11T09:30:00+02:00",
    "simulated": false
  },
  "meta": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Delete a contact group

`DELETE https://api.interweb.co.mz/v1/sms/groups/{id}`

Needs `sms:write`. The contacts are not deleted: they just leave the group. Limited to 20 changes per minute.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` of the group. |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/groups/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: en',
    ],
]);
$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": "en",
}
r = requests.delete("https://api.interweb.co.mz/v1/sms/groups/00000000-0000-4000-8000-000000000000", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Delete a contact group.

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

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Send a verification code

`POST https://api.interweb.co.mz/v1/sms/otp`

Needs `sms:write`. We generate the code, send it by SMS straight away and keep only its hash. Verify it with POST /v1/sms/otp/{id}/verify. A new code for the same number cancels the previous one. No limits by default; you can set a maximum per number per hour in PUT /v1/sms/otp-settings. The SMS costs credits like any other; with a sandbox key nothing is sent or charged and the response includes `code`, so you can test the verification.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |
| `Idempotency-Key` | header | string | no | Any unique value, up to 255 visible characters. Valid for 24 hours per key. Repeating the same request returns the saved answer; a different body, or a first request still in progress, returns 409. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `number` | string | yes | Mobile number in Mozambique or Angola, with or without the country code. |
| `sender` | string | no | Optional. An approved sender ID for the country of the number; without it, the free sender. |
| `length` | integer | no | Digits in the code. |
| `expires_in` | integer | no | Minutes the code stays valid. |
| `max_attempts` | integer | no | Wrong attempts allowed before the code fails. |
| `text` | string | no | Optional. Your own text, with `{code}` once and, if you like, `{minutes}`. The default text fits in one SMS. |

#### Examples

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/sms/otp" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"number": "+258841234567", "sender": "MYBRAND", "length": 6, "expires_in": 5}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/otp');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"number": "+258841234567", "sender": "MYBRAND", "length": 6, "expires_in": 5}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"number": "+258841234567", "sender": "MYBRAND", "length": 6, "expires_in": 5}
r = requests.post("https://api.interweb.co.mz/v1/sms/otp", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/sms/otp", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"number": "+258841234567", "sender": "MYBRAND", "length": 6, "expires_in": 5}),
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `201`

Send a verification code.

```json
{
  "data": {
    "id": "otp_3f9a1c2b7d4e5f60718293a4b5c6d7e8",
    "status": "pending",
    "to": "+25884***4567",
    "sender": "MYBRAND",
    "attempts": 0,
    "attempts_left": 5,
    "max_attempts": 5,
    "message_id": "18342",
    "mode": "production",
    "expires_at": "2026-11-12T10:35:00+02:00",
    "verified_at": null,
    "created_at": "2026-11-12T10:30:00+02:00",
    "parts": 1,
    "simulated": false
  },
  "meta": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.status` | string: pending, verified, expired, failed, cancelled | `pending` (waiting for the code), `verified`, `expired`, `failed` (no attempts left) or `cancelled` (a newer code was sent, or cancelled). |
| `data.code` | string | Only with a sandbox key. |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Get a verification code

`GET https://api.interweb.co.mz/v1/sms/otp/{id}`

Needs `sms:read`. The status, never the code.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` returned when the code was sent (otp_...). |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/otp/00000000-0000-4000-8000-000000000000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/otp/00000000-0000-4000-8000-000000000000", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Get a verification code.

```json
{
  "data": {
    "id": "otp_3f9a1c2b7d4e5f60718293a4b5c6d7e8",
    "status": "pending",
    "to": "+25884***4567",
    "sender": "MYBRAND",
    "attempts": 0,
    "attempts_left": 5,
    "max_attempts": 5,
    "message_id": "18342",
    "mode": "production",
    "expires_at": "2026-11-12T10:35:00+02:00",
    "verified_at": null,
    "created_at": "2026-11-12T10:30:00+02:00"
  },
  "meta": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.status` | string: pending, verified, expired, failed, cancelled | `pending` (waiting for the code), `verified`, `expired`, `failed` (no attempts left) or `cancelled` (a newer code was sent, or cancelled). |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Verify a code

`POST https://api.interweb.co.mz/v1/sms/otp/{id}/verify`

Needs `sms:write`. Compares the code the user typed in constant time. `valid` says if it matched; each wrong code uses one attempt and, with none left, the status becomes `failed`. Spaces are ignored. A verified, expired or failed code always answers `valid: false`.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` returned when the code was sent (otp_...). |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |
| `Idempotency-Key` | header | string | no | A unique identifier for this request, so it can be repeated without duplicating it. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | string | yes | The code the user typed. |

#### Examples

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/sms/otp/00000000-0000-4000-8000-000000000000/verify" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"code": "482913"}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/otp/00000000-0000-4000-8000-000000000000/verify');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"code": "482913"}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"code": "482913"}
r = requests.post("https://api.interweb.co.mz/v1/sms/otp/00000000-0000-4000-8000-000000000000/verify", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/sms/otp/00000000-0000-4000-8000-000000000000/verify", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"code": "482913"}),
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `200`

Verify a code.

```json
{
  "data": {
    "id": "otp_3f9a1c2b7d4e5f60718293a4b5c6d7e8",
    "status": "verified",
    "to": "+25884***4567",
    "sender": "MYBRAND",
    "attempts": 1,
    "attempts_left": 4,
    "max_attempts": 5,
    "message_id": "18342",
    "mode": "production",
    "expires_at": "2026-11-12T10:35:00+02:00",
    "verified_at": "2026-11-12T10:31:10+02:00",
    "created_at": "2026-11-12T10:30:00+02:00",
    "valid": true
  },
  "meta": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.status` | string: pending, verified, expired, failed, cancelled | `pending` (waiting for the code), `verified`, `expired`, `failed` (no attempts left) or `cancelled` (a newer code was sent, or cancelled). |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Cancel a verification code

`POST https://api.interweb.co.mz/v1/sms/otp/{id}/cancel`

Needs `sms:write`. A pending code stops being valid. The SMS already sent is not refunded.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` returned when the code was sent (otp_...). |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |
| `Idempotency-Key` | header | string | no | A unique identifier for this request, so it can be repeated without duplicating it. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/otp/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: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
r = requests.post("https://api.interweb.co.mz/v1/sms/otp/00000000-0000-4000-8000-000000000000/cancel", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Cancel a verification code.

```json
{
  "data": {
    "id": "otp_3f9a1c2b7d4e5f60718293a4b5c6d7e8",
    "status": "cancelled",
    "to": "+25884***4567",
    "sender": "MYBRAND",
    "attempts": 0,
    "attempts_left": 5,
    "max_attempts": 5,
    "message_id": "18342",
    "mode": "production",
    "expires_at": "2026-11-12T10:35:00+02:00",
    "verified_at": null,
    "created_at": "2026-11-12T10:30:00+02:00"
  },
  "meta": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.status` | string: pending, verified, expired, failed, cancelled | `pending` (waiting for the code), `verified`, `expired`, `failed` (no attempts left) or `cancelled` (a newer code was sent, or cancelled). |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Send messages in a batch

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

Needs `sms:write`. No limit on the number of messages (only the request size, 1 MB, counts), each with its own number and, if you like, its own text; `sender`, `text` and `send_at` apply to all. Each message follows the same rules as POST /v1/sms/messages and `results` says what happened to each one (`error` uses the same reasons). Messages join the queue (sent every minute); up to 20 go out straight away. Without enough credits or at the daily limit, the rest are rejected. With a sandbox key nothing is sent or charged.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |
| `Idempotency-Key` | header | string | no | Any unique value, up to 255 visible characters. Valid for 24 hours per key. Repeating the same request returns the saved answer; a different body, or a first request still in progress, returns 409. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `sender` | string | no |  |
| `text` | string | no | Text for every message without its own. |
| `send_at` | string | no | Optional, ISO 8601 with time zone. |
| `messages` | array<object> | yes |  |

#### Examples

**curl**

```bash
curl -X POST "https://api.interweb.co.mz/v1/sms/messages/batch" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Idempotency-Key: 9f1c2d3e-example" \
  -H "Content-Type: application/json" \
  -d '{"sender": "MYBRAND", "text": "Your order is ready for pickup.", "messages": [{"number": "+258841234567"}, {"number": "+244923456789", "text": "Your order is ready in Luanda."}, {"number": "12"}]}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/messages/batch');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => '{"sender": "MYBRAND", "text": "Your order is ready for pickup.", "messages": [{"number": "+258841234567"}, {"number": "+244923456789", "text": "Your order is ready in Luanda."}, {"number": "12"}]}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
payload = {"sender": "MYBRAND", "text": "Your order is ready for pickup.", "messages": [{"number": "+258841234567"}, {"number": "+244923456789", "text": "Your order is ready in Luanda."}, {"number": "12"}]}
r = requests.post("https://api.interweb.co.mz/v1/sms/messages/batch", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/sms/messages/batch", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Idempotency-Key": "9f1c2d3e-example",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"sender": "MYBRAND", "text": "Your order is ready for pickup.", "messages": [{"number": "+258841234567"}, {"number": "+244923456789", "text": "Your order is ready in Luanda."}, {"number": "12"}]}),
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `201`

Send messages in a batch.

```json
{
  "data": {
    "total": 3,
    "accepted": 2,
    "rejected": 1,
    "credits": 2.0,
    "scheduled_for": null,
    "simulated": false,
    "results": [
      {
        "index": 0,
        "id": "18401",
        "status": "queued",
        "to": "25884****567",
        "parts": 1
      },
      {
        "index": 1,
        "id": "18402",
        "status": "queued",
        "to": "24492****789",
        "parts": 1
      },
      {
        "index": 2,
        "id": null,
        "status": "rejected",
        "error": "invalid_number"
      }
    ]
  },
  "meta": {}
}
```

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Resend a verification code

`POST https://api.interweb.co.mz/v1/sms/otp/{id}/resend`

Needs `sms:write`. Sends a NEW code on the same request (`id` stays the same) and restarts the validity; wrong attempts already used stay used. No limit on resends by default; a wait between resends and an hourly limit per number apply only if you set them in PUT /v1/sms/otp-settings. Only for `pending` or `expired` codes. With a sandbox key the response includes `code`.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string | yes | The `id` returned when the code was sent (otp_...). |
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |
| `Idempotency-Key` | header | string | no | A unique identifier for this request, so it can be repeated without duplicating it. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/otp/00000000-0000-4000-8000-000000000000/resend');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
r = requests.post("https://api.interweb.co.mz/v1/sms/otp/00000000-0000-4000-8000-000000000000/resend", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Resend a verification code.

```json
{
  "data": {
    "id": "otp_3f9a1c2b7d4e5f60718293a4b5c6d7e8",
    "status": "pending",
    "to": "25884****567",
    "sender": "MYBRAND",
    "attempts": 1,
    "attempts_left": 4,
    "max_attempts": 5,
    "message_id": "18410",
    "mode": "production",
    "resends": 1,
    "expires_at": "2026-11-12T10:41:00+02:00",
    "verified_at": null,
    "created_at": "2026-11-12T10:30:00+02:00",
    "simulated": false
  },
  "meta": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.status` | string: pending, verified, expired, failed, cancelled | `pending` (waiting for the code), `verified`, `expired`, `failed` (no attempts left) or `cancelled` (a newer code was sent, or cancelled). |
| `data.code` | string | Only with a sandbox key. |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Get the delivery webhook

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

Needs `sms:read`. The address, the events and the last delivery; never the secret. We POST a JSON body `{"event": "sms.sent" | "sms.failed", "created_at", "data": {...}}` (`data` has the same fields as GET /v1/sms/messages/{id}) to your HTTPS address on port 443, only for messages created after the webhook was saved. Check the `X-Webhook-Signature: t=<unix time>,v1=<hex>` header: v1 is HMAC-SHA256 of `t + "." + raw body` with your secret; reject old timestamps. Answer with any 2xx within 5 seconds; otherwise we retry after 1 min, 5 min, 30 min, 2 h and 6 h. Internal or private addresses are refused.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/webhook');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/webhook", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Get the delivery webhook.

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

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Save the delivery webhook

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

Needs `sms:write`. Creates or changes the address. The `secret` is shown only when the webhook is created or with `rotate_secret: true`: keep it. We POST a JSON body `{"event": "sms.sent" | "sms.failed", "created_at", "data": {...}}` (`data` has the same fields as GET /v1/sms/messages/{id}) to your HTTPS address on port 443, only for messages created after the webhook was saved. Check the `X-Webhook-Signature: t=<unix time>,v1=<hex>` header: v1 is HMAC-SHA256 of `t + "." + raw body` with your secret; reject old timestamps. Answer with any 2xx within 5 seconds; otherwise we retry after 1 min, 5 min, 30 min, 2 h and 6 h. Internal or private addresses are refused.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | string | yes | HTTPS address, port 443. |
| `events` | array<string: sms.sent, sms.failed> | no | Default: both. |
| `rotate_secret` | boolean | no | Create a new secret. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/webhook');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_POSTFIELDS => '{"url": "https://example.com/webhooks/sms", "events": ["sms.sent", "sms.failed"]}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
}
payload = {"url": "https://example.com/webhooks/sms", "events": ["sms.sent", "sms.failed"]}
r = requests.put("https://api.interweb.co.mz/v1/sms/webhook", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Save the delivery webhook.

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

| Field | Type | Description |
|---|---|---|
| `data.secret` | string | Only when created or rotated. |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Delete the delivery webhook

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

Needs `sms:write`. Pending deliveries are cancelled.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/webhook');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.delete("https://api.interweb.co.mz/v1/sms/webhook", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Delete the delivery webhook.

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

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Test the delivery webhook

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

Needs `sms:write`. Sends a signed `webhook.test` event to the saved address now and returns what your server answered.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |
| `Idempotency-Key` | header | string | no | A unique identifier for this request, so it can be repeated without duplicating it. |

#### Examples

**curl**

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

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/webhook/test');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
    "Idempotency-Key": "9f1c2d3e-example",
}
r = requests.post("https://api.interweb.co.mz/v1/sms/webhook/test", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Test the delivery webhook.

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

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Get the verification code rules

`GET https://api.interweb.co.mz/v1/sms/otp-settings`

Needs `sms:read`. By default there are no limits (0).

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:read`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Examples

**curl**

```bash
curl "https://api.interweb.co.mz/v1/sms/otp-settings" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en"
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/otp-settings');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
    ],
]);
$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": "en",
}
r = requests.get("https://api.interweb.co.mz/v1/sms/otp-settings", headers=headers, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

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


#### Responses

##### `200`

Get the verification code rules.

```json
{
  "data": {
    "resend_wait_seconds": 0,
    "max_codes_per_number_per_hour": 0
  },
  "meta": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.resend_wait_seconds` | integer | Seconds between resends of the same code. 0 = no limit (default). |
| `data.max_codes_per_number_per_hour` | integer | Codes (new and resent) per number per hour. 0 = no limit (default). |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).


### Set the verification code rules

`PUT https://api.interweb.co.mz/v1/sms/otp-settings`

Needs `sms:write`. Send only what you want to change; 0 removes the limit. The same rules can be changed in the account portal, on the API keys page.

#### Authentication

Needs a valid key in the `Authorization: Bearer` header. Required permission: `sms:write`.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-API-Mode` | header | string: sandbox, production | no | Optional. If it does not match the mode of the key, the request is refused with `invalid_request` and reason `mode_mismatch`. Use it to make sure a program never runs with the wrong key. |

#### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `resend_wait_seconds` | integer | no | Seconds between resends of the same code. 0 = no limit (default). |
| `max_codes_per_number_per_hour` | integer | no | Codes (new and resent) per number per hour. 0 = no limit (default). |

#### Examples

**curl**

```bash
curl -X PUT "https://api.interweb.co.mz/v1/sms/otp-settings" \
  -H "Authorization: Bearer $RLX_API_KEY" \
  -H "Accept-Language: en" \
  -H "Content-Type: application/json" \
  -d '{"resend_wait_seconds": 30, "max_codes_per_number_per_hour": 5}'
```

**PHP**

```php
<?php
$ch = curl_init('https://api.interweb.co.mz/v1/sms/otp-settings');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_POSTFIELDS => '{"resend_wait_seconds": 30, "max_codes_per_number_per_hour": 5}',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RLX_API_KEY'),
        'Accept-Language: en',
        '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": "en",
}
payload = {"resend_wait_seconds": 30, "max_codes_per_number_per_hour": 5}
r = requests.put("https://api.interweb.co.mz/v1/sms/otp-settings", headers=headers, json=payload, timeout=30)
print(r.status_code)
print(r.text)
```

**JavaScript**

```javascript
const response = await fetch("https://api.interweb.co.mz/v1/sms/otp-settings", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.RLX_API_KEY}`,
    "Accept-Language": "en",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"resend_wait_seconds": 30, "max_codes_per_number_per_hour": 5}),
});
console.log(response.status);
console.log(await response.text());
```


#### Responses

##### `200`

Set the verification code rules.

```json
{
  "data": {
    "resend_wait_seconds": 30,
    "max_codes_per_number_per_hour": 5,
    "simulated": false
  },
  "meta": {}
}
```

| Field | Type | Description |
|---|---|---|
| `data.resend_wait_seconds` | integer | Seconds between resends of the same code. 0 = no limit (default). |
| `data.max_codes_per_number_per_hour` | integer | Codes (new and resent) per number per hour. 0 = no limit (default). |

| Header | Description |
|---|---|
| `X-Correlation-ID` | Identifier of this request. Give it to support when you report a problem. |
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | When the minute ends, as a Unix timestamp in seconds. |
| `X-API-Mode` | The mode of the key that made the request: `sandbox` or `production`. |

##### `401`

The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account.

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `403`

The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`).

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the required permission.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "required": "dns:read"
    }
  }
}
```

##### `404`

Not found.

```json
{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `409`

The operation cannot be done in the current state. See `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`

Some of the data is not valid.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Some of the data is not valid.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `429`

Too many requests. Wait for the time in Retry-After.

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please try again shortly.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {
      "retry_after": 42
    }
  }
}
```

| Header | Description |
|---|---|
| `Retry-After` | Seconds to wait before trying again. |

##### `500`

An internal error occurred. Give the correlation_id to support.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred. Please give the correlation_id to support.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```

##### `503`

The API is turned off.

```json
{
  "error": {
    "code": "disabled",
    "message": "The API is currently turned off.",
    "correlation_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "details": {}
  }
}
```


#### Errors

| Status | Code | Meaning |
|---|---|---|
| 401 | `unauthorized` | The key is missing, invalid, revoked or expired, or the user who created it no longer has access to the account. |
| 403 | `forbidden`, `insufficient_scope`, `ip_not_allowed` | The account cannot use the API (`forbidden`), the key lacks the permission (`insufficient_scope`), or the IP address is not on the list of the key (`ip_not_allowed`). |
| 404 | `not_found` | Not found. |
| 409 | `conflict` | The operation cannot be done in the current state. See `details.reason`. |
| 422 | `validation_failed` | Some of the data is not valid. |
| 429 | `rate_limited` | Too many requests. Wait for the time in Retry-After. |
| 500 | `internal_error` | An internal error occurred. Give the correlation_id to support. |
| 503 | `disabled` | The API is turned off. |

The error format is explained in [Errors](https://docs.interweb.co.mz/en/api-rules.md).
