# RelySMS — API para receber SMS — para o seu software

Se o seu software já funciona com um serviço de ativações via handler_api.php, ele funciona com a RelySMS: troque o endereço do servidor e a chave. Mesmas ações, mesmas respostas, nossos preços e reembolsos.

- Endereço: `https://relysms.pro/stubs/handler_api.php`
- Auth: `api_key` parameter
- Format: plain text tokens, JSON where noted

## Início rápido

1. Entre na sua conta e abra Saldo → API → Criar chave. A chave aparece uma vez só — guarde-a.
2. Recarregue até $20 ou mais: compras pela API só passam com esse saldo. Ter a chave com saldo zero não tem problema.
3. Configure no seu software o servidor https://relysms.pro/stubs/handler_api.php e a sua chave no lugar da do serviço anterior.
4. Compre um número (getNumber) e consulte o status a cada 5–10 segundos (getStatus) até chegar STATUS_OK com o código.
5. Finalize o pedido (setStatus=6). Se o código não vier, cancele após 2 minutos (setStatus=8) ou deixe a janela terminar: o dinheiro volta sozinho.

## Regras e limites

- Compras pela API exigem saldo de pelo menos $20. Abaixo disso, getNumber responde NO_BALANCE; os outros métodos funcionam.
- O número é seu por 20 minutos. Sem código, o pedido fecha sozinho e o dinheiro volta ao saldo.
- Dá para cancelar 2 minutos após a compra, se o código ainda não veio. O dinheiro volta na hora.
- Depois do primeiro código o pedido está pago e não é reembolsado. Enquanto a janela estiver aberta, outros códigos ainda podem chegar.
- Até 20 requisições e 5 compras por segundo por chave; até 50 pedidos abertos ao mesmo tempo.
- O preço é fixado na compra. maxPrice impede comprar acima do seu limite.
- Valem os termos do serviço: spam, fraude e cadastros em massa para esses fins são proibidos.

## Métodos

Toda requisição é um GET ou POST para um único endereço, com parâmetros na URL ou num formulário. A chave vai em api_key, a ação em action.

### `getBalance`

O saldo da conta em dólares.

Apenas api_key e action.

```
GET https://relysms.pro/stubs/handler_api.php?api_key=KEY&action=getBalance
```

Resposta:

```
ACCESS_BALANCE:25.00
```

### `getNumber`

Comprar um número para um serviço num país. Cobrado na hora; reembolsado se o código não vier. Resposta: ACCESS_NUMBER:número_do_pedido:telefone.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `service` | sim | Código do serviço (getServicesList), ex.: tg. |
| `country` | sim | Código do país (getCountries). |
| `maxPrice` | não | Preço máximo em dólares. Acima disso: WRONG_MAX_PRICE com o preço atual, nada cobrado. |

```
GET https://relysms.pro/stubs/handler_api.php?api_key=KEY&action=getNumber&service=tg&country=187&maxPrice=0.80
```

Resposta:

```
ACCESS_NUMBER:1042:15550001111
```

Erros possíveis: `BAD_SERVICE`, `BAD_COUNTRY`, `NO_NUMBERS`, `NO_BALANCE`, `WRONG_MAX_PRICE:0.59`, `LIMIT_ACTIVATIONS`, `RATE_LIMIT`, `BANNED`

### `getNumberV2`

O mesmo que getNumber, com resposta em JSON incluindo o preço e os horários da janela.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `service` | sim | Código do serviço. |
| `country` | sim | Código do país. |
| `maxPrice` | não | Preço máximo em dólares. |

```
GET https://relysms.pro/stubs/handler_api.php?api_key=KEY&action=getNumberV2&service=tg&country=187&maxPrice=0.80
```

Resposta:

```
{
  "activationId": 1042,
  "phoneNumber": "15550001111",
  "activationCost": 0.59,
  "countryCode": "187",
  "canGetAnotherSms": true,
  "activationTime": "2026-10-27T10:00:00.000Z",
  "activationEndTime": "2026-10-27T10:20:00.000Z"
}
```

Erros possíveis: `BAD_SERVICE`, `BAD_COUNTRY`, `NO_NUMBERS`, `NO_BALANCE`, `WRONG_MAX_PRICE:0.59`, `LIMIT_ACTIVATIONS`, `RATE_LIMIT`

### `getStatus`

Status do pedido: aguardando código, código recebido (com o código) ou cancelado.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `id` | sim | Número do pedido vindo do getNumber. |

```
GET https://relysms.pro/stubs/handler_api.php?api_key=KEY&action=getStatus&id=1042
```

Resposta:

```
STATUS_OK:48213
```

Erros possíveis: `NO_ACTIVATION`

### `setStatus`

Agir sobre o pedido: pronto, outro código, finalizar, cancelar (veja Status).

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `id` | sim | Número do pedido. |
| `status` | sim | 1, 3, 6 ou 8. |

```
GET https://relysms.pro/stubs/handler_api.php?api_key=KEY&action=setStatus&id=1042&status=6
```

Resposta:

```
ACCESS_ACTIVATION
```

Erros possíveis: `NO_ACTIVATION`, `BAD_STATUS`, `EARLY_CANCEL_DENIED`, `ERROR_SQL`

### `getActiveActivations`

Todos os pedidos abertos da chave: número, status, códigos e textos recebidos.

Apenas api_key e action.

```
GET https://relysms.pro/stubs/handler_api.php?api_key=KEY&action=getActiveActivations
```

Resposta:

```
{
  "status": "success",
  "activeActivations": [
    {
      "activationId": 1042,
      "serviceCode": "tg",
      "phoneNumber": "15550001111",
      "activationCost": 0.59,
      "activationStatus": "2",
      "smsCode": ["48213"],
      "smsText": ["Telegram code: 48213"],
      "activationTime": "2026-10-27T10:00:00.000Z",
      "countryCode": "187"
    }
  ]
}
```

### `getPrices`

Nossos preços em dólares e quantos números há, aproximadamente. Sem parâmetros: tudo o que está à venda.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `service` | não | Só este serviço. |
| `country` | não | Só este país. |

```
GET https://relysms.pro/stubs/handler_api.php?api_key=KEY&action=getPrices&service=tg&country=187
```

Resposta:

```
{ "187": { "tg": { "cost": 0.59, "count": 1000 } } }
```

Erros possíveis: `BAD_SERVICE`, `BAD_COUNTRY`

### `getCountries`

Países com códigos do protocolo e nomes em russo e inglês.

Apenas api_key e action.

```
GET https://relysms.pro/stubs/handler_api.php?api_key=KEY&action=getCountries
```

Resposta:

```
{ "187": { "id": 187, "rus": "США", "eng": "United States", "visible": 1 } }
```

### `getServicesList`

Serviços com códigos do protocolo.

Apenas api_key e action.

```
GET https://relysms.pro/stubs/handler_api.php?api_key=KEY&action=getServicesList
```

Resposta:

```
{ "status": "success", "services": [{ "code": "tg", "name": "Telegram" }] }
```

## Status

Valores de status no setStatus:

- `status=1` → `ACCESS_READY` — O número foi digitado no serviço; aguardando o código. Não muda nada; mantido por compatibilidade.
- `status=3` → `ACCESS_RETRY_GET` — Pedir outro código no mesmo número — depois do primeiro código, enquanto o pedido estiver aberto. Grátis. Até chegar, getStatus responde STATUS_WAIT_RETRY.
- `status=6` → `ACCESS_ACTIVATION` — Finalizar o pedido após o código. Não chegarão mais SMS ao número.
- `status=8` → `ACCESS_CANCEL` — Cancelar um pedido sem código (não antes de 2 minutos). O dinheiro volta ao saldo.

Respostas do getStatus:

- `STATUS_WAIT_CODE` — Ainda sem código — pergunte de novo em 5–10 segundos.
- `STATUS_OK:48213` — O código chegou; depois dos dois-pontos vem o código (ou o texto do SMS, se não houver código).
- `STATUS_WAIT_RETRY:48213` — Outro código foi pedido e ainda não chegou; depois dos dois-pontos vem o código anterior.
- `STATUS_CANCEL` — Cancelado ou encerrado sem código; o dinheiro já voltou ao saldo.

## Erros

Os erros chegam como texto simples, como é comum neste protocolo. Todos, exceto RATE_LIMIT, chegam com status 200.

- `NO_KEY` — api_key não enviado.
- `BAD_KEY` — Chave errada ou desativada, ou conta bloqueada.
- `BAD_ACTION` — Ação desconhecida.
- `BAD_SERVICE` — Esse código de serviço não existe.
- `BAD_COUNTRY` — Esse código de país não existe.
- `NO_NUMBERS` — Sem números para essa combinação agora; nada cobrado.
- `NO_BALANCE` — Menos de $20 de saldo (limite da API) ou menos que o preço.
- `WRONG_MAX_PRICE` — O preço passa de maxPrice; depois dos dois-pontos vem o preço atual.
- `LIMIT_ACTIVATIONS` — Pedidos abertos demais — finalize ou cancele alguns.
- `RATE_LIMIT` — Frequente demais; HTTP 429. Tente de novo em um segundo.
- `BANNED` — A conta está bloqueada — fale com o suporte.
- `NO_ACTIVATION` — Não há pedido com esse número para esta chave.
- `BAD_STATUS` — Essa ação não está disponível agora para o pedido (ex.: finalizar sem código).
- `EARLY_CANCEL_DENIED` — Só dá para cancelar 2 minutos após a compra.
- `ERROR_SQL` — Erro temporário do nosso lado — repita a solicitação em um minuto.

## Códigos de serviços e países

Os códigos são os padrão do protocolo: tg — Telegram, wa — WhatsApp etc.; o país 187 — EUA. getServicesList e getCountries retornam as listas completas — pegue os códigos de lá, não de tabelas antigas: serviços e países mudam com o estoque.

## REST API v1

Os mesmos números e as mesmas regras do handler_api.php, em JSON: serviços por nome (telegram), países por código ISO (US), status HTTP e códigos de erro para o programa decidir. Use-a em código novo; mantenha o handler_api.php para o software que já o usa.

- Base URL: `https://relysms.pro/api/v1`
- Auth: `Authorization: Bearer <key>` header
- Format: JSON

### Chave

Envie a chave num cabeçalho: Authorization: Bearer <chave> (ou X-Api-Key: <chave>). Nunca na URL — URLs vão parar nos logs. Os limites, o mínimo de $20 e o catálogo são os mesmos do handler_api.php.

### Repetições sem compras duplicadas

Envie o cabeçalho Idempotency-Key com um valor seu (um UUID, até 255 caracteres) em POST /orders e /orders/batch. Se a conexão cair e você repetir a requisição com a mesma chave, recebe a primeira resposta (com o cabeçalho Idempotent-Replayed: true) e nada é comprado duas vezes. A mesma chave com outro corpo é recusada; uma repetição que chega enquanto a primeira ainda está em andamento recebe idempotency_in_progress. Respostas bem-sucedidas ficam guardadas por 24 horas; uma compra que falhou não cobrou nada e pode ser repetida com a mesma chave.

### Vários números de uma vez

POST /orders/batch compra até 20 números de um serviço e país numa só requisição — é o jeito de comprar mais de 5 por segundo. As compras vão uma a uma; a primeira que falha encerra o lote, e a resposta traz o que foi comprado e o motivo em stoppedBy. Se nada foi comprado, a resposta é esse erro.

### Páginas

GET /orders traz os mais novos primeiro. Quando next não for null, passe-o como before para obter a próxima página.

### Webhooks

Em vez de consultar GET /orders/{id}, defina um endereço HTTPS com PUT /webhook e enviaremos um POST quando algo acontecer com um pedido comprado pela API. O corpo traz o pedido como está no momento do envio. Responda com qualquer 2xx em até 10 segundos; um pedido pode gerar vários eventos e o mesmo evento pode chegar duas vezes após uma repetição, então use o id da entrega.

- `order.code_received` — Um código chegou (cada novo envia isto de novo).
- `order.finished` — O pedido foi concluído — por você, ou quando o tempo acabou depois de um código.
- `order.cancelled` — Cancelado sem código; o dinheiro voltou ao saldo.
- `order.expired` — O tempo acabou sem código; o dinheiro voltou ao saldo.
- `ping` — Um teste de POST /webhook/test; order é null.

```http
POST /relysms-hook HTTP/1.1
Content-Type: application/json
X-RelySMS-Event: order.code_received
X-RelySMS-Delivery: cm…
X-RelySMS-Signature: t=1793095272,v1=5f2b…

{
  "id": "cm…",
  "event": "order.code_received",
  "createdAt": "2026-10-27T10:01:12.000Z",
  "order": { "id": 1042, "status": "received", "codes": [{ "code": "48213", … }], … }
}
```

Cada requisição traz X-RelySMS-Signature: t=<hora unix>,v1=<assinatura>. A assinatura é HMAC-SHA256 da string "t.corpo" (o corpo bruto, byte a byte) com o segredo do webhook, em hex. Verifique-a, e que t tenha menos de 5 minutos, antes de confiar no corpo.

```python
import hashlib, hmac, time

def verify(secret: str, body: bytes, header: str, tolerance=300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t, sig = parts.get("t", ""), parts.get("v1", "")
    if not t.isdigit() or abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)
```

```javascript
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret, rawBody, header, tolerance = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > tolerance) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return expected.length === parts.v1?.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
```

Se o seu servidor não responder 2xx, repetimos após 10 segundos, 1 minuto, 5 minutos, 30 minutos, 2 horas e 6 horas — 7 tentativas no total — e então desistimos. GET /webhook/deliveries mostra o que foi enviado e o que falhou.

### Endpoints

#### `GET /balance`

O saldo em dólares e se ele basta para comprar pela API.

```bash
curl -H "Authorization: Bearer $KEY" https://relysms.pro/api/v1/balance
```

Resposta:

```json
{ "balance": 25, "currency": "USD", "canBuy": true, "apiMinimum": 20 }
```

#### `GET /services`

Todos os serviços à venda: nosso id, o nome e o código do handler_api.php.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `lang` (query) | não | Idioma dos nomes: en (padrão), ru, uk, zh, es, pt, id, vi, tr, ar, fr. |

```bash
curl -H "Authorization: Bearer $KEY" "https://relysms.pro/api/v1/services?lang=en"
```

Resposta:

```json
{ "services": [{ "id": "telegram", "name": "Telegram", "protocolCode": "tg" }] }
```

#### `GET /countries`

Todos os países à venda: código ISO, nome e código do handler_api.php.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `lang` (query) | não | Idioma dos nomes: en (padrão), ru, uk, zh, es, pt, id, vi, tr, ar, fr. |

```bash
curl -H "Authorization: Bearer $KEY" "https://relysms.pro/api/v1/countries?lang=en"
```

Resposta:

```json
{ "countries": [{ "id": "US", "name": "United States", "protocolCode": 187 }] }
```

#### `GET /prices`

Nossos preços em dólares e quantos números há, aproximadamente. Sem parâmetros, tudo o que está à venda.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `service` (query) | não | Id do serviço de /services, ex.: telegram. |
| `country` (query) | não | Código ISO do país de /countries, ex.: US. |

```bash
curl -H "Authorization: Bearer $KEY" "https://relysms.pro/api/v1/prices?service=telegram"
```

Resposta:

```json
{ "prices": [{ "service": "telegram", "country": "US", "price": 0.59, "stock": 1000 }] }
```

Erros possíveis: `unknown_service`, `unknown_country`

#### `POST /orders`

Comprar um número. Cobrado na hora e devolvido se o código não chegar.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `service` (body) | sim | Id do serviço de /services, ex.: telegram. |
| `country` (body) | sim | Código ISO do país de /countries, ex.: US. |
| `maxPrice` (body) | não | Preço máximo em dólares, um número. Acima disso — price_above_max com o preço atual, nada cobrado. |

```bash
curl -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7c1f0e2a-run-1" \
  -d '{"service":"telegram","country":"US","maxPrice":0.8}' \
  https://relysms.pro/api/v1/orders
```

Resposta:

```json
{
  "order": {
    "id": 1042,
    "service": "telegram",
    "country": "US",
    "phone": "15550001111",
    "price": 0.59,
    "status": "waiting",
    "refunded": false,
    "codes": [],
    "awaitingAnotherCode": false,
    "createdAt": "2026-10-27T10:00:00.000Z",
    "expiresAt": "2026-10-27T10:20:00.000Z",
    "cancelableAt": "2026-10-27T10:02:00.000Z"
  }
}
```

Erros possíveis: `bad_request`, `unknown_service`, `unknown_country`, `no_numbers`, `insufficient_balance`, `below_api_minimum`, `price_above_max`, `too_many_open_orders`, `buying_too_fast`, `idempotency_key_reused`, `idempotency_in_progress`

#### `POST /orders/batch`

Comprar até 20 números de um serviço e país numa só requisição.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `service` (body) | sim | Id do serviço de /services, ex.: telegram. |
| `country` (body) | sim | Código ISO do país de /countries, ex.: US. |
| `quantity` (body) | sim | Quantos números, de 1 a 20. |
| `maxPrice` (body) | não | Preço máximo em dólares, um número. Acima disso — price_above_max com o preço atual, nada cobrado. |

```bash
curl -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7c1f0e2a-batch-1" \
  -d '{"service":"telegram","country":"US","quantity":5}' \
  https://relysms.pro/api/v1/orders/batch
```

Resposta:

```json
{
  "orders": [{ "id": 1043, "phone": "15550001112", … }, { "id": 1044, … }],
  "requested": 5,
  "placed": 2,
  "stoppedBy": { "code": "no_numbers", "message": "…" }
}
```

Erros possíveis: `bad_request`, `unknown_service`, `unknown_country`, `no_numbers`, `insufficient_balance`, `below_api_minimum`, `price_above_max`, `too_many_open_orders`, `buying_too_fast`, `idempotency_key_reused`, `idempotency_in_progress`

#### `GET /orders`

Seus pedidos, os mais novos primeiro: os abertos por padrão, ou todos.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `status` (query) | não | open (padrão) — aguardando ou com código; all — todos os pedidos. |
| `limit` (query) | não | Pedidos por página, de 1 a 100 (padrão 50). |
| `before` (query) | não | O valor next da página anterior. |

```bash
curl -H "Authorization: Bearer $KEY" "https://relysms.pro/api/v1/orders?status=all&limit=50"
```

Resposta:

```json
{ "orders": [{ "id": 1042, … }], "next": "1001" }
```

Erros possíveis: `bad_request`

#### `GET /orders/{id}`

Um pedido com seus códigos. Consulte a cada 5–10 segundos enquanto o status for waiting.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `id` (path) | sim | O id do pedido na resposta da compra. |

```bash
curl -H "Authorization: Bearer $KEY" https://relysms.pro/api/v1/orders/1042
```

Resposta:

```json
{
  "order": {
    "id": 1042,
    "status": "received",
    "codes": [{ "code": "48213", "text": "Telegram code: 48213", "receivedAt": "2026-10-27T10:01:12.000Z" }],
    …
  }
}
```

Erros possíveis: `not_found`

#### `POST /orders/{id}/finish`

Concluir um pedido com código. O número não receberá mais SMS. Repetir não faz mal.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `id` (path) | sim | O id do pedido na resposta da compra. |

```bash
curl -X POST -H "Authorization: Bearer $KEY" https://relysms.pro/api/v1/orders/1042/finish
```

Resposta:

```json
{ "order": { "id": 1042, "status": "finished", … } }
```

Erros possíveis: `not_found`, `invalid_state`

#### `POST /orders/{id}/another-code`

Outro código no mesmo número — depois do primeiro código, enquanto o pedido estiver aberto. Grátis. Até chegar, o pedido tem awaitingAnotherCode = true.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `id` (path) | sim | O id do pedido na resposta da compra. |

```bash
curl -X POST -H "Authorization: Bearer $KEY" https://relysms.pro/api/v1/orders/1042/another-code
```

Resposta:

```json
{ "order": { "id": 1042, "status": "received", "awaitingAnotherCode": true, … } }
```

Erros possíveis: `not_found`, `invalid_state`, `try_later`

#### `POST /orders/{id}/cancel`

Cancelar um pedido sem código, a partir de 2 minutos após a compra. O dinheiro volta na hora.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `id` (path) | sim | O id do pedido na resposta da compra. |

```bash
curl -X POST -H "Authorization: Bearer $KEY" https://relysms.pro/api/v1/orders/1042/cancel
```

Resposta:

```json
{ "order": { "id": 1042, "status": "cancelled", "refunded": true, … } }
```

Erros possíveis: `not_found`, `invalid_state`, `too_early_to_cancel`

#### `GET /webhook`

O endereço do webhook e seu segredo de assinatura, ou null.

```bash
curl -H "Authorization: Bearer $KEY" https://relysms.pro/api/v1/webhook
```

Resposta:

```json
{ "webhook": { "url": "https://example.com/relysms-hook", "secret": "whsec_…", "createdAt": "2026-10-27T10:00:00.000Z" } }
```

#### `PUT /webhook`

Definir ou mudar o endereço. O segredo é criado uma vez e mantido; rotateSecret cria um novo.

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `url` (body) | sim | Endereço HTTPS do seu servidor. Endereços privados e locais são recusados. |
| `rotateSecret` (body) | não | true — criar um novo segredo de assinatura. |

```bash
curl -X PUT -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/relysms-hook"}' \
  https://relysms.pro/api/v1/webhook
```

Resposta:

```json
{ "webhook": { "url": "https://example.com/relysms-hook", "secret": "whsec_…", "createdAt": "2026-10-27T10:00:00.000Z" } }
```

Erros possíveis: `bad_request`, `invalid_webhook_url`

#### `DELETE /webhook`

Remover o webhook. Eventos ainda não enviados são descartados.

```bash
curl -X DELETE -H "Authorization: Bearer $KEY" https://relysms.pro/api/v1/webhook
```

Resposta:

```json
{ "webhook": null }
```

#### `POST /webhook/test`

Enviar um evento ping para verificar o endereço e a assinatura.

```bash
curl -X POST -H "Authorization: Bearer $KEY" https://relysms.pro/api/v1/webhook/test
```

Resposta:

```json
{ "delivery": { "id": "cm…" } }
```

Erros possíveis: `not_found`

#### `GET /webhook/deliveries`

As últimas 20 entregas: evento, status, tentativas, última resposta do seu servidor.

```bash
curl -H "Authorization: Bearer $KEY" https://relysms.pro/api/v1/webhook/deliveries
```

Resposta:

```json
{
  "deliveries": [{
    "id": "cm…", "event": "order.code_received", "orderId": 1042,
    "status": "pending", "attempts": 2, "lastHttpStatus": 502, "lastError": "HTTP 502",
    "createdAt": "2026-10-27T10:01:12.000Z", "deliveredAt": null, "nextAttemptAt": "2026-10-27T10:02:22.000Z"
  }]
}
```

### Status do pedido

O campo status de um pedido:

- `waiting` — O número é seu e nenhum código chegou ainda.
- `received` — Um código chegou; está em codes. Outros podem chegar enquanto o pedido estiver aberto.
- `finished` — Você concluiu o pedido (ou o tempo acabou depois de um código).
- `cancelled` — Cancelado sem código; refunded é true e o dinheiro está no saldo.
- `expired` — O tempo acabou sem código; o dinheiro voltou ao saldo.

### Erros

Um erro é um status HTTP e um JSON com code para o programa decidir e message para pessoas. Alguns trazem campos extras (price, cancelableAt, apiMinimum).

```json
{ "error": { "code": "price_above_max", "message": "The price is above maxPrice.", "price": 0.59 } }
```

- `missing_key` (HTTP 401) — Sem chave em Authorization ou X-Api-Key.
- `invalid_key` (HTTP 401) — A chave está errada, foi substituída ou a conta está bloqueada.
- `bad_request` (HTTP 400) — O corpo ou um parâmetro está errado; message diz qual.
- `unknown_service` (HTTP 400) — Não existe esse id de serviço.
- `unknown_country` (HTTP 400) — Não existe esse código de país.
- `idempotency_key_invalid` (HTTP 400) — Idempotency-Key está vazio ou passa de 255 caracteres.
- `invalid_webhook_url` (HTTP 400) — O endereço do webhook não é HTTPS, não é uma URL ou aponta para um servidor privado ou local.
- `insufficient_balance` (HTTP 402) — O saldo é menor que o preço.
- `below_api_minimum` (HTTP 402) — Menos de $20 de saldo — a API só compra a partir de $20.
- `account_blocked` (HTTP 403) — A conta está bloqueada — fale com o suporte.
- `not_found` (HTTP 404) — Nenhum pedido com esse id para esta chave.
- `no_numbers` (HTTP 409) — Sem números para este par agora; nada cobrado.
- `price_above_max` (HTTP 409) — O preço passa de maxPrice; price traz o atual.
- `too_many_open_orders` (HTTP 409) — Pedidos abertos demais — conclua ou cancele alguns.
- `invalid_state` (HTTP 409) — Não é possível neste status (ex.: concluir sem código).
- `too_early_to_cancel` (HTTP 409) — Dá para cancelar a partir de cancelableAt, 2 minutos após a compra.
- `idempotency_in_progress` (HTTP 409) — Uma requisição com este Idempotency-Key ainda está em andamento; repita em instantes.
- `idempotency_key_reused` (HTTP 422) — Este Idempotency-Key já foi usado em outra requisição.
- `rate_limited` (HTTP 429) — Mais de 20 requisições por segundo; Retry-After diz quando repetir.
- `buying_too_fast` (HTTP 429) — Mais de 5 compras por segundo; use /orders/batch.
- `try_later` (HTTP 503) — Não deu certo agora — tente em um minuto.

### Exemplos REST

Comprar um número, esperar o código, concluir o pedido.

```python
import time, uuid, requests

BASE = "https://relysms.pro/api/v1"
S = requests.Session()
S.headers["Authorization"] = "Bearer rs_your_key"

r = S.post(f"{BASE}/orders",
           json={"service": "telegram", "country": "US", "maxPrice": 0.8},
           headers={"Idempotency-Key": str(uuid.uuid4())}, timeout=30)
if r.status_code != 201:
    raise SystemExit(r.json()["error"])   # no_numbers, below_api_minimum, …
order = r.json()["order"]
print("number:", order["phone"])

while order["status"] == "waiting":       # up to 20 minutes
    time.sleep(10)
    order = S.get(f"{BASE}/orders/{order['id']}", timeout=30).json()["order"]

if order["status"] == "received":
    print("code:", order["codes"][-1]["code"])
    S.post(f"{BASE}/orders/{order['id']}/finish", timeout=30)
# "expired" or "cancelled": the money is back on the balance
```

```javascript
const BASE = "https://relysms.pro/api/v1";
const headers = { Authorization: "Bearer rs_your_key", "Content-Type": "application/json" };

const res = await fetch(`${BASE}/orders`, {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({ service: "telegram", country: "US", maxPrice: 0.8 }),
});
if (res.status !== 201) throw new Error((await res.json()).error.code);
let { order } = await res.json();
console.log("number:", order.phone);

while (order.status === "waiting") {
  await new Promise((r) => setTimeout(r, 10_000));
  ({ order } = await (await fetch(`${BASE}/orders/${order.id}`, { headers })).json());
}
if (order.status === "received") {
  console.log("code:", order.codes.at(-1).code);
  await fetch(`${BASE}/orders/${order.id}/finish`, { method: "POST", headers });
}
```

## Exemplos

O ciclo completo: comprar um número, aguardar o código e finalizar o pedido.

```bash
KEY=rs_your_key
BASE=https://relysms.pro/stubs/handler_api.php

curl "$BASE?api_key=$KEY&action=getBalance"
curl "$BASE?api_key=$KEY&action=getNumber&service=tg&country=187&maxPrice=0.80"
# → ACCESS_NUMBER:1042:15550001111
curl "$BASE?api_key=$KEY&action=getStatus&id=1042"
# → STATUS_WAIT_CODE … then STATUS_OK:48213
curl "$BASE?api_key=$KEY&action=setStatus&id=1042&status=6"
```

```python
import time, requests

BASE = "https://relysms.pro/stubs/handler_api.php"
KEY = "rs_your_key"

def call(action, **params):
    r = requests.get(BASE, params={"api_key": KEY, "action": action, **params}, timeout=30)
    r.raise_for_status()
    return r.text

answer = call("getNumber", service="tg", country="187", maxPrice="0.80")
if not answer.startswith("ACCESS_NUMBER"):
    raise SystemExit(answer)          # NO_NUMBERS, NO_BALANCE, …
_, order_id, phone = answer.split(":")
print("number:", phone)

for _ in range(120):                  # up to 20 minutes, every 10 s
    status = call("getStatus", id=order_id)
    if status.startswith("STATUS_OK:"):
        print("code:", status.split(":", 1)[1])
        call("setStatus", id=order_id, status=6)
        break
    if status == "STATUS_CANCEL":
        break                         # refunded automatically
    time.sleep(10)
```

```javascript
const BASE = "https://relysms.pro/stubs/handler_api.php";
const KEY = "rs_your_key";

async function call(action, params = {}) {
  const url = new URL(BASE);
  url.search = new URLSearchParams({ api_key: KEY, action, ...params });
  const res = await fetch(url);
  return res.text();
}

const answer = await call("getNumber", { service: "tg", country: "187", maxPrice: "0.80" });
if (!answer.startsWith("ACCESS_NUMBER")) throw new Error(answer);
const [, id, phone] = answer.split(":");
console.log("number:", phone);

for (let i = 0; i < 120; i++) {
  const status = await call("getStatus", { id });
  if (status.startsWith("STATUS_OK:")) {
    console.log("code:", status.slice("STATUS_OK:".length));
    await call("setStatus", { id, status: "6" });
    break;
  }
  if (status === "STATUS_CANCEL") break; // refunded automatically
  await new Promise((r) => setTimeout(r, 10_000));
}
```

## Perguntas

**Meu software usava outro serviço de ativações. O que eu mudo?**

O endereço do servidor para https://relysms.pro/stubs/handler_api.php e a chave pela da sua conta. Os códigos de serviços e países continuam os mesmos.

**Por que NO_BALANCE se tenho saldo?**

Compras pela API exigem saldo de $20 ou mais. A conta e o Mini App não têm esse limite.

**E o dinheiro, se o código não vier?**

Volta sozinho ao saldo quando a janela fecha, ou na hora se você cancelar após 2 minutos.

**Dá para receber um segundo código no mesmo número?**

Sim, enquanto a janela estiver aberta: setStatus=3 e depois getStatus de novo.

**Existe modo de teste?**

Não: todas as compras são reais. Comece por uma combinação barata e teste o fluxo com um número.

**Minha chave vazou. E agora?**

Conta → Saldo → API → Gerar uma nova. A antiga para de funcionar na hora.
