# RelySMS — SMS receiving API — for your own software

If your software already works with an activation service over handler_api.php, it works with RelySMS: change the server address and the key. Same actions, same answers, our prices and refunds.

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

## Quick start

1. Sign in and open Balance → API → Create a key. The key is shown once — save it.
2. Top up to $20 or more: purchases through the API go through only with that balance. Holding a key with a zero balance is fine.
3. Point your software at https://relysms.pro/stubs/handler_api.php and use your key instead of the previous service's.
4. Buy a number (getNumber), then poll the status every 5–10 seconds (getStatus) until STATUS_OK arrives with the code.
5. Finish the order (setStatus=6). If no code comes, cancel after 2 minutes (setStatus=8) or just let the window end: the money comes back on its own.

## Rules and limits

- Purchases through the API need a balance of at least $20. Below that, getNumber answers NO_BALANCE; every other method works.
- The number is yours for 20 minutes. No code — the order closes itself and the money returns to your balance.
- You can cancel 2 minutes after buying if no code has come. The money comes back at once.
- Once the first code arrives the order is paid and not refunded. While the window is open, further codes can still arrive.
- Up to 20 requests and 5 purchases per second per key; up to 50 open orders at a time.
- The price is fixed at purchase. maxPrice keeps you from buying above your limit.
- The service terms apply: spam, fraud and mass sign-ups for those ends are forbidden.

## Methods

Every request is a GET or POST to one address, with parameters in the query string or a form body. The key goes in api_key, the action in action.

### `getBalance`

The account balance in dollars.

Only api_key and action.

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

Answer:

```
ACCESS_BALANCE:25.00
```

### `getNumber`

Buy a number for a service in a country. Charged at once; refunded if no code comes. Answer: ACCESS_NUMBER:order_number:phone.

| Parameter | Required | Description |
|---|---|---|
| `service` | yes | Service code (getServicesList), e.g. tg. |
| `country` | yes | Country code (getCountries). |
| `maxPrice` | no | Maximum price in dollars. Above it — WRONG_MAX_PRICE with the current price, nothing charged. |

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

Answer:

```
ACCESS_NUMBER:1042:15550001111
```

Possible errors: `BAD_SERVICE`, `BAD_COUNTRY`, `NO_NUMBERS`, `NO_BALANCE`, `WRONG_MAX_PRICE:0.59`, `LIMIT_ACTIVATIONS`, `RATE_LIMIT`, `BANNED`

### `getNumberV2`

The same as getNumber, answered as JSON with the price and the window's times.

| Parameter | Required | Description |
|---|---|---|
| `service` | yes | Service code. |
| `country` | yes | Country code. |
| `maxPrice` | no | Maximum price in dollars. |

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

Answer:

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

Possible errors: `BAD_SERVICE`, `BAD_COUNTRY`, `NO_NUMBERS`, `NO_BALANCE`, `WRONG_MAX_PRICE:0.59`, `LIMIT_ACTIVATIONS`, `RATE_LIMIT`

### `getStatus`

The order's status: waiting, code received (with the code), or cancelled.

| Parameter | Required | Description |
|---|---|---|
| `id` | yes | Order number from getNumber. |

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

Answer:

```
STATUS_OK:48213
```

Possible errors: `NO_ACTIVATION`

### `setStatus`

Act on an order: ready, another code, finish, cancel (see Statuses).

| Parameter | Required | Description |
|---|---|---|
| `id` | yes | Order number. |
| `status` | yes | 1, 3, 6 or 8. |

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

Answer:

```
ACCESS_ACTIVATION
```

Possible errors: `NO_ACTIVATION`, `BAD_STATUS`, `EARLY_CANCEL_DENIED`, `ERROR_SQL`

### `getActiveActivations`

Every open order of the key: number, status, codes and texts received.

Only api_key and action.

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

Answer:

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

Our prices in dollars and roughly how many numbers. Without parameters — everything on sale.

| Parameter | Required | Description |
|---|---|---|
| `service` | no | Only this service. |
| `country` | no | Only this country. |

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

Answer:

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

Possible errors: `BAD_SERVICE`, `BAD_COUNTRY`

### `getCountries`

Countries with protocol codes and names in Russian and English.

Only api_key and action.

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

Answer:

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

### `getServicesList`

Services with protocol codes.

Only api_key and action.

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

Answer:

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

## Statuses

Values of status in setStatus:

- `status=1` → `ACCESS_READY` — The number is entered in the service, waiting for the code. Changes nothing; kept for compatibility.
- `status=3` → `ACCESS_RETRY_GET` — Ask for another code on the same number — after the first code, while the order is open. Free. Until it comes, getStatus answers STATUS_WAIT_RETRY.
- `status=6` → `ACCESS_ACTIVATION` — Finish the order after the code. No more SMS will reach the number.
- `status=8` → `ACCESS_CANCEL` — Cancel an order without a code (not before 2 minutes). The money returns to your balance.

getStatus answers:

- `STATUS_WAIT_CODE` — No code yet — ask again in 5–10 seconds.
- `STATUS_OK:48213` — The code arrived; after the colon is the code (or the SMS text if it has no code).
- `STATUS_WAIT_RETRY:48213` — Another code was asked for and hasn't come yet; after the colon is the previous code.
- `STATUS_CANCEL` — Cancelled or closed without a code; the money is already back.

## Errors

Errors are plain strings, as is usual for this protocol. All but RATE_LIMIT come with status 200.

- `NO_KEY` — api_key is missing.
- `BAD_KEY` — Wrong or disabled key, or a blocked account.
- `BAD_ACTION` — Unknown action.
- `BAD_SERVICE` — No such service code.
- `BAD_COUNTRY` — No such country code.
- `NO_NUMBERS` — No numbers for this pair right now; nothing charged.
- `NO_BALANCE` — Less than $20 on the balance (the API floor), or less than the price.
- `WRONG_MAX_PRICE` — The price is above maxPrice; the current price follows the colon.
- `LIMIT_ACTIVATIONS` — Too many open orders — finish or cancel some.
- `RATE_LIMIT` — Too frequent; HTTP 429. Retry in a second.
- `BANNED` — The account is blocked — contact support.
- `NO_ACTIVATION` — No order with that number for this key.
- `BAD_STATUS` — That action isn't available for the order now (e.g. finishing without a code).
- `EARLY_CANCEL_DENIED` — Cancelling is allowed 2 minutes after the purchase.
- `ERROR_SQL` — A temporary error on our side — repeat the request in a minute.

## Service and country codes

The codes are the protocol's standard ones: tg — Telegram, wa — WhatsApp and so on; country 187 — the US. getServicesList and getCountries return the full lists — take codes from there, not from old tables: services and countries change with stock.

## REST API v1

The same numbers and the same rules as handler_api.php, as JSON: services by name (telegram), countries by ISO code (US), HTTP statuses and error codes a program can branch on. Use it for new code; keep handler_api.php for software that already speaks it.

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

### Key

Send the key in a header: Authorization: Bearer <key> (or X-Api-Key: <key>). Never in the URL — URLs end up in logs. The limits, the $20 floor and the catalogue are the same as for handler_api.php.

### Retries without double purchases

Send Idempotency-Key with a value of your own (a UUID, up to 255 characters) on POST /orders and /orders/batch. If the connection drops and you repeat the request with the same key, you get the first answer back (with the header Idempotent-Replayed: true) and nothing is bought twice. The same key with a different body is refused; a repeat that arrives while the first is still running gets idempotency_in_progress. Successful answers are kept for 24 hours; a purchase that failed charged nothing and can be repeated with the same key.

### Several numbers at once

POST /orders/batch buys up to 20 numbers for one service and country in one request — this is the way to buy faster than 5 a second. Purchases go one by one; the first that fails stops the batch, and the answer lists what was bought plus the reason in stoppedBy. If nothing was bought, the answer is that error.

### Pages

GET /orders returns the newest first. When next is not null, pass it as before to get the next page.

### Webhooks

Instead of polling GET /orders/{id}, set an HTTPS address with PUT /webhook and we POST to it when something happens to an order bought through the API. The body carries the order as it is at the moment of sending. Answer with any 2xx within 10 seconds; one order can produce several events, and the same event can come twice after a retry, so key on the delivery id.

- `order.code_received` — A code arrived (each new one sends this again).
- `order.finished` — The order was finished — by you, or when its time ran out after a code.
- `order.cancelled` — Cancelled with no code; the money is back on the balance.
- `order.expired` — Time ran out with no code; the money is back on the balance.
- `ping` — A test from POST /webhook/test; order is 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", … }], … }
}
```

Every request carries X-RelySMS-Signature: t=<unix time>,v1=<signature>. The signature is HMAC-SHA256 of the string "t.body" (the raw body, byte for byte) with your webhook secret, in hex. Check it, and that t is within 5 minutes, before trusting the body.

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

If your server doesn't answer 2xx, we retry after 10 seconds, 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours — 7 attempts in all — then give up. GET /webhook/deliveries shows what was sent and what failed.

### Endpoints

#### `GET /balance`

The balance in dollars, and whether it is enough to buy through the API.

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

Answer:

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

#### `GET /services`

Every service on offer: our id, the name and the handler_api.php code.

| Parameter | Required | Description |
|---|---|---|
| `lang` (query) | no | Language of the names: en (default), ru, uk, zh, es, pt, id, vi, tr, ar, fr. |

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

Answer:

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

#### `GET /countries`

Every country on offer: ISO code, name and the handler_api.php code.

| Parameter | Required | Description |
|---|---|---|
| `lang` (query) | no | Language of the names: en (default), ru, uk, zh, es, pt, id, vi, tr, ar, fr. |

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

Answer:

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

#### `GET /prices`

Our prices in dollars and roughly how many numbers are available. Without parameters, everything on sale.

| Parameter | Required | Description |
|---|---|---|
| `service` (query) | no | Service id from /services, e.g. telegram. |
| `country` (query) | no | Country ISO code from /countries, e.g. US. |

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

Answer:

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

Possible errors: `unknown_service`, `unknown_country`

#### `POST /orders`

Buy a number. The money is charged at once and returned if no code arrives.

| Parameter | Required | Description |
|---|---|---|
| `service` (body) | yes | Service id from /services, e.g. telegram. |
| `country` (body) | yes | Country ISO code from /countries, e.g. US. |
| `maxPrice` (body) | no | Highest price in dollars, a number. Above it — price_above_max with the current price, nothing charged. |

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

Answer:

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

Possible errors: `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`

Buy up to 20 numbers for one service and country in one request.

| Parameter | Required | Description |
|---|---|---|
| `service` (body) | yes | Service id from /services, e.g. telegram. |
| `country` (body) | yes | Country ISO code from /countries, e.g. US. |
| `quantity` (body) | yes | How many numbers, 1 to 20. |
| `maxPrice` (body) | no | Highest price in dollars, a number. Above it — price_above_max with the current price, nothing charged. |

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

Answer:

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

Possible errors: `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`

Your orders, newest first: the open ones by default, or all.

| Parameter | Required | Description |
|---|---|---|
| `status` (query) | no | open (default) — waiting or with a code; all — every order. |
| `limit` (query) | no | Orders per page, 1 to 100 (default 50). |
| `before` (query) | no | The next value from the previous page. |

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

Answer:

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

Possible errors: `bad_request`

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

One order with its codes. Poll it every 5–10 seconds while the status is waiting.

| Parameter | Required | Description |
|---|---|---|
| `id` (path) | yes | The order id from the purchase answer. |

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

Answer:

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

Possible errors: `not_found`

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

Finish an order that has a code. The number will take no more SMS. Repeating is harmless.

| Parameter | Required | Description |
|---|---|---|
| `id` (path) | yes | The order id from the purchase answer. |

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

Answer:

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

Possible errors: `not_found`, `invalid_state`

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

Another code on the same number — after the first code, while the order is open. Free. Until it comes, the order has awaitingAnotherCode = true.

| Parameter | Required | Description |
|---|---|---|
| `id` (path) | yes | The order id from the purchase answer. |

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

Answer:

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

Possible errors: `not_found`, `invalid_state`, `try_later`

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

Cancel an order with no code, from 2 minutes after purchase. The money goes back at once.

| Parameter | Required | Description |
|---|---|---|
| `id` (path) | yes | The order id from the purchase answer. |

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

Answer:

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

Possible errors: `not_found`, `invalid_state`, `too_early_to_cancel`

#### `GET /webhook`

The webhook address and its signing secret, or null.

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

Answer:

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

#### `PUT /webhook`

Set or change the address. The secret is made once and kept; rotateSecret makes a new one.

| Parameter | Required | Description |
|---|---|---|
| `url` (body) | yes | HTTPS address of your server. Private and local addresses are refused. |
| `rotateSecret` (body) | no | true — make a new signing secret. |

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

Answer:

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

Possible errors: `bad_request`, `invalid_webhook_url`

#### `DELETE /webhook`

Remove the webhook. Events not yet sent are dropped.

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

Answer:

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

#### `POST /webhook/test`

Send a ping event to check the address and the signature.

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

Answer:

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

Possible errors: `not_found`

#### `GET /webhook/deliveries`

The last 20 deliveries: event, status, attempts, your server's last answer.

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

Answer:

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

### Order statuses

The status field of an order:

- `waiting` — The number is yours and no code has come yet.
- `received` — A code has come; it is in codes. More codes can come while the order is open.
- `finished` — You finished the order (or its time ran out after a code).
- `cancelled` — Cancelled with no code; refunded is true and the money is on the balance.
- `expired` — Time ran out with no code; the money is back on the balance.

### Errors

An error is an HTTP status and a JSON body with a code to branch on and a message for people. Some carry extra fields (price, cancelableAt, apiMinimum).

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

- `missing_key` (HTTP 401) — No key in Authorization or X-Api-Key.
- `invalid_key` (HTTP 401) — The key is wrong, was replaced, or the account is blocked.
- `bad_request` (HTTP 400) — The body or a parameter is wrong; message says which.
- `unknown_service` (HTTP 400) — No such service id.
- `unknown_country` (HTTP 400) — No such country code.
- `idempotency_key_invalid` (HTTP 400) — Idempotency-Key is empty or longer than 255 characters.
- `invalid_webhook_url` (HTTP 400) — The webhook address is not HTTPS, not a URL, or points to a private or local server.
- `insufficient_balance` (HTTP 402) — The balance is below the price.
- `below_api_minimum` (HTTP 402) — Less than $20 on the balance — the API buys only from $20.
- `account_blocked` (HTTP 403) — The account is blocked — write to support.
- `not_found` (HTTP 404) — No order with this id for this key.
- `no_numbers` (HTTP 409) — No numbers for this pair right now; nothing charged.
- `price_above_max` (HTTP 409) — The price is above maxPrice; price holds the current one.
- `too_many_open_orders` (HTTP 409) — Too many open orders — finish or cancel some.
- `invalid_state` (HTTP 409) — Not possible in this status (e.g. finishing without a code).
- `too_early_to_cancel` (HTTP 409) — Cancelling is possible from cancelableAt, 2 minutes after purchase.
- `idempotency_in_progress` (HTTP 409) — A request with this Idempotency-Key is still running; repeat in a moment.
- `idempotency_key_reused` (HTTP 422) — This Idempotency-Key was already used for a different request.
- `rate_limited` (HTTP 429) — More than 20 requests a second; Retry-After says when to repeat.
- `buying_too_fast` (HTTP 429) — More than 5 purchases a second; use /orders/batch.
- `try_later` (HTTP 503) — Temporarily didn't work — try again in a minute.

### REST examples

Buy a number, wait for the code, finish the order.

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

## Examples

The whole cycle: buy a number, wait for the code, finish the order.

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

## Questions

**My software used another activation service. What do I change?**

The server address — to https://relysms.pro/stubs/handler_api.php; the key — to the one from your account. Service and country codes stay the same.

**Why NO_BALANCE when I have money on the balance?**

API purchases need a balance of $20 or more. The account and Mini App have no such floor.

**What happens to the money if no code comes?**

It returns to your balance on its own when the window closes, or at once if you cancel after 2 minutes.

**Can I get a second code on the same number?**

Yes, while the window is open: setStatus=3, then getStatus again.

**Is there a test mode?**

No: every purchase is real. Start with a cheap pair and check your flow on one number.

**My key leaked. What now?**

Account → Balance → API → Issue a new one. The old key stops working at once.
