اربط منصتك
بمتاجر عملائك
التاجر بيوافق مرة واحدة، وإنت بتاخد توكن محدود الصلاحيات على متجره هو بس — مع إشعارات فورية موقّعة لكل حدث.
- OAuth 2.0 + PKCE
- ويبهوك موقّعة HMAC
- صلاحيات محدودة لكل متجر
الأسرع · رابط تثبيت بدون تسجيل
مش محتاج تسجّل تطبيقك عندنا ولا تستنى موافقة. ابنِ الرابط ده وحطه في لوحة تحكم عميلك — يوافق، وإحنا بنبعتلك المفتاح على سيرفرك.
GET https://elmatajer.com/install-app ?app_name=Your App &app_description=What it does, in one line &app_icon=https://your-app.com/logo.png &permissions=orders.read,orders.write,webhooks &callback_url=https://your-app.com/callback // we POST the key here &webhook_url=https://your-app.com/webhook // events go here &redirect_url=https://your-app.com/connected // merchant lands here → after approval we POST to your callback_url: { "api_key": "19|xxxxxxxxxxxxxxxx", "store_id": 42, "scope": "orders.read orders.write webhooks", "webhook_secret": "...", "api_base": "https://elmatajer.com/api/v1/vendor" }
مهم · أول ربط بيمرّ على مراجعة
أول ما تاجر يضغط رابطك، تطبيقك بيتسجّل عندنا ويدخل طابور المراجعة. لحد ما نوافق، أي محاولة ربط بترجع 403 والتاجر بيشوف صفحة «التطبيق قيد المراجعة».
أو · تدفّق OAuth الكامل
لو عايز شارة «تطبيق موثّق» وعناوين إرجاع مقفولة على تطبيقك، سجّل تطبيقك واستخدم التدفّق ده.
تدفّق OAuth 2.0 قياسي (authorization code + PKCE). لو ربطت قبل كده مع أي منصة، ده نفس الكلام.
١ · رابط الموافقة
وجّه التاجر للرابط ده. لو مش مسجّل دخول هيسجّل الأول وبعدين يرجع لنفس الصفحة.
GET https://elmatajer.com/oauth/authorize ?client_id=YOUR_CLIENT_ID &redirect_uri=https://your-app.com/callback &scope=orders.read orders.write webhooks &state=RANDOM_ANTI_CSRF_VALUE &code_challenge=BASE64URL(SHA256(verifier)) &code_challenge_method=S256
٢ · بدّل الكود بتوكن
من سيرفرك — مش من المتصفح. الكود صالح ٥ دقايق ولمرة واحدة بس.
POST https://elmatajer.com/oauth/token Content-Type: application/json { "grant_type": "authorization_code", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", "code": "THE_CODE_FROM_THE_REDIRECT", "redirect_uri": "https://your-app.com/callback", "code_verifier": "THE_ORIGINAL_VERIFIER" } → 200 OK { "access_token": "17|xxxxxxxxxxxxxxxx", "token_type": "Bearer", "scope": "orders.read orders.write webhooks", "store": { "vendor_id": 42, "name": "..." }, "webhook_secret": "store this — it signs your webhooks", "api_base": "https://elmatajer.com/api/v1/vendor" } → 401 { "error": "invalid_client" } wrong client_id / client_secret, or the app is not approved → 400 { "error": "invalid_grant", "error_description": "code_expired" } error_description: code_expired · code_already_used · redirect_uri_mismatch · invalid_code_verifier
٣ · الصلاحيات
اطلب أقل صلاحيات ممكنة — كل صلاحية بتظهر للتاجر بالاسم، والقائمة القصيرة بتتوافق عليها أسرع.
| الصلاحية | بتسمح بإيه |
|---|---|
| الطلبات | |
orders.read |
الوصول إلى الطلبات |
orders.write |
تعديل الطلبات |
| الكتالوج | |
products.read |
الوصول إلى المنتجات |
products.write |
تعديل المنتجات |
categories.read |
الوصول إلى الأقسام |
| العملاء | |
customers.read |
الوصول إلى العملاء |
carts.read |
السلات المتروكة |
| الشحن | |
shipping.read |
مناطق الشحن |
| التسويق | |
coupons.read |
الوصول إلى الكوبونات |
| التقارير | |
reports.read |
الوصول إلى التقارير |
| الإشعارات | |
webhooks |
استقبال إشعارات الأحداث |
٤ · استدعاء الـ API
المتجر بيتحدد من التوكن نفسه — مفيش vendor_id بتبعته، وما ينفعش توصل لمتجر تاني.
GET https://elmatajer.com/api/v1/vendor/orders Authorization: Bearer YOUR_ACCESS_TOKEN Accept: application/json → 200 OK { "status": "ok", "data": [ { "id": 1042, "order_number": "AB1234", "status": 1, "status_type": 1, "status_label": "...", "payment_status": 1, "payment_type": "cod", "customer": { "name": "...", "email": "...", "mobile": "..." }, "totals": { "sub_total": 1200, "delivery_charge": 50, "discount_amount": 0, "grand_total": 1250, "currency": "...", "currency_code": "EGP" }, "items": [ ... ], "created_at": "2026-10-08T12:00:00+03:00" } ], "meta": { "current_page": 1, "last_page": 3, "per_page": 20, "total": 52, "has_more": true } } → 403 if the merchant did not grant that scope { "code": "insufficient_scope", "required_scope": "orders.read" }
مرجع نقاط النهاية
كل نقطة تقدر توصلها، مرتّبة حسب الصلاحية اللي بتفتحها. كلها تحت https://elmatajer.com/api/v1
| الطريقة | المسار |
|---|---|
orders.read
الوصول إلى الطلبات
|
|
GET |
/vendor/orders |
GET |
/vendor/orders/statuses |
GET |
/vendor/orders/summary |
GET |
/vendor/orders/{id} |
orders.write
تعديل الطلبات
|
|
POST |
/vendor/orders/{id}/status |
products.read
الوصول إلى المنتجات
|
|
GET |
/vendor/products |
GET |
/vendor/products/brands |
GET |
/vendor/products/counts |
GET |
/vendor/products/variant-options |
GET |
/vendor/products/{id} |
products.write
تعديل المنتجات
|
|
POST |
/vendor/categories |
DELETE |
/vendor/categories/{id} |
PUT |
/vendor/categories/{id} |
POST |
/vendor/categories/{id}/status |
POST |
/vendor/products |
POST |
/vendor/products/ai-description |
POST |
/vendor/products/translate |
POST |
/vendor/products/variant-options |
DELETE |
/vendor/products/{id} |
PUT |
/vendor/products/{id} |
POST |
/vendor/products/{id}/availability |
POST |
/vendor/products/{id}/images |
DELETE |
/vendor/products/{id}/images/{imageId} |
POST |
/vendor/products/{id}/stock |
categories.read
الوصول إلى الأقسام
|
|
GET |
/vendor/categories |
GET |
/vendor/products/categories |
customers.read
الوصول إلى العملاء
|
|
GET |
/vendor/customers |
shipping.read
مناطق الشحن
|
|
GET |
/vendor/shipping |
coupons.read
الوصول إلى الكوبونات
|
|
GET |
/vendor/coupons |
reports.read
الوصول إلى التقارير
|
|
GET |
/vendor/reports |
البيانات المرسلة والردود
ابعت JSON (أو multipart/form-data لو فيه ملفات). * = إجباري. أي حقل مش في القايمة بيتجاهل — زي vendor_id أو id.
| نقطة النهاية | الحقول | ملاحظات |
|---|---|---|
POST/vendor/products |
name* · price* · category_ids*[] · name_en · description · description_en · compare_price · sku · video_url · brand_ids[] · is_available · manage_stock · stock_qty · low_qty · variants[]{color, size, price*, qty, sku} · images[] |
201 + المنتج. is_available=false يخفي المنتج. images ملفات (jpg/png/webp حتى 4MB، 10 بالكتير) — ساعتها ابعت multipart/form-data. |
PUT/vendor/products/{id} |
نفس حقول POST |
استبدال كامل: الحقول الاختيارية الناقصة بتتمسح، و manage_stock الناقصة بتوقف تتبع المخزون. |
POST/vendor/products/{id}/stock |
qty* · low_qty |
بيشغّل تتبع المخزون. نزول qty تحت low_qty بيبعت product.low_stock. |
POST/vendor/products/{id}/availability |
— |
بدون body: بيقلب الحالة ظاهر ↔ مخفي ويرجّع المنتج. |
POST/vendor/products/{id}/images |
image* (file) |
multipart/form-data. 201 + { id, url }. |
DELETE/vendor/products/{id} |
— |
حذف نهائي — مفيش استرجاع. |
POST/vendor/categories |
name* · name_en · image (file) |
201 + القسم. لو فيه image ابعت multipart/form-data. PUT /categories/{id} بنفس الحقول. |
POST/vendor/categories/{id}/status |
is_available* |
true يظهر القسم، false يخفيه. |
DELETE/vendor/categories/{id} |
— |
حذف نهائي، وبيتشال من كل منتجاته — product_count في GET /categories بيقولك كام منتج هيتأثر. |
POST/vendor/orders/{id}/status |
status* · type* |
status = id من GET /orders/statuses، و type = نوعها (1–6). بيشغّل نفس آثار لوحة التحكم: رسالة/إيميل للعميل وإرجاع المخزون عند الإلغاء. |
حقول الرد
كل رد ناجح: { "status": "ok", "data": … } — والقوايم المقسّمة لصفحات فيها meta كمان.
| المورد | الحقول |
|---|---|
order (GET /orders, /orders/{id}) |
id, order_number, status, status_type, status_label, payment_status, payment_type, order_type, customer{name, email, mobile}, address{line, building, landmark} (when set), totals{sub_total, delivery_charge, discount_amount, tips, grand_total, currency, currency_code}, items_count, items[], notes, created_at |
order status (GET /orders/statuses?order_type=1) |
id, type, name |
product (GET /products, /products/{id}) |
id, name, name_en, slug, sku, image, images[]{id, url} (single product), price, original_price, discount_pct, stock{state: in|low|out|untracked, qty, tracked}, category_ids, is_available, is_draft, has_variants, variants[]{group, options}, category, views, video_url, description, created_at |
GET /products/counts |
all, active, hidden, draft, out, low |
GET /products/brands |
id, name, image, is_available |
GET /products/variant-options |
colors[], sizes[] |
category (GET /categories) |
id, name, name_en, slug, image, is_available, product_count |
GET /products/categories |
id, name, image, is_available |
customer (GET /customers?q=&per_page=) |
id, name, email, mobile, orders_count, total_spent, currency, currency_code, last_order_at, joined_at |
coupon (GET /coupons — not paginated) |
id, name, code, type: percent|amount, amount, min_amount, usage_limit, start_date, exp_date, is_active, is_expired |
GET /shipping |
zones[]{id, name, charge, is_active}, free_shipping_amount, currency, currency_code |
GET /reports?range=week|month|year (month) |
range, currency, currency_code, revenue, orders, avg_order, series[]{label, total}, top_products[]{name, sold, revenue} |
الأخطاء والحدود
كل خطأ بيرجع JSON فيه code ثابت — اتعامل معاه بالـ code مش بنص الرسالة، الرسالة بتتغير باللغة.
| HTTP | code | المعنى وإيه اللي تعمله |
|---|---|---|
401 |
unauthenticated |
التوكن ناقص أو مسحوب — التاجر ألغى الربط. اطلب منه يربط تاني. |
402 |
subscription_expired |
اشتراك التاجر في المنصة انتهى — كل endpoints المتجر بترجع 402 لحد ما يجدد. وقّف المزامنة واعرض له رسالة. |
403 |
insufficient_scope |
التاجر ما منحش الصلاحية دي (الرد بيقولك required_scope)، أو endpoint مش متاح للتطبيقات أصلًا. |
403 |
app_disabled |
المنصة أوقفت تطبيقك أو لغت اعتماده — التوكن مش هيشتغل لحد ما يرجع. كلّمنا. |
403 |
— |
على /install-app: تطبيقك لسه تحت المراجعة أو مرفوض. |
404 |
not_found |
العنصر أو الـ endpoint مش موجود في متجر التاجر ده (عنصر متجر تاني = 404 برضه). |
405 |
method_not_allowed |
الـ method غلط للمسار ده. |
422 |
validation_failed |
بيانات ناقصة أو غير صالحة — الرد فيه errors لكل حقل. تغيير حالة طلب بحالة مش متاحة = invalid_status. |
429 |
rate_limited |
60 طلب في الدقيقة لكل متجر (مشتركة بين كل التطبيقات المربوطة بالمتجر وتطبيق التاجر نفسه). تابع X-RateLimit-Remaining، ولو جالك 429 استنى Retry-After ثانية. |
٥ · الويبهوك
بنبعتلك POST بكل حدث، وبنعيد المحاولة بتدرّج لو سيرفرك مردّش. لازم ترجّع 2xx.
| الحدث | إمتى | محتاج | data |
|---|---|---|---|
order.created |
طلب جديد اتعمل | orders.read + webhooks |
order{id, order_number, sub_total, tax, discount, grand_total, payment_type, order_type, order_from, status}, customer{id, name, email, phone} + vendor_id |
order.paid |
الطلب اتدفع | orders.read + webhooks |
order_id, order_number, grand_total, payment_type, customer{id, name, email, phone} + vendor_id |
order.status_changed |
حالة الطلب اتغيّرت | orders.read + webhooks |
order_id, order_number, from_status, to_status, customer{id, name, email, phone} + vendor_id |
customer.created |
عميل جديد سجّل | customers.read + webhooks |
customer{id, name, email, phone}, source + vendor_id |
product.low_stock |
مخزون منتج قرب يخلص | products.read + webhooks |
product{id, name, sku, price}, qty_remaining, threshold + vendor_id |
cart.abandoned |
سلة اتسابت من غير شراء (بمرجع سلة مُعمّى cart_ref، مش رقم جلسة) | carts.read + webhooks |
cart{cart_ref, user_id, item_count, subtotal, items[], last_activity}, idle_minutes + vendor_id |
POST https://your-app.com/webhook X-Elmatajer-Event: order.created X-Elmatajer-Event-Id: 9f1c... // use it to de-duplicate X-Elmatajer-Signature: sha256=<hmac> { "event": "order.created", "event_id": "9f1c...", "vendor_id": 42, "created_at": "2026-10-08T12:00:00+03:00", "data": { ... } } // Verify before trusting anything — PHP $expected = hash_hmac('sha256', $rawBody, $webhookSecret); if (!hash_equals($expected, str_replace('sha256=', '', $header))) abort(401);
مثال كامل شغّال
الحتّتين اللي كل شريك لازم يظبّطهم: استقبال المفتاح، والتحقق من الويبهوك قبل ما تصدّقه.
// 1 — receive the merchant's credentials (your callback_url) POST /elmatajer/callback $body = json_decode(file_get_contents('php://input'), true); saveStore([ 'store_id' => $body['store_id'], // unique per merchant 'api_key' => $body['api_key'], // use as Bearer token 'webhook_secret' => $body['webhook_secret'], // verifies their events ]); http_response_code(200); // answer 2xx or we treat it as failed // 2 — call the API for that store $res = $http->get('https://elmatajer.com/api/v1/vendor/orders', [ 'headers' => [ 'Authorization' => 'Bearer ' . $store['api_key'], 'Accept' => 'application/json', ], ]); // no store_id in the request — the token already identifies the store // 3 — receive events (your webhook_url) POST /elmatajer/webhook $raw = file_get_contents('php://input'); $sig = str_replace('sha256=', '', $_SERVER['HTTP_X_ELMATAJER_SIGNATURE'] ?? ''); $store = findStoreById(json_decode($raw, true)['vendor_id']); // VERIFY FIRST — never trust the body before this passes if (!hash_equals(hash_hmac('sha256', $raw, $store['webhook_secret']), $sig)) { http_response_code(401); exit; } $event = json_decode($raw, true); // de-duplicate: retries resend the same event_id if (alreadyHandled($_SERVER['HTTP_X_ELMATAJER_EVENT_ID'])) { http_response_code(200); exit; } match ($event['event']) { 'order.created' => onNewOrder($event['data']), 'order.status_changed' => onStatusChange($event['data']), default => null, }; http_response_code(200);
عايز تبدأ؟
ابعتلنا اسم التطبيق، وصف قصير، اللوجو، عناوين الإرجاع، والصلاحيات اللي محتاجها — ونرجعلك client_id و client_secret.
تواصل معناأي تاجر يقدر يشوف تطبيقك وصلاحياته وآخر استخدام، ويسحبها في أي وقت من صفحة «التطبيقات المرتبطة». وقتها التوكن بيتلغي فورًا.