شكل الاستجابة والتقسيم إلى صفحات
كل استجابة /v1 — نجاح أو خطأ — تشترك في مغلّف واحد. بمجرد أن تحلّله مرة واحدة، تكون قد حللتها كلها.
المغلّف
{
"statusCode": 200,
"errorCode": null,
"message": null,
"data": { }
}
| الحقل | النوع | المعنى |
| --- | --- | --- |
| statusCode | عدد صحيح | نفس قيمة رمز حالة HTTP. |
| errorCode | نص أو null | null عند النجاح. رمز ثابت وقابل للقراءة آليًا عند الفشل — راجع الأخطاء. |
| message | نص أو null | null عند النجاح. وصف مقروء للبشر عند الفشل. آمن للتسجيل في السجلات، لكن غير آمن للمطابقة النمطية عليه — طابق errorCode بدلًا منه. |
| data | كائن أو مصفوفة أو null | الحمولة عند النجاح. دائمًا null عند الفشل. |
استجابة النجاح تحمل دائمًا errorCode: null وmessage: null؛ واستجابة الفشل تحمل دائمًا data: null. الشكل لا يختلط أبدًا.
// مورد واحد
{ "statusCode": 200, "errorCode": null, "message": null, "data": { "id": "...", "name": "..." } }
// فشل
{ "statusCode": 404, "errorCode": "not_found", "message": "Resource not found", "data": null }
التقسيم إلى صفحات
كل نقطة نهاية للسرد تقبل:
| المعامل | الافتراضي | المعنى |
| --- | --- | --- |
| limit | 50 | عدد العناصر في الصفحة. يجب أن يكون بين 1 و100؛ أي قيمة أخرى تُرجع 400 bad_request. |
| cursor | لا شيء | نص معتم من next_cursor استجابة سابقة. مرّره كما استلمته تمامًا. |
الاستجابة المقسّمة تضيف مفتاحًا واحدًا بجانب data، لا داخله أبدًا:
{
"statusCode": 200,
"errorCode": null,
"message": null,
"data": [ { "id": "..." }, { "id": "..." } ],
"next_cursor": "eyJ0IjoxNzM3..."
}
يكون next_cursor نصًا عند وجود صفحة أخرى، وnull في الصفحة الأخيرة (يبقى المفتاح موجودًا — لا يُحذف أبدًا). تصفّح كل الصفحات يبدو هكذا:
cursor=""
while :; do
res=$(curl -s "https://api.oneguard.one/v1/vaults?limit=100${cursor:+&cursor=$cursor}" \
-H "Authorization: Bearer $TOKEN")
echo "$res" | jq -c '.data[]'
cursor=$(echo "$res" | jq -r '.next_cursor // empty')
[ -z "$cursor" ] && break
done
المؤشر المشوَّه أو المنتهي يُرجع 400 مع errorCode: "invalid_cursor" — لا تبنِه إلا بإعادة تمرير next_cursor أُعطيته، لا يدويًا أبدًا.
الكتابات الآمنة عند التكرار (Idempotent)
أي POST ينشئ موردًا يقبل ترويسة Idempotency-Key (أي نص، من 1 إلى 255 حرفًا قابلًا للطباعة). أرسل المفتاح نفسه عند إعادة محاولة الطلب نفسه فتحصل على النتيجة الأصلية بعينها بدل إنشاء مورد ثانٍ:
curl -s https://api.oneguard.one/v1/vaults \
-X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: 8f14e45f-...-a3b2" \
-H "Content-Type: application/json" \
-d '{"name": "Marketing site"}'
| الحالة | النتيجة |
| --- | --- |
| نفس المفتاح، نفس نص الطلب | تُعاد الاستجابة المخزَّنة، مع ترويسة إضافية Idempotent-Replayed: true. |
| نفس المفتاح، نص طلب مختلف | 422 unprocessable_entity مع errorCode: "idempotency_key_reuse". |
| نفس المفتاح، أثناء معالجة الطلب الأول | 409 conflict مع errorCode: "idempotency_in_progress". |
| بلا ترويسة Idempotency-Key | يُنفَّذ الطلب دائمًا؛ لا يحدث أي إزالة تكرار. |
المفاتيح محدودة النطاق بالمؤسسة، ومفتاح API، والمسار والطريقة، وتُحفظ لمدة 24 ساعة. الطلب الذي يفشل (أي 4xx أو 5xx) لا يُخزَّن شيء منه، فإعادة المحاولة بعد فشل بنفس المفتاح تُنفّذ الطلب من جديد ببساطة.
نصوص الطلبات
نصوص POST/PATCH كائنات JSON، بترويسة Content-Type: application/json، بحد أقصى 1 ميغابايت. النص الفارغ أو المفقود أو غير الكائن في مسار يتطلب واحدًا يُرجع 400 bad_request.
ترويسات في كل استجابة
| الترويسة | المعنى |
| --- | --- |
| X-Request-Id | بصيغة req_<uuid>. اقتبسه عند التواصل مع الدعم بخصوص طلب معيّن — لا يتكرر أبدًا داخل نص JSON. |
| Cache-Control: no-store | لا شيء من /v1 قابل للتخزين المؤقت أبدًا. |
CORS مغلق: لا توجد ترويسة Access-Control-* في أي استجابة /v1، بما فيها الطلبات الاستباقية (preflight). استدعِ الواجهة من خادم، لا من متصفح.