التوثيق — الإصدار v1
المصادقة
كل نداء يحمل أربع ترويسات (توقيع الإصدار ٢). التوقيع على خمسة أسطر
يفصل بينها محرف سطرٍ جديد واحد (LF، \n):
"v2\n{timestamp}\n{METHOD}\n{path?query}\n{raw_body}"
بخوارزمية HMAC-SHA256 وسرّك: السطر الأوّل v2 حرفيًّا، والطريقة بحروف كبيرة،
والمسار كما يُرسَل حرفيًّا (بترميز %XX) ومعه
? والاستعلام إن وُجد (بلا ?
إن لم يوجد)، والجسم أخيرًا كما يُرسَل (فارغ لطلب GET، بلا سطرٍ بعده). ونافذة القبول ٣٠٠ ثانية —
فاضبط ساعة خادمك، فهي أكثر أسباب فشل التوقيع.
X-Syriana-Key: sy_live_xxxxxxxxxxxx
X-Syriana-Timestamp: 1756000000
X-Syriana-Signature-Version: 2
X-Syriana-Signature: hex(hmac_sha256(secret,
"v2\n1756000000\nGET\n/api/v1/products?page=2\n" + body))
- التوقيع يُستعمل مرّة واحدة: إعادة إرسال الطلب الموقَّع
نفسه تُرفض بـ
replayed_request— حتى GET. طلبان متطابقان في الثانية نفسها يتطابق توقيعهما، فوقّع كلًّا بختمه أو غيّر الاستعلام. وطلب الكتابة بـIdempotency-Keyلا يُستثنى: عند إعادة المحاولة وقّعه من جديد بختمٍ جديد وأبقِ المفتاح نفسه، فتأتيك الإجابة المحفوظة ولا يُنفَّذ مرّتين. - توقيع طلبٍ لا يصلح لمسارٍ آخر ولا لاستعلامٍ آخر ولا لطريقةٍ أخرى.
- الإصدار ١ (
"{timestamp}.{raw_body}"بلا ترويسة الإصدار) ما زال مقبولًا لكنّه متقادم: لا يغطّي المسار ولا الاستعلام، وقد يُفرض الإصدار ٢ على تطبيقك فيُرفض الأوّل بـsignature_v2_required. انتقل إليه الآن.
KEY="sy_test_xxx"; SECRET="xxx"
METHOD="GET"; TARGET="/api/v1/products?page=1" # path + ?query exactly as sent
BODY=''
TS=$(date +%s)
SIG=$(printf 'v2\n%s\n%s\n%s\n%s' "$TS" "$METHOD" "$TARGET" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)
curl -s -X "$METHOD" "https://syriana.store$TARGET" \
-H "X-Syriana-Key: $KEY" \
-H "X-Syriana-Timestamp: $TS" \
-H "X-Syriana-Signature-Version: 2" \
-H "X-Syriana-Signature: $SIG"
import hashlib, hmac, json, time, requests
from urllib.parse import quote, urlencode
KEY, SECRET = "sy_test_xxx", "xxx"
BASE = "https://syriana.store"
def call(method, path, payload=None, query=None):
# v2 signs METHOD, the path + query exactly as sent, and the body. Build
# each string once and send those same strings — json.dumps twice can
# differ. Percent-encode path segments yourself: quote(sku, safe="").
body = json.dumps(payload, ensure_ascii=False) if payload else ""
target = path + ("?" + urlencode(query) if query else "")
ts = str(int(time.time()))
to_sign = "\n".join(["v2", ts, method.upper(), target, body])
sig = hmac.new(SECRET.encode(), to_sign.encode(), hashlib.sha256).hexdigest()
return requests.request(method, BASE + target, data=body.encode(), headers={
"X-Syriana-Key": KEY,
"X-Syriana-Timestamp": ts,
"X-Syriana-Signature-Version": "2",
"X-Syriana-Signature": sig,
"Content-Type": "application/json",
})
print(call("GET", "/api/v1/ping").json())
print(call("GET", "/api/v1/products", query={"page": 1}).json())
<?php
$key = "sy_test_xxx"; $secret = "xxx";
$base = "https://syriana.store";
function sy_call($method, $path, $payload = null, $query = []) {
global $key, $secret, $base;
// JSON_UNESCAPED_UNICODE matters: Arabic escaped differently here than
// when you signed it would produce a valid signature over other bytes.
$body = $payload ? json_encode($payload, JSON_UNESCAPED_UNICODE) : "";
$target = $path . ($query ? "?" . http_build_query($query) : "");
$ts = (string) time();
$toSign = implode("\n", ["v2", $ts, strtoupper($method), $target, $body]);
$sig = hash_hmac("sha256", $toSign, $secret);
$ch = curl_init($base . $target);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Syriana-Key: $key",
"X-Syriana-Timestamp: $ts",
"X-Syriana-Signature-Version: 2",
"X-Syriana-Signature: $sig",
"Content-Type: application/json",
],
]);
$out = curl_exec($ch); curl_close($ch);
return json_decode($out, true);
}
print_r(sy_call("GET", "/api/v1/ping"));
print_r(sy_call("GET", "/api/v1/products", null, ["page" => 1]));
const crypto = require("crypto");
const KEY = "sy_test_xxx", SECRET = "xxx";
const BASE = "https://syriana.store";
async function call(method, path, payload, query) {
const body = payload ? JSON.stringify(payload) : "";
const qs = query ? new URLSearchParams(query).toString() : "";
const target = path + (qs ? "?" + qs : ""); // exactly what is sent
const ts = Math.floor(Date.now() / 1000).toString();
const sig = crypto.createHmac("sha256", SECRET)
.update(["v2", ts, method.toUpperCase(), target, body].join("\n"))
.digest("hex");
const res = await fetch(BASE + target, {
method,
body: body || undefined,
headers: {
"X-Syriana-Key": KEY,
"X-Syriana-Timestamp": ts,
"X-Syriana-Signature-Version": "2",
"X-Syriana-Signature": sig,
"Content-Type": "application/json",
},
});
return res.json();
}
call("GET", "/api/v1/ping").then(console.log);
call("GET", "/api/v1/products", null, { page: 1 }).then(console.log);
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"strings"
"time"
)
const key, secret, base = "sy_test_xxx", "xxx", "https://syriana.store"
// target = path + "?" + url.Values{...}.Encode() — exactly what is sent.
func call(method, target, body string) (string, error) {
ts := fmt.Sprintf("%d", time.Now().Unix())
mac := hmac.New(sha256.New, []byte(secret))
toSign := strings.Join([]string{"v2", ts, strings.ToUpper(method), target, body}, "\n")
mac.Write([]byte(toSign))
sig := hex.EncodeToString(mac.Sum(nil))
req, _ := http.NewRequest(method, base+target, strings.NewReader(body))
req.Header.Set("X-Syriana-Key", key)
req.Header.Set("X-Syriana-Timestamp", ts)
req.Header.Set("X-Syriana-Signature-Version", "2")
req.Header.Set("X-Syriana-Signature", sig)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
return string(out), nil
}
func main() {
fmt.Println(call("GET", "/api/v1/ping", ""))
fmt.Println(call("GET", "/api/v1/products?page=1", ""))
}
شكل الردّ
ثابت لا يتغيّر: النجاح دائماً data وmeta،
والفشل دائماً error. الحقول تُضاف ولا تُحذف، فما تبنيه
اليوم يبقى يعمل.
{"data": {...}, "meta": {"version": "v1", "page": 1, "total": 201}}
{"error": {"code": "missing_scope", "message": "..."}}
كيف تقبض على موقعك أنت
نحن لا نستضيف صفحة دفع داخل موقعك، ولا نمرّر أموالك عبر حسابنا. الطريقة واحدة وواضحة: تطلب رابطاً وتحوّل عميلك إليه، فيدفع إلى حسابك أنت لدى مزوّدك، ثم نُخبر خادمك.
- اربط مزوّدك مرّة واحدة في مدفوعاتي — بدون حساب مزوّد فعّال يُرفض إنشاء أي رابط، لأننا لا نُحصّل نيابةً عنك.
- أنشئ تطبيقاً في تطبيقاتي،
وامنحه
payments:writeوpayments:read، وحدّد وسائل الدفع المسموحة له إن أردت تضييقها. - ناد
POST /api/v1/payment_linksعند كل طلب، وحوّل العميل إلىurlفي الردّ. - سلّم عند وصول
payment.paidإلى رابط الإشعارات، لا عند عودة العميل إلى موقعك — العميل قد يغلق الصفحة، والإشعار يصل على أي حال.
BODY='{"title":"اشتراك سنوي","amount":250,"currency":"SAR","mode":"once","methods":["stripe"],"reference":"ORDER-1042"}'
TS=$(date +%s)
SIG=$(printf 'v2\n%s\n%s\n%s\n%s' "$TS" POST /api/v1/payment_links "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)
curl -s -X POST "https://syriana.store/api/v1/payment_links" \
-H "X-Syriana-Key: $KEY" -H "X-Syriana-Timestamp: $TS" \
-H "X-Syriana-Signature-Version: 2" -H "X-Syriana-Signature: $SIG" \
-H "Content-Type: application/json" -d "$BODY"
link = call("POST", "/api/v1/payment_links", {
"title": "اشتراك سنوي",
"amount": 250,
"currency": "SAR",
"mode": "once", # or "reusable"
"methods": ["stripe"], # [] = every method you enabled
"reference": "ORDER-1042", # yours; comes back on the webhook
}).json()["data"]
redirect_to = link["url"] # send your customer here
$link = sy_call("POST", "/api/v1/payment_links", [
"title" => "اشتراك سنوي",
"amount" => 250,
"currency" => "SAR",
"mode" => "once",
"methods" => ["stripe"],
"reference" => "ORDER-1042",
])["data"];
header("Location: " . $link["url"]);
const { data: link } = await call("POST", "/api/v1/payment_links", {
title: "اشتراك سنوي",
amount: 250,
currency: "SAR",
mode: "once",
methods: ["stripe"],
reference: "ORDER-1042",
});
res.redirect(link.url);
body := `{"title":"اشتراك سنوي","amount":250,"currency":"SAR",
"mode":"once","methods":["stripe"],"reference":"ORDER-1042"}`
out, err := call("POST", "/api/v1/payment_links", body)
// out.data.url هو ما تحوّل إليه عميلك
أيّ الطرق تظهر لعميلك
الظاهر هو تقاطع ثلاث قوائم، والفارغ منها يعني «لا
تضييق»: ما فعّلته في مدفوعاتي ∩ ما سمحت به لهذا التطبيق ∩
ما طلبته في methods. فإذا لم يظهر زرّ توقّعته،
فالسبب إحدى الثلاث — ونقطة
GET /api/v1/payment_links/{link_id}
تُعيد methods فعلياً، فاقرأها بدل التخمين.
التحقّق من الإشعار
كل إشعار موقّع بسرّ الويب‑هوك الخاص بتطبيقك على
"{timestamp}.{raw_body}" (الإشعار لا يحمل
مسارًا يخصّك، فلا إصدار ثانيًا له). تحقّق من التوقيع قبل أن تُسلّم شيئاً،
ولا تثق بجسم الطلب وحده — وتذكّر أن الإشعار قد يصل أكثر من
مرّة، فاجعل تسليمك يحتمل التكرار.
# ليست عملية سطر أوامر: التحقّق يجري في خادمك.
# القاعدة واحدة في كل لغة —
# expected = hmac_sha256(webhook_secret, timestamp + "." + raw_body)
# ثم قارنه بترويسة X-Syriana-Signature بمقارنة ثابتة الزمن.
import hmac, hashlib
def verify(raw_body: bytes, timestamp: str, signature: str, secret: str):
expected = hmac.new(secret.encode(),
timestamp.encode() + b"." + raw_body,
hashlib.sha256).hexdigest()
# compare_digest, never ==: a plain comparison returns faster on an early
# mismatch, and that timing alone leaks the signature byte by byte.
return hmac.compare_digest(expected, signature)
# Read the RAW body. A framework that parses and re-serialises JSON for you
# will hand you different bytes than the ones we signed.
<?php
function sy_verify($rawBody, $timestamp, $signature, $secret) {
$expected = hash_hmac("sha256", $timestamp . "." . $rawBody, $secret);
// hash_equals, not ===, for the same timing reason.
return hash_equals($expected, $signature);
}
// $rawBody = file_get_contents("php://input"); // NOT $_POST
const crypto = require("crypto");
function verify(rawBody, timestamp, signature, secret) {
const expected = crypto.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected),
Buffer.from(signature));
}
// express: app.use(express.raw({type: "application/json"}))
// so rawBody is the exact bytes we signed.
func verify(rawBody []byte, timestamp, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(timestamp + "."))
mac.Write(rawBody)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
جرّب قبل أن تبيع
مفتاحك التجريبي يُنشئ روابط تجريبية فقط — البيئة يقرّرها المفتاح لا جسم الطلب، فلا يمكن لخطأ في الإعداد أن يقبض مالاً حقيقياً وأنت تختبر.
فاتورة كاملة على صفحة الدفع
أرسل مع POST /api/v1/payment_links تفاصيل الفاتورة،
فيراها عميلك قبل أن يدفع: بياناته، وبياناتك، والبنود، والضريبة، والاستحقاق.
كلّها اختياريّة — وما لا ترسله لا يظهر.
| الحقل | ما هو |
|---|---|
customer | {name, email, phone} — عميلك. |
seller | {name, email, phone, address, vat, cr} — أنت (الرقم الضريبيّ والسجلّ التجاريّ اختياريّان). |
lines | [{label, qty, total}] حتى ١٠٠ بند. |
subtotal + tax | مجموعهما يجب أن يساوي amount — وإلّا يُرفض الطلب، لأنّ فاتورةً لا تُجمع صحيحاً تُحرجك أمام عميلك. |
invoiceNo · dueDate | رقم فاتورتك وتاريخ استحقاقها (YYYY-MM-DD). |
successUrl | https فقط: يُعاد إليه عميلك بعد نجاح الدفع. |
الصفحة بالعربية افتراضاً؛ أضف ?lang=en إلى الرابط لعميلٍ لا يقرأ العربية.
{
"title": "اشتراك سنوي",
"amount": 115, "currency": "USD", "reference": "ORD-1042",
"customer": {"name": "سامي", "email": "sami@example.com"},
"seller": {"name": "متجري", "vat": "300000000000003"},
"lines": [{"label": "اشتراك سنوي", "qty": 1, "total": 100}],
"subtotal": 100, "tax": 15,
"invoiceNo": "INV-2026-0042", "dueDate": "2026-10-01",
"successUrl": "https://mystore.com/thanks"
}
بعد أن يدفع عميلك
- يرى صفحة شكرٍ واضحة على صفحة الدفع نفسها، والفاتورة تتحوّل إلى «مدفوعة».
- إن أرسلت
successUrlنعيده إليه بعد ٥ ثوانٍ، ومعه?pay_link=…&reference=…&invoice=…لتعرف أيّ طلب. - يصل خادمك
payment.paid— وهذا وحده دليل الدفع. العودة إلىsuccessUrlقد لا تحدث (يغلق العميل الصفحة)، وقد يزوّرها أيّ أحد بكتابة الرابط بيده. سلّم عند الإشعار، لا عند العودة.
الدفع بالمحافظ (شام كاش) يُتابَع آليّاً كل ٥ دقائق: ما يُدفع يُقيَّد ويصلك إشعاره حتى لو أغلق العميل الصفحة، وما تنتهي مهلته يُغلق وحده — لا شيء يبقى «بانتظار الدفع» إلى الأبد.
صفحة الدفع باسمك
- شعارك ولونك وسطر أسفل الصفحة: من تبويب «صفحة الدفع» في صفحة تطبيقك،
أو بنداء
POST /api/v1/pay_page(صلاحيّةbranding:write). - اسمك بدل اسمنا (العلامة البيضاء): صفحتك تقول «مدفوعات آمنة — اسم متجرك» بدل «عبر Syriana». تأتي مع باقتَي Store وEnterprise، وتعمل ما دامت الباقة فعّالة.
- نطاقك مثل
pay.yourstore.com: أضفه، ثمّ ضع عند مزوّد نطاقك سجلّCNAMEمن الاسم إلىconnect.sy-ft.net. على Cloudflare اجعله DNS only (السحابة رماديّة). نفحصه كل دقيقة ونُصدر شهادة HTTPS، ولا نقول «يعمل ✅» إلّا بعد أن نفتح صفحةً عليه فعلاً. بعدها كل روابطك تُفتح عليه، وعودة العميل من المزوّد تعود إليه.
POST /api/v1/pay_page {"colour": "#0f766e", "footer": "خدمة العملاء 0999 000 000"}
POST /api/v1/pay_page/domains {"host": "pay.mystore.com"}
GET /api/v1/pay_page → domains[0].state: waiting → active
الاسترجاع — يدويّ بقرار
لا نُخرج مالاً من حسابك بنداءٍ آليّ. إذا أردت إرجاع مبلغ لعميلك: أرجِعه أنت من حسابك لدى المزوّد (لوحة Stripe أو فواتيرك أو غيرها، أو بالتحويل في شام كاش)، ثمّ قيّده عندنا — من «مدفوعاتي» بزرّ «سجّل استرجاعاً»، أو من نظامك:
POST /api/v1/payments/{reference}/refund
{"alreadyDone": true, "amount": 20, "reason": "مقاس خاطئ"}
صلاحيّة payments:refund. بدون alreadyDone الردّ manual_only.
كاملاً أو جزءاً؛ وIdempotency-Key يمنع تقييده مرّتين.
يصلك payment.refunded عند الكامل وpayment.partially_refunded عند الجزئيّ.
رسومنا وأرقامك
المال يصل إلى حسابك أنت لدى مزوّدك كاملاً؛ رسومنا تُحسب على كل دفعة ناجحة (fee في
الإشعار) وتُجمع شهريّاً: تُخصم من رصيد محفظتك إن كفى، وإلّا تصلك بها فاتورة. لا رسوم على
ما استُرجع. GET /api/v1/payments/summary يعطيك في نداء واحد: كم بعت، كم أرجعت،
كم رسوماً مستحقّة وكم دُفعت، وصافيك — لكل عملة على حدة.
نقاط النهاية
لا نقطة تطابق بحثك.
التحقّق
/api/v1/ping
توقيع فقط
{"ok": true, "time": "...", "sandbox": true}
/api/v1/me
توقيع فقط
{"app": "...", "scopes": [...], "wallet": {...}}
الكتالوج
/api/v1/products
catalog:read
قائمة منتجات، كلٌّ برمز sku ثابت وصورة وفئاته وسعرك أنت. مع currency يُضاف كائن display — والسعر الأصلي يبقى هو المعتمد للخصم.
/api/v1/categories
catalog:read
[{"id": 5, "name": "...", "parent_id": 2, "product_count": 38}]
/api/v1/products/{sku}
catalog:read
كائن منتج مفصّل.
/api/v1/products/changes
catalog:read
قائمة منتجات مفصّلة مرتّبة بتاريخ التعديل.
المحفظة
/api/v1/wallet
wallet:read
{"balance": .., "spendable": .., "mode": "prepaid"}
/api/v1/wallet/entries
wallet:read
قائمة حركات بالرصيد بعد كلٍّ منها.
/api/v1/wallet/topup-methods
wallet:read
قائمة وسائل بحدودها.
/api/v1/wallet/topup
wallet:write
201 مع pay_url — افتحه في المتصفّح لإتمام الدفع.
/api/v1/wallet/topups
wallet:read
قائمة شحنات.
الطلبات
/api/v1/customers/record
orders:write
201 عند أول تسجيل، و200 مع عدّاد الطلبات بعدها.
/api/v1/notify/licence
orders:write
202 مع «channel» (whatsapp أو email) وعدد الأكواد. وعند السقوط إلى البريد يحمل «whatsapp_skipped» السبب.
/api/v1/orders
orders:write
201 مع order_id والتراخيص، أو 402 لعدم كفاية الرصيد، أو 409 لتضارب مفتاح التكرار.
/api/v1/orders
orders:read
قائمة طلبات مختصرة.
/api/v1/orders/{order_id}
orders:read
الطلب بأصنافه وتراخيصه.
/api/v1/orders/{order_id}/licenses
licenses:read
قائمة تراخيص بحالة كلٍّ منها.
التجّار
/api/v1/merchants/provision
merchants:provision
201 مع key_id والسرّ (مرّة واحدة) و verify_url، أو 200 بلا سرّ إن كان الحساب موجوداً.
القنوات
/api/v1/channels/report
merchants:provision
{"recorded": true}
روابط الدفع
/api/v1/payment_links
payments:write
{"id": 12, "url": "https://…/pay/xyz", "state": "active", "methods": ["stripe", "paytabs"]}
/api/v1/payment_links
payments:read
قائمة روابط بنفس شكل الإنشاء، مع paid_count.
/api/v1/payment_links/{link_id}
payments:read
{"id": 12, …, "payments": [{"state": "paid", …}]}
/api/v1/payment_links/{link_id}/disable
payments:write
الرابط بحالته الجديدة disabled.
المدفوعات والاسترجاع
/api/v1/payments
payments:read
[{"reference": "PAY-…", "state": "paid", "amount": 50, "fee": 0.5, "net": 49.5, "refundable": 50, …}]
/api/v1/payments/summary
payments:read
{"currencies": [{"currency": "USD", "gross": 1200, "net": 1188, …}], "whiteLabel": false}
/api/v1/payments/{reference}
payments:read
{"reference": "PAY-…", …, "refunds": [...], "attempts": [...]}
/api/v1/payments/{reference}/refund
payments:refund
الدفعة بتفاصيلها بعد الاسترجاع (201).
صفحة الدفع باسمك
/api/v1/pay_page
payments:read
{"colour": "#1f6feb", "whiteLabel": true, "dns": {"type": "CNAME", "target": "connect.sy-ft.net"}, "domains": [...]}
/api/v1/pay_page
branding:write
الصفحة بعد التعديل، بشكل GET /api/v1/pay_page.
/api/v1/pay_page/domains
branding:write
الصفحة بعد الإضافة (201).
/api/v1/pay_page/domains/{domain_id}/check
branding:write
الصفحة بحالة النطاق waiting.
/api/v1/pay_page/domains/{domain_id}/remove
branding:write
الصفحة بعد الإزالة.
التقسيط
/api/v1/installments
installments:write
{"id": 7, "reference": "IC-…", "url": "https://…/installment/…", "state": "offered", "schedule": […]}
/api/v1/installments
installments:read
قائمة عقود مع amount_paid و amount_left و days_late.
/api/v1/installments/{contract_id}
installments:read
{"id": 7, …, "schedule": [{"due_date": "2026-10-10", "amount": 300.0, "state": "open", "pay_url": "…"}]}
/api/v1/installments/{contract_id}/cancel
installments:write
العقد بحالته cancelled.
حساب المزوّد
/api/v1/pay_accounts/providers
payments:read
{"providers": [{"code": "stripe", "label": "Stripe"}, …], "fields": {"stripe": {"public": "Publishable key", …}}}
/api/v1/pay_accounts
payments:read
قائمة حسابات؛ id · providerCode · env · state · publicKey · hasSecret · webhookUrl.
/api/v1/pay_accounts
payments:write
الحساب بحالته — بلا أي سرّ.
/api/v1/pay_accounts/{account_id}/qr
payments:write
الحساب بحالته، وفيه hasQr.
/api/v1/pay_accounts/{account_id}/activate
payments:write
الحساب بحالته الجديدة.
/api/v1/pay_domains
platform:domains
قائمة {id, host, state, message, checkedAt}.
/api/v1/pay_domains/{domain_id}/result
platform:domains
{id, host, state}.
/api/v1/merchants/branding
merchants:provision
colour · whiteLabel · hasLogo · domains.
/api/v1/merchants/payments
merchants:provision
{"scopes": [...], "granted": ["payments:read", …]}
توثيق الهويّة
/api/v1/kyc
توقيع فقط
{"state": "submitted", "check": {"lines": [...]}, "form": {...}}
/api/v1/kyc/documents
توقيع فقط
الحالة كما في GET /kyc.
/api/v1/kyc
توقيع فقط
الحالة ونتيجة الفحص.
/api/v1/merchants/kyc
merchants:kyc
[{"ref": "6", "merchant": "…", "waiting": true, "check": {...}}]
/api/v1/merchants/kyc/{ref}/documents/{kind}
merchants:kyc
{"mime": "image/jpeg", "data": "<base64>"}
/api/v1/merchants/kyc/{ref}/decide
merchants:kyc
الحالة بعد القرار.
/api/v1/merchants/kyc/{ref}/reset
merchants:kyc
{"state": "requested", "purgedDocuments": 3, "pausedAccounts": 1}
السوق
/api/v1/market/listings
market:read
{"ok": true, "listings": [{"key": "setup-service", "kind": "service", "vendor": "syriana", "platforms": {"salla": {"sell": "addon", "addon": "setup", "price": {"kind": "one_time", "amount": 199, "currency": "SAR"}}}}]}
شبكة الحماية
/api/v1/risk/check
risk:read
{"verdict": "allow|warn|block", "action": "allow|verify|block", "buyer_message": "…", "reporting_merchants": 2, "signals": [...], "clean_orders": 5, "vouches": 1, "identity_verified": false, "advice": "…"}
«action» هو ما قرّرتَه أنت في سياستك، و«buyer_message» نصّك أنت الذي يراه مشتريك عند المنع — اعرضه كما هو.
/api/v1/risk/report
risk:write
201 مع رقم البلاغ وتاريخ انتهائه، أو 409 إن سبق أن أبلغت.
/api/v1/risk/vouch
risk:write
201، أو 403 إن لم تكن بينكما معاملة، أو 409 إن سبق أن شهدت.
/api/v1/risk/verify
risk:write
201 مع url وexpires_at، أو already_verified إن كان موثّقاً، أو 403 إن لم تكن بينكما معاملة.
تشات كونكت
/api/v1/chat/provision
chat:connect
{"ok": true, …} — أو 422 مع «error» حين يرفض الطلب.
/api/v1/chat/event
chat:connect
{"ok": true, …} — أو 422 مع «error» حين يرفض الطلب.
/api/v1/chat/login
chat:connect
{"ok": true, …} — أو 422 مع «error» حين يرفض الطلب.
/api/v1/chat/send_login
chat:connect
{"ok": true, …} — أو 422 مع «error» حين يرفض الطلب.
/api/v1/chat/status
chat:connect
{"ok": true, …} — أو 422 مع «error» حين يرفض الطلب.
/api/v1/chat/rename
chat:connect
{"ok": true, …} — أو 422 مع «error» حين يرفض الطلب.
/api/v1/chat/addon
chat:connect
{"ok": true, …} — أو 422 مع «error» حين يرفض الطلب.
/api/v1/chat/addons_due
chat:connect
{"ok": true, …} — أو 422 مع «error» حين يرفض الطلب.
/api/v1/chat/addon_renew_failed
chat:connect
{"ok": true, …} — أو 422 مع «error» حين يرفض الطلب.
/api/v1/chat/help_request
chat:connect
{"ok": true, …} — أو 422 مع «error» حين يرفض الطلب.
التفعيل بالهاتف
/api/v1/activation/cid
activation:cid
{"ok": true, "cid": "111111 222222 ... 888888", "charged": 0.25, "request_id": 42}
الدومينات
/api/v1/domains/check
domains:read
{"ok": true, "currency": "USD", "results": [{"domain": "mystore.com", "available": true, "price": 16.12}]}
/api/v1/domains
domains:write
{"ok": true, "domain": "mystore.com", "years": 1, "charged": 16.12}
/api/v1/domains/<name>/renew
domains:write
{"ok": true, "expires": "2028-09-26"}
/api/v1/domains/<name>/nameservers
domains:write
{"ok": true}
/api/v1/domains/transfer
domains:write
{"ok": true}
/api/v1/domains/suggest
domains:read
{"ok": true, "about": "محل طباعة…", "results": [{"domain": "syrprint.com", "price": 15.84}]}
/api/v1/domains/<name>/dns
domains:read
{"ok": true, "records": [{"type": "A", "host": "@", "value": "1.2.3.4"}]}
/api/v1/domains/<name>/dns
domains:write
{"ok": true}
/api/v1/domains/<name>/dns/delete
domains:write
{"ok": true}
/api/v1/domains/<name>/forward
domains:write
{"ok": true}
/api/v1/domains
domains:read
{"ok": true, "domains": []}
بريد الأعمال
/api/v1/mail/plans
mail:read
{"ok": true, "plans": [{"id": "1762", "name": "Professional", "months": [1,3,6,12]}]}
/api/v1/mail
mail:write
{"ok": true, "account": {"id": 7, "state": "pending"}}
/api/v1/mail
mail:read
{"ok": true, "accounts": []}
/api/v1/mail/<id>/login
mail:write
{"ok": true, "url": "https://…"}
/api/v1/mail/<id>/renew
mail:write
{"ok": true, "account": {"expires": "2028-09-26"}}
/api/v1/mail/<id>/mailboxes
mail:write
{"ok": true, "account": {"mailboxes": 5}}
Cloudflare
/api/v1/cloud/zones
cloud:read
{"ok": true, "zones": [{"domain": "mystore.com", "state": "active", "plan": "free"}]}
/api/v1/cloud/zones
cloud:write
{"ok": true, "zone": {"domain": "mystore.com", "state": "pending", "name_servers": ["ada.ns.cloudflare.com"]}}
/api/v1/cloud/zones/<name>/dns
cloud:read
{"ok": true, "records": [{"id": "…", "type": "A", "host": "www", "value": "1.2.3.4", "proxied": true}]}
/api/v1/cloud/zones/<name>/dns
cloud:write
{"ok": true, "record": {"id": "…"}}
/api/v1/cloud/zones/<name>/dns/delete
cloud:write
{"ok": true}
/api/v1/cloud/zones/<name>/settings
cloud:write
{"ok": true}
/api/v1/cloud/zones/<name>/purge
cloud:write
{"ok": true}
/api/v1/cloud/zones/<name>/check
cloud:write
{"ok": true, "zone": {"state": "active"}}
Google Workspace
/api/v1/workspace/plans
workspace:read
{"ok": true, "plans": [{"id": "1657", "name": "Business Starter", "per_user_month": {"12": 3.84}}]}
/api/v1/workspace
workspace:write
{"ok": true, "subscription": {"id": 4, "state": "queued"}}
/api/v1/workspace
workspace:read
{"ok": true, "subscriptions": []}
/api/v1/workspace/<id>/users
workspace:write
{"ok": true}
استضافة ووردبريس
/api/v1/hosting/plans
hosting:read
{"ok": true, "plans": [{"id": "1889", "name": "Starter", "websites": "1"}]}
/api/v1/hosting
hosting:write
{"ok": true, "site": {"id": 3, "state": "pending"}}
/api/v1/hosting
hosting:read
{"ok": true, "sites": []}
/api/v1/hosting/<id>/login
hosting:write
{"ok": true, "url": "https://…"}
/api/v1/hosting/<id>/renew
hosting:write
{"ok": true, "site": {"expires": "2027-09-26"}}
الإشعارات الصادرة
نرسلها إلى الرابط الذي تسجّله، موقّعة بنفس الخوارزمية في
X-Syriana-Signature. أعد 2xx لتأكيد
الاستلام؛ وإلّا نعيد المحاولة ست مرّات على مدى ٤ ساعات.
order.fulfilled |
اكتمل الطلب وصدرت تراخيصه فعلاً (مفاتيح حقيقية). |
order.processing |
قُبل الطلب وخُصم، وبعض التراخيص قيد التجهيز. |
order.cancelled |
أُلغي الطلب وأُعيد المبلغ إلى رصيدك. |
license.delivered |
وصل مفتاح كان طلباً مسبقاً — اقرأه من /orders/{id}/licenses. |
wallet.low |
الرصيد قارب النفاد — قبل أن يفشل طلب، لا بعده. |
wallet.topped_up |
وصلت شحنتك وأُضيفت إلى رصيدك. |
wallet.settled |
صدرت فاتورة دورتك الآجلة وصُفِّر الرصيد. |
wallet.plan_due |
حان تجديد باقتك ورصيدك لا يكفي. الخدمة تعمل — اشحن ويُخصم التجديد تلقائياً. |
quota.warning |
بلغت ٨٠٪ من طلبات باقتك في هذه الدورة. تنبيه واحد لكل دورة، ولا نوقف شيئاً عند التجاوز. |
payment.paid |
دفع عميلك رابط دفع لك — المبلغ والرسوم والصافي في الجسم. هذا هو الحدث الذي تُسلّم عنده. |
payment.failed |
فشلت محاولة على رابطك؛ الرابط ما زال قابلاً للدفع. |
payment.cancelled |
ألغى الدافع العملية. |
payment.refunded |
استُرجعت الدفعة كاملةً — سواء عبر لوحتك أو لوحة المزوّد. الاسترجاع الجزئي لا يُطلق هذا الحدث. |
payment.partially_refunded |
استُرجع جزءٌ من الدفعة وبقيت «مدفوعة» — refunded في الجسم يقول كم خرج حتى الآن. |
installment.signed |
وقّع عميلك عقد التقسيط — كل قسط بعده يصلك payment.paid ومرجعه رقم العقد. |
installment.completed |
سُدّد العقد كاملاً. |
verification.verified |
أثبت المشتري هويّته لدينا. لا نرسل لك مستنداً ولا اسماً — النتيجة فقط. |
verification.refused |
لم يجتَز التوثيق. القرار في البيع يبقى قرارك. |
رموز الأخطاء
| الرمز | HTTP | المعنى |
|---|---|---|
unauthorized |
401 | مفتاح أو توقيع غير صالح. لا نفرّق بين السببين عمداً. |
stale_timestamp |
401 | ساعة خادمك بعيدة أكثر من ٥ دقائق عن ساعتنا. |
app_not_live |
403 | التطبيق لم يُعتمد بعد؛ يعمل تجريبياً فقط. |
app_suspended |
403 | التطبيق موقوف. |
live_key_required |
403 | نقطة تلمس بيانات حقيقيّة (مفاتيح، رسائل، سجلّات، شحن): لا تقبل مفتاح التجربة. |
missing_scope |
403 | المفتاح لا يملك الصلاحية المطلوبة لهذه النقطة. |
no_wallet |
403 | مفتاح قراءة بلا محفظة — البيع يحتاج محفظة. |
not_found |
404 | لا يوجد مورد بهذا المعرّف يخصّك. |
invalid_request |
400 | جسم الطلب ناقص أو خارج الحدود المسموحة. |
invalid_json |
400 | الجسم ليس كائن JSON صالحاً. |
unknown_sku |
400 | رمز منتج غير معروف أو غير قابل للبيع. |
customer_required |
400 | الطلب بلا «customer.ref» — من اشترى منك؟ إلزامي منذ 2026-09-09. |
missing_parameter |
400 | معامل إلزامي غائب (مثل since). |
invalid_parameter |
400 | قيمة معامل غير مفهومة. |
unsupported_currency |
400 | لا نملك سعر صرف لهذه العملة. الرسالة تُعدّد المتاح. |
currency_mismatch |
409 | محفظتك بعملة غير عملة الكتالوج. |
idempotency_required |
400 | ترويسة Idempotency-Key ناقصة على الطلبات. |
idempotency_conflict |
409 | نفس المفتاح استُخدم بجسم مختلف. |
request_in_progress |
409 | نسخة سابقة من نفس الطلب قيد المعالجة. |
wallet_busy |
409 | طلب آخر يستخدم محفظتك الآن — أعد المحاولة. |
conflict_retry |
409 | تعارض مؤقّت مع طلب متزامن. أعد المحاولة بنفس مفتاح التكرار. |
insufficient_funds |
402 | الرصيد لا يكفي. |
provision_failed |
400 | تعذّر إنشاء حساب التاجر — الرسالة تقول السبب. |
provision_in_progress |
409 | نفس المتجر يُنشأ الآن في طلب آخر — أعد المحاولة بعد ثوانٍ. |
unsupported_platform |
422 | الخدمة لا تعمل بعد على منصّة تطبيقك — الرسالة تُعدّد المتاح. |
channel_mismatch |
403 | تطبيقك يتكلّم باسم قناته وحدها — «channel» يخالفها أو لتطبيقٍ آخر. |
channel_unbound |
403 | لم تُربط قناةٌ بتطبيقك بعد — مراجعة التوثيق والهويّة ومنح الدفع لتجّار قناتك وحدها. راسلنا لربطها. |
not_a_connector |
403 | حساب تاجرٍ مُزوَّد لا يتكلّم باسم قناة. |
platform_only |
403 | صلاحيّات platform:* لمنصّة سيريانا نفسها. |
method_not_allowed |
405 | المسار موجود بطريقةٍ أخرى — ترويسة Allow تذكرها. |
live_only |
403 | إنشاء الحسابات يحتاج تطبيقاً معتمداً للإنتاج. |
invalid_idempotency_key |
400 | مفتاح التكرار لا يبدأ بـ«test:» في الإنتاج. |
replayed_request |
401 | طلب موقَّع وصل من قبل — وقّع كل طلب بختم زمني جديد. بتوقيع v2 يُستعمل التوقيع مرّةً واحدة لكلّ طريقة، حتى GET وحتى مع Idempotency-Key: أعد التوقيع عند إعادة المحاولة وأبقِ المفتاح نفسه فتأتيك الإجابة المحفوظة. |
signature_v2_required |
401 | تطبيقك (أو المنصّة كلّها) يشترط توقيع الإصدار ٢: X-Syriana-Signature-Version: 2 على "v2\n{timestamp}\n{METHOD}\n{path?query}\n{body}" (أسطر يفصلها LF). |
codes_not_yours |
403 | «codes» تحوي ما ليس من طلباتك عبر الواجهة في آخر ٦٠ يوماً. |
too_many_notices |
429 | أُرسل لهذا العميل ٢٠ إشعاراً خلال ٢٤ ساعة — أعد غداً. |
invalid_provider |
400 | مزوّد دفع غير معروف — راجع /pay_accounts/providers. |
missing_public |
400 | المفتاح العام (publicKey) إلزامي. |
missing_secret |
400 | المفتاح السرّي (secretKey) إلزامي عند أوّل ربط لهذا المزوّد. |
kyc_required |
403 | حساب env=live يحتاج توثيق هويّة أوّلاً — وثّق من /my/verify. |
already_verified |
409 | الحساب موثَّق بالفعل — القرار القائم لا يُعاد فتحه. |
kyc_locked |
409 | البيانات قيد المراجعة أو سليمة — لا يُعدَّل إلّا ما لم يتطابق. |
kyc_cooldown |
429 | قدّمت التوثيق للتوّ — انتظر دقيقة قبل إعادة التقديم. |
invalid_document |
400 | المستند مرفوض: ليس صورة، أو أكبر من الحدّ، أو نوعه غير معروف. |
activation_failed |
400 | التفعيل رفضه النموذج (مفتاحٌ ناقص مثلاً) — الرسالة تقول السبب بالحرف. |
topup_refused |
400 | الشحنة تحت الحدّ الأدنى أو فوق السقف أو بوسيلة غير متاحة لك — الرسالة تقول أيّها. |
not_available |
503 | خدمة لدينا غير مهيّأة الآن — البريد مثلاً. أعد المحاولة أو راسلنا. |
installment_refused |
422 | شروطك أو حدود باقتك ترفض عرض التقسيط هذا — رمز السبب أوّل الرسالة. |
manual_only |
422 | الاسترجاع يدويّ: أرجِع من لوحة المزوّد ثمّ أرسل alreadyDone: true. |
nothing_to_refund |
409 | الدفعة ليست مدفوعة أو استُرجعت كلّها. |
exceeds_payment |
400 | مبلغ الاسترجاع أكبر من المتبقّي. |
unsupported |
422 | المزوّد لا يتيح الاسترجاع الآليّ — أرجِع من لوحته ثمّ أرسل alreadyDone: true. |
no_account |
409 | لا حساب مزوّدٍ مربوط لهذه البيئة. |
no_provider_reference |
409 | المزوّد لم يُعطنا مرجع العمليّة بعد. |
no_credentials |
409 | مفاتيحك لدى المزوّد ناقصة. |
unreachable |
502 | المزوّد لا يردّ — لم يُسترجَع شيء، أعد المحاولة. |
refused |
402 | المزوّد رفض الاسترجاع — سببه في الرسالة. |
rate_limited |
429 | تجاوزت حدّ الطلبات في الدقيقة. |
payload_too_large |
413 | حجم الطلب أكبر من المسموح. |
internal_error |
500 | خطأ لدينا. سُجّل بالكامل للمراجعة. |
already_reported |
409 | لك بلاغ من هذا النوع على هذه الهويّة بالفعل. |
no_transaction |
403 | لا معاملة بينك وبين هذا المشتري — لا يُبلَّغ ولا يُشهَد إلّا عمّن اشترى منك فعلاً. |
risk_quota_exceeded |
402 | استهلكت استعلامات باقتك لهذه الدورة. الإبلاغ يبقى مجانياً بلا حدّ. |