# RelySMS — واجهة API لاستقبال الرسائل — لبرنامجك

إن كان برنامجك يعمل مع خدمة تفعيل عبر handler_api.php، فسيعمل مع RelySMS: غيّر عنوان الخادم والمفتاح. الإجراءات نفسها والردود نفسها، بأسعارنا وسياسة الاسترداد لدينا.

- العنوان: `https://relysms.pro/stubs/handler_api.php`
- Auth: `api_key` parameter
- Format: plain text tokens, JSON where noted

## بداية سريعة

1. ادخل حسابك وافتح «الرصيد» → «API» → «إنشاء مفتاح». يظهر المفتاح مرة واحدة — احفظه.
2. اشحن رصيدك إلى $20 أو أكثر: الشراء عبر API لا يتم إلا بهذا الرصيد. لا بأس بالاحتفاظ بالمفتاح ورصيدك صفر.
3. اضبط في برنامجك الخادم https://relysms.pro/stubs/handler_api.php ومفتاحك بدلًا من مفتاح الخدمة السابقة.
4. اشترِ رقمًا (getNumber) ثم استعلم عن الحالة كل 5–10 ثوانٍ (getStatus) حتى يصل STATUS_OK مع الرمز.
5. أكمِل الطلب (setStatus=6). إن لم يصل رمز، ألغِه بعد دقيقتين (setStatus=8) أو انتظر انتهاء المهلة: يعود المال تلقائيًا.

## القواعد والحدود

- الشراء عبر API يتطلب رصيدًا لا يقل عن $20. دون ذلك يرد getNumber بـ NO_BALANCE، وتعمل بقية الطرق.
- الرقم لك 20 دقيقة. إن لم يصل رمز يُغلق الطلب تلقائيًا ويعود المال إلى رصيدك.
- يمكنك الإلغاء بعد 2 دقيقة من الشراء إن لم يصل رمز. يعود المال فورًا.
- بعد وصول أول رمز يُعدّ الطلب مدفوعًا ولا يُسترد. ما دامت المهلة مفتوحة قد تصل رموز أخرى.
- حتى 20 طلبًا و5 عمليات شراء في الثانية لكل مفتاح؛ وحتى 50 طلبًا مفتوحًا في الوقت نفسه.
- يُثبَّت السعر لحظة الشراء. يمنعك maxPrice من الشراء فوق حدك.
- تسري شروط الخدمة: يُمنع السبام والاحتيال والتسجيل الجماعي لهذه الأغراض.

## الطرق

كل طلب هو GET أو POST إلى عنوان واحد، والمعاملات في سلسلة الاستعلام أو في جسم النموذج. يُرسل المفتاح في api_key والإجراء في action.

### `getBalance`

رصيد الحساب بالدولار.

api_key وaction فقط.

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

الرد:

```
ACCESS_BALANCE:25.00
```

### `getNumber`

شراء رقم لخدمة في دولة. يُخصم المبلغ فورًا ويُسترد إن لم يصل رمز. الرد: ACCESS_NUMBER:رقم_الطلب:الهاتف.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `service` | نعم | رمز الخدمة (getServicesList)، مثل tg. |
| `country` | نعم | رمز الدولة (getCountries). |
| `maxPrice` | لا | أقصى سعر بالدولار. إن كان أغلى — WRONG_MAX_PRICE مع السعر الحالي دون خصم. |

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

الرد:

```
ACCESS_NUMBER:1042:15550001111
```

أخطاء محتملة: `BAD_SERVICE`, `BAD_COUNTRY`, `NO_NUMBERS`, `NO_BALANCE`, `WRONG_MAX_PRICE:0.59`, `LIMIT_ACTIVATIONS`, `RATE_LIMIT`, `BANNED`

### `getNumberV2`

مثل getNumber، لكن الرد بصيغة JSON مع السعر وأوقات المهلة.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `service` | نعم | رمز الخدمة. |
| `country` | نعم | رمز الدولة. |
| `maxPrice` | لا | أقصى سعر بالدولار. |

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

الرد:

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

أخطاء محتملة: `BAD_SERVICE`, `BAD_COUNTRY`, `NO_NUMBERS`, `NO_BALANCE`, `WRONG_MAX_PRICE:0.59`, `LIMIT_ACTIVATIONS`, `RATE_LIMIT`

### `getStatus`

حالة الطلب: بانتظار الرمز، وصل الرمز (مع الرمز)، أو أُلغي.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `id` | نعم | رقم الطلب من getNumber. |

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

الرد:

```
STATUS_OK:48213
```

أخطاء محتملة: `NO_ACTIVATION`

### `setStatus`

إجراء على الطلب: جاهز، رمز آخر، إكمال، إلغاء (انظر «الحالات»).

| المعامل | إلزامي | الوصف |
|---|---|---|
| `id` | نعم | رقم الطلب. |
| `status` | نعم | 1 أو 3 أو 6 أو 8. |

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

الرد:

```
ACCESS_ACTIVATION
```

أخطاء محتملة: `NO_ACTIVATION`, `BAD_STATUS`, `EARLY_CANCEL_DENIED`, `ERROR_SQL`

### `getActiveActivations`

كل الطلبات المفتوحة للمفتاح: الرقم والحالة والرموز والنصوص الواردة.

api_key وaction فقط.

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

الرد:

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

أسعارنا بالدولار وعدد تقريبي للأرقام. دون معاملات — كل ما هو معروض.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `service` | لا | هذه الخدمة فقط. |
| `country` | لا | هذه الدولة فقط. |

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

الرد:

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

أخطاء محتملة: `BAD_SERVICE`, `BAD_COUNTRY`

### `getCountries`

قائمة الدول برموز البروتوكول وأسمائها بالروسية والإنجليزية.

api_key وaction فقط.

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

الرد:

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

### `getServicesList`

قائمة الخدمات برموز البروتوكول.

api_key وaction فقط.

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

الرد:

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

## الحالات

قيم status في setStatus:

- `status=1` → `ACCESS_READY` — أُدخل الرقم في الخدمة وننتظر الرمز. لا يغيّر شيئًا؛ باقٍ للتوافق.
- `status=3` → `ACCESS_RETRY_GET` — اطلب رمزًا آخر على الرقم نفسه — بعد الرمز الأول وما دام الطلب مفتوحًا. مجانًا. حتى يصل يجيب getStatus بـ STATUS_WAIT_RETRY.
- `status=6` → `ACCESS_ACTIVATION` — إكمال الطلب بعد الرمز. لن تصل رسائل جديدة إلى الرقم.
- `status=8` → `ACCESS_CANCEL` — إلغاء طلب بلا رمز (ليس قبل دقيقتين). يعود المال إلى رصيدك.

ردود getStatus:

- `STATUS_WAIT_CODE` — لم يصل رمز بعد — اسأل مجددًا بعد 5–10 ثوانٍ.
- `STATUS_OK:48213` — وصل الرمز؛ بعد النقطتين الرمز نفسه (أو نص الرسالة إن خلت من رمز).
- `STATUS_WAIT_RETRY:48213` — طُلب رمز آخر ولم يصل بعد؛ بعد النقطتين الرمز السابق.
- `STATUS_CANCEL` — أُلغي أو أُغلق بلا رمز؛ المال عاد إلى رصيدك.

## الأخطاء

تصل الأخطاء نصًا كما هو معتاد في هذا البروتوكول. كلها، عدا RATE_LIMIT، بحالة 200.

- `NO_KEY` — لم يُرسل api_key.
- `BAD_KEY` — المفتاح خاطئ أو موقوف، أو الحساب محظور.
- `BAD_ACTION` — إجراء غير معروف.
- `BAD_SERVICE` — لا يوجد رمز خدمة كهذا.
- `BAD_COUNTRY` — لا يوجد رمز دولة كهذا.
- `NO_NUMBERS` — لا أرقام لهذا الزوج حاليًا؛ لم يُخصم شيء.
- `NO_BALANCE` — الرصيد أقل من $20 (حد API) أو أقل من سعر الرقم.
- `WRONG_MAX_PRICE` — السعر أعلى من maxPrice؛ بعد النقطتين السعر الحالي.
- `LIMIT_ACTIVATIONS` — طلبات مفتوحة كثيرة — أكمِل بعضها أو ألغِه.
- `RATE_LIMIT` — طلبات متكررة جدًا؛ HTTP 429. أعد المحاولة بعد ثانية.
- `BANNED` — الحساب محظور — راسل الدعم.
- `NO_ACTIVATION` — لا يوجد طلب بهذا الرقم لهذا المفتاح.
- `BAD_STATUS` — هذا الإجراء غير متاح للطلب الآن (مثل الإكمال دون رمز).
- `EARLY_CANCEL_DENIED` — الإلغاء متاح بعد دقيقتين من الشراء.
- `ERROR_SQL` — خطأ مؤقت من جهتنا — أعد الطلب بعد دقيقة.

## رموز الخدمات والدول

الرموز هي الرموز القياسية للبروتوكول: tg — Telegram، وwa — WhatsApp، وهكذا؛ والدولة 187 — الولايات المتحدة. يعيد getServicesList وgetCountries القوائم كاملة — خذ الرموز منها لا من جداول قديمة: الخدمات والدول تتغير مع التوفر.

## REST API v1

الأرقام نفسها والقواعد نفسها كما في handler_api.php، لكن بصيغة JSON: الخدمات بالاسم (telegram)، والدول برمز ISO (US)، مع حالات HTTP ورموز أخطاء يسهل على البرنامج التفرع بها. استخدمها للشيفرة الجديدة، واترك handler_api.php للبرامج التي تستخدمه أصلًا.

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

### المفتاح

أرسل المفتاح في ترويسة: Authorization: Bearer <المفتاح> (أو X-Api-Key: <المفتاح>). لا تضعه في الرابط أبدًا — الروابط تُسجَّل في السجلات. الحدود وعتبة $20 والكتالوج كما في handler_api.php.

### إعادة المحاولة دون شراء مزدوج

أضف ترويسة Idempotency-Key بقيمة من عندك (UUID، حتى 255 حرفًا) إلى POST /orders و/orders/batch. إن انقطع الاتصال وأعدت الطلب بالمفتاح نفسه، يصلك الرد الأول (مع الترويسة Idempotent-Replayed: true) ولا يُشترى شيء مرتين. المفتاح نفسه مع محتوى مختلف يُرفض؛ والتكرار الذي يصل بينما الأول ما زال قيد التنفيذ يتلقى idempotency_in_progress. تُحفظ الردود الناجحة 24 ساعة؛ والشراء الفاشل لم يخصم شيئًا ويمكن إعادته بالمفتاح نفسه.

### عدة أرقام دفعة واحدة

يشتري POST /orders/batch حتى 20 رقمًا لخدمة ودولة واحدة في طلب واحد — وهذه طريقة الشراء بأسرع من 5 في الثانية. تتم المشتريات واحدًا تلو الآخر؛ أول شراء يفشل يوقف الدفعة، ويضم الرد ما اشتُري والسبب في stoppedBy. إن لم يُشترَ شيء فالرد هو ذلك الخطأ نفسه.

### الصفحات

يعيد GET /orders الأحدث أولًا. إن لم تكن قيمة next فارغة (null)، مرّرها في before لتحصل على الصفحة التالية.

### Webhooks

بدلًا من الاستعلام عن GET /orders/{id} باستمرار، حدّد عنوان HTTPS عبر PUT /webhook وسنرسل إليه طلب POST عندما يحدث شيء لطلب اشتُري عبر API. يحمل المحتوى الطلب كما هو لحظة الإرسال. أجب بأي رمز 2xx خلال 10 ثوانٍ؛ قد يولّد الطلب الواحد عدة أحداث، وقد يصل الحدث نفسه مرتين بعد إعادة المحاولة، فاعتمد على معرّف التسليم.

- `order.code_received` — وصل رمز (كل رمز جديد يرسل هذا الحدث مجددًا).
- `order.finished` — اكتمل الطلب — بواسطتك أو عند انتهاء وقته بعد وصول رمز.
- `order.cancelled` — أُلغي بلا رمز؛ عاد المال إلى الرصيد.
- `order.expired` — انتهى الوقت بلا رمز؛ عاد المال إلى الرصيد.
- `ping` — اختبار من 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", … }], … }
}
```

يحمل كل طلب الترويسة X-RelySMS-Signature: t=<وقت unix>,v1=<التوقيع>. التوقيع هو HMAC-SHA256 للسلسلة "t.body" (المحتوى الخام بايتًا بايتًا) بسرّ الـ webhook، بصيغة hex. تحقق منه ومن أن t لا يتجاوز 5 دقائق قبل الوثوق بالمحتوى.

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

إن لم يُجب خادمك بـ 2xx نعيد المحاولة بعد 10 ثوانٍ ودقيقة و5 دقائق و30 دقيقة وساعتين و6 ساعات — 7 محاولات في المجموع — ثم نتوقف. يعرض GET /webhook/deliveries ما أُرسل وما فشل.

### نقاط الوصول

#### `GET /balance`

الرصيد بالدولار، وهل يكفي للشراء عبر API.

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

الرد:

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

#### `GET /services`

كل الخدمات المعروضة: معرّفنا والاسم ورمز handler_api.php.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `lang` (query) | لا | لغة الأسماء: en (افتراضي)، ru، uk، zh، es، pt، id، vi، tr، ar، fr. |

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

الرد:

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

#### `GET /countries`

كل الدول المعروضة: رمز ISO والاسم ورمز handler_api.php.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `lang` (query) | لا | لغة الأسماء: en (افتراضي)، ru، uk، zh، es، pt، id، vi، tr، ar، fr. |

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

الرد:

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

#### `GET /prices`

أسعارنا بالدولار وعدد تقريبي للأرقام. دون معاملات — كل ما هو معروض.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `service` (query) | لا | معرّف الخدمة من /services، مثل telegram. |
| `country` (query) | لا | رمز ISO للدولة من /countries، مثل US. |

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

الرد:

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

أخطاء محتملة: `unknown_service`, `unknown_country`

#### `POST /orders`

شراء رقم. يُخصم المبلغ فورًا ويُسترد إن لم يصل رمز.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `service` (body) | نعم | معرّف الخدمة من /services، مثل telegram. |
| `country` (body) | نعم | رمز ISO للدولة من /countries، مثل US. |
| `maxPrice` (body) | لا | أقصى سعر بالدولار، رقمًا. إن تجاوزه — price_above_max مع السعر الحالي دون خصم. |

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

الرد:

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

أخطاء محتملة: `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`

شراء حتى 20 رقمًا لخدمة ودولة واحدة في طلب واحد.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `service` (body) | نعم | معرّف الخدمة من /services، مثل telegram. |
| `country` (body) | نعم | رمز ISO للدولة من /countries، مثل US. |
| `quantity` (body) | نعم | عدد الأرقام، من 1 إلى 20. |
| `maxPrice` (body) | لا | أقصى سعر بالدولار، رقمًا. إن تجاوزه — price_above_max مع السعر الحالي دون خصم. |

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

الرد:

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

أخطاء محتملة: `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`

طلباتك، الأحدث أولًا: المفتوحة افتراضيًا، أو الكل.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `status` (query) | لا | open (افتراضي) — بانتظار الرمز أو وصلها رمز؛ all — كل الطلبات. |
| `limit` (query) | لا | عدد الطلبات في الصفحة، من 1 إلى 100 (افتراضيًا 50). |
| `before` (query) | لا | قيمة next من الصفحة السابقة. |

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

الرد:

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

أخطاء محتملة: `bad_request`

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

طلب واحد مع رموزه. استعلم كل 5–10 ثوانٍ ما دامت الحالة waiting.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `id` (path) | نعم | معرّف الطلب من رد الشراء. |

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

الرد:

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

أخطاء محتملة: `not_found`

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

إكمال طلب وصله رمز. لن يستقبل الرقم رسائل أخرى. التكرار لا يضر.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `id` (path) | نعم | معرّف الطلب من رد الشراء. |

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

الرد:

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

أخطاء محتملة: `not_found`, `invalid_state`

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

رمز آخر على الرقم نفسه — بعد الرمز الأول وما دام الطلب مفتوحًا. مجانًا. حتى يصل يكون في الطلب awaitingAnotherCode = true.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `id` (path) | نعم | معرّف الطلب من رد الشراء. |

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

الرد:

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

أخطاء محتملة: `not_found`, `invalid_state`, `try_later`

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

إلغاء طلب بلا رمز، بعد دقيقتين من الشراء. يعود المال فورًا.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `id` (path) | نعم | معرّف الطلب من رد الشراء. |

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

الرد:

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

أخطاء محتملة: `not_found`, `invalid_state`, `too_early_to_cancel`

#### `GET /webhook`

عنوان الـ webhook وسرّ التوقيع، أو null.

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

الرد:

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

#### `PUT /webhook`

تحديد العنوان أو تغييره. يُنشأ السر مرة واحدة ويُحفظ؛ ويُنشئ rotateSecret سرًا جديدًا.

| المعامل | إلزامي | الوصف |
|---|---|---|
| `url` (body) | نعم | عنوان HTTPS لخادمك. تُرفض العناوين الخاصة والمحلية. |
| `rotateSecret` (body) | لا | true — إنشاء سرّ توقيع جديد. |

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

الرد:

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

أخطاء محتملة: `bad_request`, `invalid_webhook_url`

#### `DELETE /webhook`

حذف الـ webhook. تُهمل الأحداث التي لم تُرسل بعد.

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

الرد:

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

#### `POST /webhook/test`

إرسال حدث ping للتحقق من العنوان والتوقيع.

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

الرد:

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

أخطاء محتملة: `not_found`

#### `GET /webhook/deliveries`

آخر 20 تسليمًا: الحدث والحالة والمحاولات وآخر رد من خادمك.

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

الرد:

```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 في الطلب:

- `waiting` — الرقم لك ولم يصل رمز بعد.
- `received` — وصل رمز، وهو في codes. قد تصل رموز أخرى ما دام الطلب مفتوحًا.
- `finished` — أكملت الطلب (أو انتهى وقته بعد وصول رمز).
- `cancelled` — أُلغي بلا رمز؛ refunded قيمته true والمال في رصيدك.
- `expired` — انتهى الوقت بلا رمز؛ عاد المال إلى رصيدك.

### الأخطاء

الخطأ حالة HTTP مع JSON فيه code ليتفرع به البرنامج وmessage للبشر. بعضها يحمل حقولًا إضافية (price، cancelableAt، apiMinimum).

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

- `missing_key` (HTTP 401) — لا يوجد مفتاح في Authorization أو X-Api-Key.
- `invalid_key` (HTTP 401) — المفتاح خاطئ أو استُبدل، أو الحساب محظور.
- `bad_request` (HTTP 400) — خطأ في المحتوى أو أحد المعاملات؛ يوضح message موضعه.
- `unknown_service` (HTTP 400) — لا يوجد معرّف خدمة كهذا.
- `unknown_country` (HTTP 400) — لا يوجد رمز دولة كهذا.
- `idempotency_key_invalid` (HTTP 400) — Idempotency-Key فارغ أو أطول من 255 حرفًا.
- `invalid_webhook_url` (HTTP 400) — عنوان الـ webhook ليس HTTPS أو ليس رابطًا أو يشير إلى خادم خاص أو محلي.
- `insufficient_balance` (HTTP 402) — الرصيد أقل من السعر.
- `below_api_minimum` (HTTP 402) — الرصيد أقل من $20 — لا تشتري API إلا من $20.
- `account_blocked` (HTTP 403) — الحساب محظور — راسل الدعم.
- `not_found` (HTTP 404) — لا يوجد طلب بهذا المعرّف لهذا المفتاح.
- `no_numbers` (HTTP 409) — لا أرقام لهذا الزوج حاليًا؛ لم يُخصم شيء.
- `price_above_max` (HTTP 409) — السعر أعلى من maxPrice؛ السعر الحالي في price.
- `too_many_open_orders` (HTTP 409) — طلبات مفتوحة كثيرة — أكمِل بعضها أو ألغِه.
- `invalid_state` (HTTP 409) — غير ممكن في هذه الحالة (مثل الإكمال دون رمز).
- `too_early_to_cancel` (HTTP 409) — الإلغاء متاح من cancelableAt، بعد دقيقتين من الشراء.
- `idempotency_in_progress` (HTTP 409) — طلب بهذا Idempotency-Key ما زال قيد التنفيذ؛ أعد المحاولة بعد لحظة.
- `idempotency_key_reused` (HTTP 422) — استُخدم هذا Idempotency-Key لطلب آخر.
- `rate_limited` (HTTP 429) — أكثر من 20 طلبًا في الثانية؛ يخبرك Retry-After متى تعيد المحاولة.
- `buying_too_fast` (HTTP 429) — أكثر من 5 عمليات شراء في الثانية؛ استخدم /orders/batch.
- `try_later` (HTTP 503) — تعذّر مؤقتًا — حاول بعد دقيقة.

### أمثلة REST

شراء رقم، انتظار الرمز، إكمال الطلب.

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

## أمثلة

الدورة كاملة: شراء رقم، انتظار الرمز، إكمال الطلب.

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

## أسئلة

**برنامجي كان يعمل مع خدمة تفعيل أخرى. ماذا أغيّر؟**

عنوان الخادم إلى https://relysms.pro/stubs/handler_api.php، والمفتاح إلى مفتاحك من الحساب. رموز الخدمات والدول تبقى كما هي.

**لماذا NO_BALANCE ولديّ رصيد؟**

الشراء عبر API يتطلب رصيدًا من $20 فأكثر. لا يوجد هذا الحد في الحساب ولا في Mini App.

**ماذا يحدث للمال إن لم يصل الرمز؟**

يعود إلى رصيدك تلقائيًا عند انتهاء المهلة، أو فورًا إن ألغيت بعد دقيقتين.

**هل يمكنني تلقي رمز ثانٍ على الرقم نفسه؟**

نعم ما دامت المهلة مفتوحة: setStatus=3 ثم getStatus مجددًا.

**هل يوجد وضع تجريبي؟**

لا: كل عمليات الشراء حقيقية. ابدأ بزوج رخيص وجرّب سير العمل على رقم واحد.

**تسرّب مفتاحي. ماذا أفعل؟**

الحساب ← «الرصيد» ← «API» ← «إصدار مفتاح جديد». يتوقف القديم فورًا.
