# RelySMS — SMS alma API'si — kendi yazılımınız için

Yazılımınız bir aktivasyon servisiyle handler_api.php üzerinden çalışıyorsa RelySMS ile de çalışır: sunucu adresini ve anahtarı değiştirin. Aynı işlemler, aynı yanıtlar; bizim fiyatlarımız ve iadelerimiz.

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

## Hızlı başlangıç

1. Hesabınıza girin ve Bakiye → API → Anahtar oluştur'u açın. Anahtar yalnızca bir kez gösterilir — kaydedin.
2. Bakiyenizi $20 veya üzerine yükleyin: API ile alım yalnızca bu bakiyeyle yapılır. Sıfır bakiyeyle anahtar tutmak sorun değil.
3. Yazılımınızda sunucu olarak https://relysms.pro/stubs/handler_api.php adresini ve önceki servisin anahtarı yerine kendi anahtarınızı girin.
4. Numara alın (getNumber), kodla birlikte STATUS_OK gelene kadar durumu 5–10 saniyede bir sorgulayın (getStatus).
5. Siparişi tamamlayın (setStatus=6). Kod gelmezse 2 dakika sonra iptal edin (setStatus=8) ya da sürenin dolmasını bekleyin: para kendiliğinden döner.

## Kurallar ve sınırlar

- API ile alım için bakiye en az $20 olmalıdır. Altında getNumber NO_BALANCE döndürür; diğer yöntemler çalışır.
- Numara 20 dakika sizindir. Kod gelmezse sipariş kendiliğinden kapanır ve para bakiyeye döner.
- Kod gelmediyse alımdan 2 dakika sonra iptal edebilirsiniz. Para hemen döner.
- İlk kod geldiğinde sipariş ödenmiş sayılır ve iade edilmez. Süre açık kaldıkça sonraki kodlar da gelebilir.
- Anahtar başına saniyede en fazla 20 istek ve 5 alım; aynı anda en fazla 50 açık sipariş.
- Fiyat alım anında sabitlenir. maxPrice, sınırınızın üstünde alım yapılmasını engeller.
- Hizmet koşulları geçerlidir: spam, dolandırıcılık ve bu amaçla toplu kayıt yasaktır.

## Yöntemler

Her istek tek bir adrese yapılan GET veya POST'tur; parametreler sorgu dizesinde ya da form gövdesindedir. Anahtar api_key'de, işlem action'da gönderilir.

### `getBalance`

Hesap bakiyesi (dolar).

Yalnızca api_key ve action.

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

Yanıt:

```
ACCESS_BALANCE:25.00
```

### `getNumber`

Bir ülkede bir servis için numara al. Hemen tahsil edilir; kod gelmezse iade edilir. Yanıt: ACCESS_NUMBER:sipariş_no:telefon.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `service` | evet | Servis kodu (getServicesList), örneğin tg. |
| `country` | evet | Ülke kodu (getCountries). |
| `maxPrice` | hayır | Dolar cinsinden azami fiyat. Daha pahalıysa — güncel fiyatla WRONG_MAX_PRICE, tahsilat yok. |

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

Yanıt:

```
ACCESS_NUMBER:1042:15550001111
```

Olası hatalar: `BAD_SERVICE`, `BAD_COUNTRY`, `NO_NUMBERS`, `NO_BALANCE`, `WRONG_MAX_PRICE:0.59`, `LIMIT_ACTIVATIONS`, `RATE_LIMIT`, `BANNED`

### `getNumberV2`

getNumber ile aynı, ancak fiyat ve süre bilgisini içeren JSON yanıtı döner.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `service` | evet | Servis kodu. |
| `country` | evet | Ülke kodu. |
| `maxPrice` | hayır | Dolar cinsinden azami fiyat. |

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

Yanıt:

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

Olası hatalar: `BAD_SERVICE`, `BAD_COUNTRY`, `NO_NUMBERS`, `NO_BALANCE`, `WRONG_MAX_PRICE:0.59`, `LIMIT_ACTIVATIONS`, `RATE_LIMIT`

### `getStatus`

Sipariş durumu: kod bekleniyor, kod geldi (kodla birlikte) veya iptal edildi.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `id` | evet | getNumber'dan gelen sipariş numarası. |

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

Yanıt:

```
STATUS_OK:48213
```

Olası hatalar: `NO_ACTIVATION`

### `setStatus`

Sipariş üzerinde işlem: hazır, yeni kod, tamamla, iptal (bkz. Durumlar).

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `id` | evet | Sipariş numarası. |
| `status` | evet | 1, 3, 6 veya 8. |

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

Yanıt:

```
ACCESS_ACTIVATION
```

Olası hatalar: `NO_ACTIVATION`, `BAD_STATUS`, `EARLY_CANCEL_DENIED`, `ERROR_SQL`

### `getActiveActivations`

Anahtarın tüm açık siparişleri: numara, durum, gelen kodlar ve metinler.

Yalnızca api_key ve action.

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

Yanıt:

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

Dolar cinsinden fiyatlarımız ve yaklaşık numara sayısı. Parametresiz — satıştaki her şey.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `service` | hayır | Yalnızca bu servis. |
| `country` | hayır | Yalnızca bu ülke. |

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

Yanıt:

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

Olası hatalar: `BAD_SERVICE`, `BAD_COUNTRY`

### `getCountries`

Protokol kodları ve Rusça, İngilizce adlarıyla ülke listesi.

Yalnızca api_key ve action.

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

Yanıt:

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

### `getServicesList`

Protokol kodlarıyla servis listesi.

Yalnızca api_key ve action.

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

Yanıt:

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

## Durumlar

setStatus'taki status değerleri:

- `status=1` → `ACCESS_READY` — Numara servise girildi, kod bekleniyor. Hiçbir şeyi değiştirmez; uyumluluk için duruyor.
- `status=3` → `ACCESS_RETRY_GET` — Aynı numaraya bir kod daha isteyin — ilk koddan sonra, sipariş açıkken. Ücretsiz. Yeni kod gelene kadar getStatus STATUS_WAIT_RETRY döner.
- `status=6` → `ACCESS_ACTIVATION` — Koddan sonra siparişi tamamla. Numaraya artık yeni SMS gelmez.
- `status=8` → `ACCESS_CANCEL` — Kodu gelmemiş siparişi iptal et (en erken 2 dakika sonra). Para bakiyeye döner.

getStatus yanıtları:

- `STATUS_WAIT_CODE` — Henüz kod yok — 5–10 saniye sonra tekrar sorun.
- `STATUS_OK:48213` — Kod geldi; iki noktadan sonra kodun kendisi (kod yoksa SMS metni) yer alır.
- `STATUS_WAIT_RETRY:48213` — Bir kod daha istendi, henüz gelmedi; iki noktadan sonra önceki kod var.
- `STATUS_CANCEL` — Kodsuz iptal edildi veya kapandı; para bakiyeye döndü.

## Hatalar

Hatalar, bu protokolde olağan olduğu gibi düz metin olarak gelir. RATE_LIMIT dışındakilerin hepsi 200 durum koduyla döner.

- `NO_KEY` — api_key gönderilmedi.
- `BAD_KEY` — Anahtar yanlış veya kapatılmış ya da hesap engellenmiş.
- `BAD_ACTION` — Bilinmeyen işlem.
- `BAD_SERVICE` — Böyle bir servis kodu yok.
- `BAD_COUNTRY` — Böyle bir ülke kodu yok.
- `NO_NUMBERS` — Bu eşleşme için şu an numara yok; tahsilat yapılmadı.
- `NO_BALANCE` — Bakiye $20'nin (API eşiği) veya numara fiyatının altında.
- `WRONG_MAX_PRICE` — Fiyat maxPrice'ı aşıyor; iki noktadan sonra güncel fiyat yer alır.
- `LIMIT_ACTIVATIONS` — Çok fazla açık sipariş — bir kısmını tamamlayın veya iptal edin.
- `RATE_LIMIT` — Çok sık; HTTP 429. Bir saniye sonra tekrar deneyin.
- `BANNED` — Hesap engellendi — destekle iletişime geçin.
- `NO_ACTIVATION` — Bu anahtar için bu numarada sipariş yok.
- `BAD_STATUS` — Bu işlem şu an sipariş için geçerli değil (örneğin kodsuz tamamlama).
- `EARLY_CANCEL_DENIED` — İptal, alımdan 2 dakika sonra mümkündür.
- `ERROR_SQL` — Bizim tarafta geçici bir hata — isteği bir dakika sonra yineleyin.

## Servis ve ülke kodları

Kodlar protokolün standart kodlarıdır: tg — Telegram, wa — WhatsApp vb.; 187 numaralı ülke — ABD. Tam listeleri getServicesList ve getCountries verir — kodları eski tablolardan değil, oradan alın: servisler ve ülkeler stokla birlikte değişir.

## REST API v1

handler_api.php ile aynı numaralar ve aynı kurallar, JSON olarak: servisler adıyla (telegram), ülkeler ISO koduyla (US), HTTP durumları ve programın dallanabileceği hata kodları. Yeni kod için bunu kullanın; handler_api.php kullanan yazılımı olduğu gibi bırakın.

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

### Anahtar

Anahtarı başlıkta gönderin: Authorization: Bearer <anahtar> (veya X-Api-Key: <anahtar>). Asla URL'de değil — URL'ler loglara düşer. Sınırlar, $20 eşiği ve katalog handler_api.php ile aynıdır.

### Çift alım olmadan yeniden deneme

POST /orders ve /orders/batch isteklerine kendi değerinizle (UUID, en fazla 255 karakter) Idempotency-Key başlığı ekleyin. Bağlantı koparsa ve isteği aynı anahtarla tekrarlarsanız ilk yanıtı alırsınız (Idempotent-Replayed: true başlığıyla) ve hiçbir şey iki kez alınmaz. Aynı anahtar farklı gövdeyle reddedilir; ilki hâlâ sürerken gelen tekrar idempotency_in_progress alır. Başarılı yanıtlar 24 saat saklanır; başarısız bir alım hiçbir şey tahsil etmedi ve aynı anahtarla tekrarlanabilir.

### Birden çok numara aynı anda

POST /orders/batch tek istekte bir servis ve ülke için en fazla 20 numara alır — saniyede 5'ten hızlı almanın yolu budur. Alımlar sırayla yapılır; ilk başarısız alım partiyi durdurur, yanıtta alınanlar ve stoppedBy içinde sebep yer alır. Hiçbir şey alınamadıysa yanıt o hatanın kendisidir.

### Sayfalar

GET /orders önce en yenileri döndürür. next null değilse sonraki sayfa için onu before olarak gönderin.

### Webhook'lar

GET /orders/{id} ile sürekli sorgulamak yerine PUT /webhook ile bir HTTPS adresi belirleyin; API ile alınan bir siparişte bir şey olduğunda oraya POST göndeririz. Gövdede siparişin gönderim anındaki hâli yer alır. 10 saniye içinde herhangi bir 2xx ile yanıt verin; bir sipariş birden fazla olay üretebilir ve aynı olay yeniden denemeden sonra iki kez gelebilir, bu yüzden teslimat id'sini esas alın.

- `order.code_received` — Kod geldi (her yeni kod bunu yeniden gönderir).
- `order.finished` — Sipariş tamamlandı — sizin tarafınızdan ya da koddan sonra süre dolunca.
- `order.cancelled` — Kodsuz iptal edildi; para bakiyeye döndü.
- `order.expired` — Süre kodsuz doldu; para bakiyeye döndü.
- `ping` — POST /webhook/test'ten gelen deneme; 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", … }], … }
}
```

Her istekte X-RelySMS-Signature: t=<unix zamanı>,v1=<imza> başlığı vardır. İmza, "t.gövde" dizesinin (ham gövde, bayt bayt) webhook sırrınızla HMAC-SHA256'sıdır, hex olarak. Gövdeye güvenmeden önce imzayı ve t'nin 5 dakikadan eski olmadığını kontrol edin.

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

Sunucunuz 2xx dönmezse 10 saniye, 1 dakika, 5 dakika, 30 dakika, 2 saat ve 6 saat sonra yeniden deneriz — toplam 7 deneme — sonra bırakırız. GET /webhook/deliveries neyin gittiğini ve neyin başarısız olduğunu gösterir.

### Uç noktalar

#### `GET /balance`

Dolar cinsinden bakiye ve API ile alım için yetip yetmediği.

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

Yanıt:

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

#### `GET /services`

Satıştaki tüm servisler: bizim id'miz, adı ve handler_api.php kodu.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `lang` (query) | hayır | Adların dili: en (varsayılan), ru, uk, zh, es, pt, id, vi, tr, ar, fr. |

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

Yanıt:

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

#### `GET /countries`

Satıştaki tüm ülkeler: ISO kodu, adı ve handler_api.php kodu.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `lang` (query) | hayır | Adların dili: en (varsayılan), ru, uk, zh, es, pt, id, vi, tr, ar, fr. |

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

Yanıt:

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

#### `GET /prices`

Dolar cinsinden fiyatlarımız ve yaklaşık numara sayısı. Parametresiz — satıştaki her şey.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `service` (query) | hayır | /services'ten servis id'si, örneğin telegram. |
| `country` (query) | hayır | /countries'ten ülke ISO kodu, örneğin US. |

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

Yanıt:

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

Olası hatalar: `unknown_service`, `unknown_country`

#### `POST /orders`

Numara al. Hemen tahsil edilir; kod gelmezse iade edilir.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `service` (body) | evet | /services'ten servis id'si, örneğin telegram. |
| `country` (body) | evet | /countries'ten ülke ISO kodu, örneğin US. |
| `maxPrice` (body) | hayır | Dolar cinsinden azami fiyat, sayı olarak. Üstündeyse — güncel fiyatla price_above_max, tahsilat yok. |

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

Yanıt:

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

Olası hatalar: `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`

Tek istekte bir servis ve ülke için en fazla 20 numara al.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `service` (body) | evet | /services'ten servis id'si, örneğin telegram. |
| `country` (body) | evet | /countries'ten ülke ISO kodu, örneğin US. |
| `quantity` (body) | evet | Kaç numara, 1 ile 20 arası. |
| `maxPrice` (body) | hayır | Dolar cinsinden azami fiyat, sayı olarak. Üstündeyse — güncel fiyatla price_above_max, tahsilat yok. |

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

Yanıt:

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

Olası hatalar: `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`

Siparişleriniz, en yeniler önce: varsayılan olarak açık olanlar ya da tümü.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `status` (query) | hayır | open (varsayılan) — bekleyen veya kodu gelmiş; all — tüm siparişler. |
| `limit` (query) | hayır | Sayfa başına sipariş, 1 ile 100 arası (varsayılan 50). |
| `before` (query) | hayır | Önceki sayfanın next değeri. |

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

Yanıt:

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

Olası hatalar: `bad_request`

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

Kodlarıyla tek bir sipariş. Durum waiting iken 5–10 saniyede bir sorgulayın.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `id` (path) | evet | Alım yanıtındaki sipariş id'si. |

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

Yanıt:

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

Olası hatalar: `not_found`

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

Kodu gelmiş siparişi tamamla. Numaraya artık SMS gelmez. Tekrarlamak zararsızdır.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `id` (path) | evet | Alım yanıtındaki sipariş id'si. |

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

Yanıt:

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

Olası hatalar: `not_found`, `invalid_state`

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

Aynı numaraya bir kod daha — ilk koddan sonra, sipariş açıkken. Ücretsiz. Yeni kod gelene kadar siparişte awaitingAnotherCode = true.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `id` (path) | evet | Alım yanıtındaki sipariş id'si. |

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

Yanıt:

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

Olası hatalar: `not_found`, `invalid_state`, `try_later`

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

Kodu gelmemiş siparişi alımdan 2 dakika sonra iptal et. Para hemen döner.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `id` (path) | evet | Alım yanıtındaki sipariş id'si. |

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

Yanıt:

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

Olası hatalar: `not_found`, `invalid_state`, `too_early_to_cancel`

#### `GET /webhook`

Webhook adresi ve imza sırrı ya da null.

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

Yanıt:

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

#### `PUT /webhook`

Adresi belirle veya değiştir. Sır bir kez oluşturulur ve korunur; rotateSecret yenisini üretir.

| Parametre | Zorunlu | Açıklama |
|---|---|---|
| `url` (body) | evet | Sunucunuzun HTTPS adresi. Özel ve yerel adresler reddedilir. |
| `rotateSecret` (body) | hayır | true — yeni bir imza sırrı üret. |

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

Yanıt:

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

Olası hatalar: `bad_request`, `invalid_webhook_url`

#### `DELETE /webhook`

Webhook'u kaldır. Henüz gönderilmemiş olaylar atılır.

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

Yanıt:

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

#### `POST /webhook/test`

Adresi ve imzayı denemek için ping olayı gönder.

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

Yanıt:

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

Olası hatalar: `not_found`

#### `GET /webhook/deliveries`

Son 20 teslimat: olay, durum, deneme sayısı, sunucunuzun son yanıtı.

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

Yanıt:

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

### Sipariş durumları

Siparişin status alanı:

- `waiting` — Numara sizin, henüz kod gelmedi.
- `received` — Kod geldi; codes içinde. Sipariş açıkken başka kodlar da gelebilir.
- `finished` — Siparişi tamamladınız (ya da koddan sonra süresi doldu).
- `cancelled` — Kodsuz iptal edildi; refunded true ve para bakiyede.
- `expired` — Süre kodsuz doldu; para bakiyeye döndü.

### Hatalar

Hata, bir HTTP durumu ve program için code, insanlar için message içeren bir JSON'dur. Bazılarında ek alanlar vardır (price, cancelableAt, apiMinimum).

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

- `missing_key` (HTTP 401) — Authorization veya X-Api-Key içinde anahtar yok.
- `invalid_key` (HTTP 401) — Anahtar yanlış, değiştirilmiş ya da hesap engellenmiş.
- `bad_request` (HTTP 400) — Gövde veya bir parametre hatalı; message hangisi olduğunu söyler.
- `unknown_service` (HTTP 400) — Böyle bir servis id'si yok.
- `unknown_country` (HTTP 400) — Böyle bir ülke kodu yok.
- `idempotency_key_invalid` (HTTP 400) — Idempotency-Key boş ya da 255 karakterden uzun.
- `invalid_webhook_url` (HTTP 400) — Webhook adresi HTTPS değil, geçerli bir URL değil ya da özel veya yerel bir sunucuyu gösteriyor.
- `insufficient_balance` (HTTP 402) — Bakiye fiyatın altında.
- `below_api_minimum` (HTTP 402) — Bakiye $20'nin altında — API yalnızca $20'den itibaren alır.
- `account_blocked` (HTTP 403) — Hesap engellendi — destekle iletişime geçin.
- `not_found` (HTTP 404) — Bu anahtar için bu id'de sipariş yok.
- `no_numbers` (HTTP 409) — Bu eşleşme için şu an numara yok; tahsilat yapılmadı.
- `price_above_max` (HTTP 409) — Fiyat maxPrice'ı aşıyor; güncel fiyat price alanında.
- `too_many_open_orders` (HTTP 409) — Çok fazla açık sipariş — bir kısmını tamamlayın veya iptal edin.
- `invalid_state` (HTTP 409) — Bu durumda yapılamaz (örneğin kodsuz tamamlama).
- `too_early_to_cancel` (HTTP 409) — İptal, alımdan 2 dakika sonra, cancelableAt anından itibaren mümkündür.
- `idempotency_in_progress` (HTTP 409) — Bu Idempotency-Key ile bir istek hâlâ sürüyor; biraz sonra tekrarlayın.
- `idempotency_key_reused` (HTTP 422) — Bu Idempotency-Key başka bir istek için kullanılmış.
- `rate_limited` (HTTP 429) — Saniyede 20'den fazla istek; ne zaman tekrarlanacağını Retry-After söyler.
- `buying_too_fast` (HTTP 429) — Saniyede 5'ten fazla alım; /orders/batch kullanın.
- `try_later` (HTTP 503) — Geçici olarak olmadı — bir dakika sonra tekrar deneyin.

### REST örnekleri

Numara al, kodu bekle, siparişi tamamla.

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

## Örnekler

Tam döngü: numara al, kodu bekle, siparişi tamamla.

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

## Sorular

**Yazılımım başka bir aktivasyon servisi kullanıyordu. Neyi değiştireyim?**

Sunucu adresini https://relysms.pro/stubs/handler_api.php olarak, anahtarı da hesabınızdaki anahtarla değiştirin. Servis ve ülke kodları aynı kalır.

**Bakiyem varken neden NO_BALANCE?**

API ile alım için bakiye $20 veya üzeri olmalı. Hesap ve Mini App'te böyle bir eşik yok.

**Kod gelmezse param ne olur?**

Süre dolduğunda kendiliğinden bakiyeye döner; 2 dakika sonra iptal ederseniz hemen döner.

**Aynı numaraya ikinci kod alabilir miyim?**

Evet, süre açıkken: setStatus=3, ardından yeniden getStatus.

**Test modu var mı?**

Hayır: tüm alımlar gerçek. Ucuz bir eşleşmeyle başlayın ve akışı tek numarada deneyin.

**Anahtarım sızdı. Ne yapmalıyım?**

Hesap → Bakiye → API → Yenisini çıkar. Eskisi hemen çalışmaz olur.
