الانتقال إلى المحتوى

واجهة BookNDish البرمجية API

ابنِ على BookNDish

واجهة REST API موثقة، وخطافات ويب موقّعة، وبيئة تجريبية مجانية. اربط موقعًا إلكترونيًا أو شاشة مطبخ أو نظام CRM أو أداة محاسبة بأي مطعم على BookNDish، بإذن من المالك.

مرجع API أدناه مكتوب باللغة الإنجليزية.

بداية سريعة

  1. 01

    احصل على رمز

    يفتح مالك المطعم أو أحد المديرين الإعدادات ثم المطورون، وينشئ رمزًا بالصلاحيات التي يحتاجها تكاملك، أو رمزًا للبيئة التجريبية للبدء ببيانات نموذجية. الرمز يخص مطعمًا واحدًا ويُعرض مرة واحدة فقط.

  2. 02

    استدعِ API

    أرسل الرمز بوصفه رمز Bearer. كل رد بصيغة JSON ويحتوي على data وmeta وlinks.

  3. 03

    اكتب بأمان

    أعطِ كل طلب POST قيمة Idempotency-Key. تعيد المحاولة بالمفتاح نفسه الرد الأول، ولا يُنفَّذ شيء مرتين.

العنوان الأساسي: https://restaurant-saas-19qm.onrender.com/api/v1

curl https://restaurant-saas-19qm.onrender.com/api/v1/menus \
  -H "Authorization: Bearer $BOOKNDISH_TOKEN" \
  -H "Accept: application/json"
curl -X POST https://restaurant-saas-19qm.onrender.com/api/v1/orders \
  -H "Authorization: Bearer $BOOKNDISH_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5b0e1f4e-6a8c-4e0e-9a55-0c1f2d3e4f5a" \
  -d '{"type":"pickup","customer_name":"Ada","customer_phone":"+15550100","lines":[{"item_id":42,"quantity":2}]}'

الصلاحيات

يحمل الرمز الصلاحيات التي مُنحت له فقط، وكل نقطة نهاية تطلب واحدة منها.

  • menu:read
  • menu:write
  • orders:read
  • orders:write
  • reservations:read
  • reservations:write
  • guests:read
  • reports:read

مرجع API

Menu

8 نقطة نهاية

Menus, categories and dishes.

GET/menus

Every menu with its categories

الصلاحية: menu:read

مثال على الرد 200
{
    "data": [
        {
            "id": 3,
            "name": {
                "en": "Dinner"
            },
            "is_active": true,
            "sort_order": 0,
            "categories": [
                {
                    "id": 7,
                    "menu_id": 3,
                    "name": {
                        "en": "Mains"
                    },
                    "is_active": true,
                    "sort_order": 0
                }
            ]
        }
    ],
    "meta": {
        "count": 1,
        "currency": "EUR"
    },
    "links": {}
}
GET/menus/{menu}

One menu, its categories and their dishes

meta.now says what menus by time of day and happy hours make of the menu right now, on the default branch's clock: whether it and each category are open, and each dish or size a happy hour has cut, at the price charged now.

الصلاحية: menu:read

المعاملات
الاسمالموضعالنوعمطلوب
menupathintegerمطلوب
مثال على الرد 200
{
    "data": {
        "id": 3,
        "name": {
            "en": "Dinner"
        },
        "is_active": true,
        "sort_order": 0,
        "categories": [
            {
                "id": 7,
                "menu_id": 3,
                "name": {
                    "en": "Mains"
                },
                "is_active": true,
                "sort_order": 0,
                "items": [
                    {
                        "id": 42,
                        "category_id": 7,
                        "name": {
                            "en": "Burger"
                        },
                        "description": {
                            "en": "Beef, brioche"
                        },
                        "price_minor": 1100,
                        "is_available": true,
                        "show_online": true,
                        "dietary": "none",
                        "allergens": [
                            "gluten",
                            "milk"
                        ],
                        "sort_order": 1,
                        "variants": [],
                        "modifier_groups": [],
                        "updated_at": "2026-09-29T10:12:00+00:00"
                    }
                ]
            }
        ]
    },
    "meta": {
        "currency": "EUR",
        "now": {
            "available": true,
            "closed_category_ids": [],
            "prices": []
        }
    },
    "links": {}
}
GET/categories

Every category, or one menu's

الصلاحية: menu:read

المعاملات
الاسمالموضعالنوعمطلوب
menu_idqueryintegerاختياري
مثال على الرد 200
{
    "data": [
        {
            "id": 7,
            "menu_id": 3,
            "name": {
                "en": "Mains"
            },
            "is_active": true,
            "sort_order": 0
        }
    ],
    "meta": {
        "count": 1
    },
    "links": {}
}
GET/items

Dishes, a page at a time

الصلاحية: menu:read

المعاملات
الاسمالموضعالنوعمطلوب
category_idqueryintegerاختياري
availablequerystring: "true", "false", "1", "0"اختياري
per_pagequeryintegerاختياري
cursorquerystringاختياري
مثال على الرد 200
{
    "data": [
        {
            "id": 42,
            "category_id": 7,
            "name": {
                "en": "Burger"
            },
            "description": null,
            "price_minor": 1100,
            "is_available": true,
            "show_online": true,
            "dietary": "none",
            "allergens": null,
            "sort_order": 1,
            "variants": [],
            "modifier_groups": [],
            "updated_at": "2026-09-29T10:12:00+00:00"
        }
    ],
    "meta": {
        "currency": "EUR",
        "per_page": 25,
        "next_cursor": null,
        "prev_cursor": null
    },
    "links": {
        "next": null,
        "prev": null
    }
}
POST/items

Add a dish

name and description are a string in the restaurant's default language, or a translation map. A dish is priced by price_minor, or per size by variants.

الصلاحية: menu:write

المعاملات
الاسمالموضعالنوعمطلوب
Idempotency-Keyheaderstringاختياري
محتوى الطلب
{
    "category_id": 7,
    "name": "Burger",
    "price_minor": 1100,
    "allergens": [
        "gluten"
    ]
}
مثال على الرد 201
{
    "data": {
        "id": 42,
        "category_id": 7,
        "name": {
            "en": "Burger"
        },
        "description": {
            "en": "Beef, brioche"
        },
        "price_minor": 1100,
        "is_available": true,
        "show_online": true,
        "dietary": "none",
        "allergens": [
            "gluten",
            "milk"
        ],
        "sort_order": 1,
        "variants": [],
        "modifier_groups": [
            {
                "id": 5,
                "name": {
                    "en": "Extras"
                },
                "min_select": 0,
                "max_select": null,
                "options": [
                    {
                        "id": 9,
                        "name": {
                            "en": "Cheese"
                        },
                        "price_minor": 100,
                        "is_available": true
                    }
                ]
            }
        ],
        "updated_at": "2026-09-29T10:12:00+00:00"
    },
    "meta": {
        "currency": "USD"
    },
    "links": {}
}
GET/items/{item}

One dish

الصلاحية: menu:read

المعاملات
الاسمالموضعالنوعمطلوب
itempathintegerمطلوب
مثال على الرد 200
{
    "data": {
        "id": 42,
        "category_id": 7,
        "name": {
            "en": "Burger"
        },
        "description": {
            "en": "Beef, brioche"
        },
        "price_minor": 1100,
        "is_available": true,
        "show_online": true,
        "dietary": "none",
        "allergens": [
            "gluten",
            "milk"
        ],
        "sort_order": 1,
        "variants": [],
        "modifier_groups": [
            {
                "id": 5,
                "name": {
                    "en": "Extras"
                },
                "min_select": 0,
                "max_select": null,
                "options": [
                    {
                        "id": 9,
                        "name": {
                            "en": "Cheese"
                        },
                        "price_minor": 100,
                        "is_available": true
                    }
                ]
            }
        ],
        "updated_at": "2026-09-29T10:12:00+00:00"
    },
    "meta": {
        "currency": "USD"
    },
    "links": {}
}
PATCH/items/{item}

Change a dish

Only the fields sent change; variants and modifier groups are kept. A price change is recorded in the activity log. A dish priced per size refuses price_minor.

الصلاحية: menu:write

المعاملات
الاسمالموضعالنوعمطلوب
itempathintegerمطلوب
محتوى الطلب
{
    "price_minor": 1250
}
مثال على الرد 200
{
    "data": {
        "id": 42,
        "category_id": 7,
        "name": {
            "en": "Burger"
        },
        "description": {
            "en": "Beef, brioche"
        },
        "price_minor": 1100,
        "is_available": true,
        "show_online": true,
        "dietary": "none",
        "allergens": [
            "gluten",
            "milk"
        ],
        "sort_order": 1,
        "variants": [],
        "modifier_groups": [
            {
                "id": 5,
                "name": {
                    "en": "Extras"
                },
                "min_select": 0,
                "max_select": null,
                "options": [
                    {
                        "id": 9,
                        "name": {
                            "en": "Cheese"
                        },
                        "price_minor": 100,
                        "is_available": true
                    }
                ]
            }
        ],
        "updated_at": "2026-09-29T10:12:00+00:00"
    },
    "meta": {
        "currency": "USD"
    },
    "links": {}
}
PUT/items/{item}/availability

Sold out, and back

Recorded like a flip on the till.

الصلاحية: menu:write

المعاملات
الاسمالموضعالنوعمطلوب
itempathintegerمطلوب
محتوى الطلب
{
    "is_available": false
}
مثال على الرد 200
{
    "data": {
        "id": 42,
        "category_id": 7,
        "name": {
            "en": "Burger"
        },
        "description": {
            "en": "Beef, brioche"
        },
        "price_minor": 1100,
        "is_available": true,
        "show_online": true,
        "dietary": "none",
        "allergens": [
            "gluten",
            "milk"
        ],
        "sort_order": 1,
        "variants": [],
        "modifier_groups": [
            {
                "id": 5,
                "name": {
                    "en": "Extras"
                },
                "min_select": 0,
                "max_select": null,
                "options": [
                    {
                        "id": 9,
                        "name": {
                            "en": "Cheese"
                        },
                        "price_minor": 100,
                        "is_available": true
                    }
                ]
            }
        ],
        "updated_at": "2026-09-29T10:12:00+00:00"
    },
    "meta": {
        "currency": "USD"
    },
    "links": {}
}

Orders

4 نقطة نهاية

Orders on any channel, and their status.

GET/orders

Orders, newest first

الصلاحية: orders:read

المعاملات
الاسمالموضعالنوعمطلوب
statusquerystringاختياري
typequerystring: "dine_in", "pickup", "delivery"اختياري
channelquerystringاختياري
placed_fromquerystringاختياري
placed_toquerystringاختياري
updated_sincequerystringاختياري
per_pagequeryintegerاختياري
cursorquerystringاختياري
مثال على الرد 200
{
    "data": [],
    "meta": {
        "per_page": 25,
        "next_cursor": null,
        "prev_cursor": null
    },
    "links": {
        "next": null,
        "prev": null
    }
}
POST/orders

Place an order

The order is priced from the menu (any price in the body is ignored), the owner's ordering rules apply (types offered, minimum order, delivery zones, opening hours; an order outside hours is held for the next opening), the kitchen gets its tickets and a connected POS gets the order. Dine-in orders need table_code.

الصلاحية: orders:write

المعاملات
الاسمالموضعالنوعمطلوب
Idempotency-Keyheaderstringاختياري
محتوى الطلب
{
    "type": "delivery",
    "channel": "web",
    "customer_name": "Ada Lovelace",
    "customer_phone": "+15550100",
    "customer_email": "ada@example.com",
    "note": "Ring twice",
    "delivery_address": {
        "line1": "12 Main St",
        "city": "Springfield"
    },
    "lines": [
        {
            "item_id": 42,
            "quantity": 2,
            "option_ids": [
                9
            ]
        },
        {
            "item_id": 43,
            "variant_id": 11,
            "quantity": 1
        }
    ]
}
مثال على الرد 201
{
    "data": {
        "id": "9d1c7a52-8f0e-4b8c-a1f5-3a1a5b1f0c2d",
        "status": "placed",
        "type": "delivery",
        "channel": "web",
        "branch_id": 1,
        "table": null,
        "customer": {
            "name": "Ada Lovelace",
            "phone": "+15550100",
            "email": "ada@example.com"
        },
        "note": "Ring twice",
        "delivery_address": {
            "line1": "12 Main St",
            "city": "Springfield"
        },
        "currency": "USD",
        "subtotal_minor": 2300,
        "discount_minor": 0,
        "service_charge_minor": 0,
        "tax_added_minor": 0,
        "delivery_fee_minor": 300,
        "total_minor": 2600,
        "tax_breakdown": null,
        "cancel_reason": null,
        "items": [
            {
                "id": 1,
                "item_id": 42,
                "name": {
                    "en": "Burger"
                },
                "variant_name": null,
                "quantity": 2,
                "unit_price_minor": 1200,
                "line_total_minor": 2400,
                "voided": false,
                "modifiers": [
                    {
                        "name": {
                            "en": "Cheese"
                        },
                        "price_minor": 100
                    }
                ]
            }
        ],
        "placed_at": "2026-09-29T10:15:00+00:00",
        "scheduled_for": null,
        "paid_at": null,
        "updated_at": "2026-09-29T10:15:00+00:00"
    },
    "meta": {},
    "links": {}
}
GET/orders/{order}

One order

الصلاحية: orders:read

المعاملات
الاسمالموضعالنوعمطلوب
orderpathstringمطلوب
مثال على الرد 200
{
    "data": {
        "id": "9d1c7a52-8f0e-4b8c-a1f5-3a1a5b1f0c2d",
        "status": "placed",
        "type": "delivery",
        "channel": "web",
        "branch_id": 1,
        "table": null,
        "customer": {
            "name": "Ada Lovelace",
            "phone": "+15550100",
            "email": "ada@example.com"
        },
        "note": "Ring twice",
        "delivery_address": {
            "line1": "12 Main St",
            "city": "Springfield"
        },
        "currency": "USD",
        "subtotal_minor": 2300,
        "discount_minor": 0,
        "service_charge_minor": 0,
        "tax_added_minor": 0,
        "delivery_fee_minor": 300,
        "total_minor": 2600,
        "tax_breakdown": null,
        "cancel_reason": null,
        "items": [
            {
                "id": 1,
                "item_id": 42,
                "name": {
                    "en": "Burger"
                },
                "variant_name": null,
                "quantity": 2,
                "unit_price_minor": 1200,
                "line_total_minor": 2400,
                "voided": false,
                "modifiers": [
                    {
                        "name": {
                            "en": "Cheese"
                        },
                        "price_minor": 100
                    }
                ]
            }
        ],
        "placed_at": "2026-09-29T10:15:00+00:00",
        "scheduled_for": null,
        "paid_at": null,
        "updated_at": "2026-09-29T10:15:00+00:00"
    },
    "meta": {},
    "links": {}
}
POST/orders/{order}/status

Move an order one step

placed to accepted or rejected, accepted to ready, ready to completed, and anything not finished to cancelled. Accepting fires the kitchen. A cancel with a reason (3 to 200 characters) also voids the kitchen tickets and keeps the reason.

الصلاحية: orders:write

المعاملات
الاسمالموضعالنوعمطلوب
orderpathstringمطلوب
Idempotency-Keyheaderstringاختياري
محتوى الطلب
{
    "status": "accepted"
}
مثال على الرد 200
{
    "data": {
        "id": "9d1c7a52-8f0e-4b8c-a1f5-3a1a5b1f0c2d",
        "status": "placed",
        "type": "delivery",
        "channel": "web",
        "branch_id": 1,
        "table": null,
        "customer": {
            "name": "Ada Lovelace",
            "phone": "+15550100",
            "email": "ada@example.com"
        },
        "note": "Ring twice",
        "delivery_address": {
            "line1": "12 Main St",
            "city": "Springfield"
        },
        "currency": "USD",
        "subtotal_minor": 2300,
        "discount_minor": 0,
        "service_charge_minor": 0,
        "tax_added_minor": 0,
        "delivery_fee_minor": 300,
        "total_minor": 2600,
        "tax_breakdown": null,
        "cancel_reason": null,
        "items": [
            {
                "id": 1,
                "item_id": 42,
                "name": {
                    "en": "Burger"
                },
                "variant_name": null,
                "quantity": 2,
                "unit_price_minor": 1200,
                "line_total_minor": 2400,
                "voided": false,
                "modifiers": [
                    {
                        "name": {
                            "en": "Cheese"
                        },
                        "price_minor": 100
                    }
                ]
            }
        ],
        "placed_at": "2026-09-29T10:15:00+00:00",
        "scheduled_for": null,
        "paid_at": null,
        "updated_at": "2026-09-29T10:15:00+00:00"
    },
    "meta": {},
    "links": {}
}

Reservations

5 نقطة نهاية

Bookings and table availability. Needs a plan with reservations.

GET/reservations

Bookings for a day or a range

الصلاحية: reservations:read

المعاملات
الاسمالموضعالنوعمطلوب
datequerystringاختياري
fromquerystringاختياري
toquerystringاختياري
statusquerystring: "pending", "confirmed", "declined", "cancelled", "seated", "no_show"اختياري
per_pagequeryintegerاختياري
cursorquerystringاختياري
مثال على الرد 200
{
    "data": [],
    "meta": {
        "per_page": 25,
        "next_cursor": null,
        "prev_cursor": null
    },
    "links": {
        "next": null,
        "prev": null
    }
}
POST/reservations

Write a booking down

The booking rules apply (party size, lead time, closing days); the guest gets the same confirmation as for a phone booking.

الصلاحية: reservations:write

المعاملات
الاسمالموضعالنوعمطلوب
Idempotency-Keyheaderstringاختياري
محتوى الطلب
{
    "name": "Ada Lovelace",
    "phone": "+15550100",
    "party_size": 2,
    "date": "2026-10-03",
    "time": "19:30"
}
مثال على الرد 201
{
    "data": {
        "id": "1b6e1c1e-2f4f-4c43-9e0e-4b9d6f1a2c3d",
        "status": "confirmed",
        "name": "Ada Lovelace",
        "phone": "+15550100",
        "email": null,
        "party_size": 2,
        "date": "2026-10-03",
        "time": "19:30",
        "table_id": null,
        "note": null,
        "deposit_minor": null,
        "deposit_status": null,
        "created_at": "2026-10-01T09:00:00+00:00",
        "updated_at": "2026-10-01T09:00:00+00:00"
    },
    "meta": {},
    "links": {}
}
GET/availability

Bookable times for a day and a party

الصلاحية: reservations:read

المعاملات
الاسمالموضعالنوعمطلوب
datequerystringمطلوب
party_sizequeryintegerمطلوب
مثال على الرد 200
{
    "data": {
        "date": "2026-10-03",
        "party_size": 2,
        "engine": true,
        "slots": [
            {
                "time": "19:00",
                "available": true,
                "guarantee": false
            },
            {
                "time": "19:30",
                "available": false,
                "guarantee": false
            }
        ],
        "guarantee": null,
        "notify": true
    },
    "meta": {},
    "links": {}
}
GET/reservations/{reservation}

One booking

الصلاحية: reservations:read

المعاملات
الاسمالموضعالنوعمطلوب
reservationpathstringمطلوب
مثال على الرد 200
{
    "data": {
        "id": "1b6e1c1e-2f4f-4c43-9e0e-4b9d6f1a2c3d",
        "status": "confirmed",
        "name": "Ada Lovelace",
        "phone": "+15550100",
        "email": null,
        "party_size": 2,
        "date": "2026-10-03",
        "time": "19:30",
        "table_id": null,
        "note": null,
        "deposit_minor": null,
        "deposit_status": null,
        "created_at": "2026-10-01T09:00:00+00:00",
        "updated_at": "2026-10-01T09:00:00+00:00"
    },
    "meta": {},
    "links": {}
}
POST/reservations/{reservation}/cancel

Cancel a booking

Cancelled by the restaurant; the guest is told.

الصلاحية: reservations:write

المعاملات
الاسمالموضعالنوعمطلوب
reservationpathstringمطلوب
Idempotency-Keyheaderstringاختياري
مثال على الرد 200
{
    "data": {
        "id": "1b6e1c1e-2f4f-4c43-9e0e-4b9d6f1a2c3d",
        "status": "confirmed",
        "name": "Ada Lovelace",
        "phone": "+15550100",
        "email": null,
        "party_size": 2,
        "date": "2026-10-03",
        "time": "19:30",
        "table_id": null,
        "note": null,
        "deposit_minor": null,
        "deposit_status": null,
        "created_at": "2026-10-01T09:00:00+00:00",
        "updated_at": "2026-10-01T09:00:00+00:00"
    },
    "meta": {},
    "links": {}
}

Guests

2 نقطة نهاية

Guest profiles, with contact details only for guests who agreed to share them.

GET/guests

Guest profiles, a page at a time

Guests who asked to be forgotten never appear. Phone and email are included only for guests who agreed to hear from the restaurant. Staff notes are never included.

الصلاحية: guests:read

المعاملات
الاسمالموضعالنوعمطلوب
marketing_opted_inquerystring: "true", "false", "1", "0"اختياري
updated_sincequerystringاختياري
per_pagequeryintegerاختياري
cursorquerystringاختياري
مثال على الرد 200
{
    "data": [
        {
            "id": 5,
            "name": "Ada Lovelace",
            "phone": "+15550100",
            "email": "ada@example.com",
            "contact_shared": true,
            "locale": "en",
            "marketing_opted_in": true,
            "marketing_opted_in_at": "2026-09-01T12:00:00+00:00",
            "visits_count": 4,
            "total_spend_minor": 9200,
            "last_visit_at": "2026-09-28T20:15:00+00:00",
            "created_at": "2026-06-02T19:40:00+00:00"
        }
    ],
    "meta": {
        "per_page": 25,
        "next_cursor": null,
        "prev_cursor": null
    },
    "links": {
        "next": null,
        "prev": null
    }
}
GET/guests/{guest}

One guest profile

الصلاحية: guests:read

المعاملات
الاسمالموضعالنوعمطلوب
guestpathintegerمطلوب
مثال على الرد 200
{
    "data": {
        "id": 6,
        "name": "Alan Turing",
        "phone": null,
        "email": null,
        "contact_shared": false,
        "locale": null,
        "marketing_opted_in": false,
        "marketing_opted_in_at": null,
        "visits_count": 1,
        "total_spend_minor": 2400,
        "last_visit_at": null,
        "created_at": "2026-09-30T11:00:00+00:00"
    },
    "meta": {},
    "links": {}
}

Reports

1 نقطة نهاية

Sales figures. Needs a plan with reporting.

GET/reports/sales-summary

Revenue, orders and average order for a period

With the preceding period of the same length. Defaults to today on the restaurant's clock; at most 366 days.

الصلاحية: reports:read

المعاملات
الاسمالموضعالنوعمطلوب
fromquerystringاختياري
toquerystringاختياري
branch_idqueryintegerاختياري
مثال على الرد 200
{
    "data": {
        "from": "2026-09-01",
        "to": "2026-09-30",
        "branch_id": null,
        "currency": "EUR",
        "orders": 812,
        "revenue_minor": 1843200,
        "average_order_minor": 2270,
        "previous": {
            "orders": 760,
            "revenue_minor": 1690000,
            "average_order_minor": 2223
        },
        "change": {
            "revenue_pct": 9.1,
            "orders_pct": 6.8
        }
    },
    "meta": {},
    "links": {}
}

OAuth

2 نقطة نهاية

Apps from the marketplace: a venue owner allows an app on the consent screen, and the app exchanges the code for an access token (used as the bearer token of every operation above) and a refresh token. Authorization code with PKCE (S256).

POST/oauth/token

Exchange a code, or refresh, for tokens

authorization_code: redeem the code the venue's consent sent to your redirect address, with the PKCE verifier. refresh_token: get a new access token and a new refresh token; the old pair stops working. Authenticate with your client id and secret, as HTTP Basic or as client_id and client_secret in the body. Answers are never cached.

محتوى الطلب
{
    "grant_type": "authorization_code",
    "code": "bnd_ac_9fK2...",
    "redirect_uri": "https://app.example.com/callback",
    "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
    "client_id": "bnd_app_4kq8...",
    "client_secret": "bnd_cs_..."
}
مثال على الرد 200
{
    "access_token": "812|bnd_oat_Xf3k...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "refresh_token": "bnd_ort_q8Lz...",
    "scope": "menu:read orders:read"
}
POST/oauth/revoke

Revoke a token

Gives back a refresh token (with its access token) or an access token (RFC 7009). An unknown token is not an error. Authenticate with your client id and secret, as HTTP Basic or as client_id and client_secret in the body. To end the whole grant, the venue uninstalls the app.

محتوى الطلب
{
    "token": "bnd_ort_q8Lz...",
    "client_id": "bnd_app_4kq8...",
    "client_secret": "bnd_cs_..."
}
مثال على الرد 200
{}

خطافات الويب

أضف عنوان https في الإعدادات ثم المطورون، واختر أحداثه. كل عملية إرسال هي طلب POST موقّع: أجب بأي رمز 2xx خلال 10 ثوانٍ. تُعاد محاولة الإرسال الفاشل بعد دقيقة واحدة، ثم 5 دقائق، ثم 30 دقيقة، ثم ساعتين، ثم 6 ساعات، ويمكن لسجل الإرسال إعادة تشغيله.

الأحداث

order.created
يُنشأ طلب عبر أي قناة.
order.status_changed
تنتقل حالة الطلب إلى حالة أخرى.
order.paid
المدفوعات تغطي الطلب.
reservation.created
يتم إجراء حجز، من الضيف أو من الموظفين.
reservation.updated
يتغير حجز.
reservation.cancelled
يُلغى حجز.
menu.item_updated
يتغير طبق: السعر أو الاسم أو نفاد الكمية.
payment.refunded
يُسترد مبلغ دفعة.
guest.created
يُنشأ ملف ضيف.
guest.updated
تتغير بيانات الضيف أو موافقته، أو يُمسح الضيف.
giftcard.issued
تُباع بطاقة هدية أو تُصدر.
giftcard.redeemed
تدفع بطاقة هدية ثمن طلب.
review.created
يرسل ضيف تقييمًا.
stock.below_par
ينخفض مكوّن عن مستواه المرجعي في أحد الفروع.
shift.published
ينشر مدير جدول أسبوع من المناوبات.
experience.booked
تُدفع تذاكر تجربة.

التحقق من التوقيع

يحمل BookNDish-Signature القيمة t وهي وقت Unix، والقيمة v1 وهي HMAC-SHA256 بالصيغة السداسية عشرية للقيمة t ثم نقطة ثم المحتوى الخام، بمفتاح سر التوقيع الخاص بك. قارن في زمن ثابت ولا تقبل قيمة t أقدم من خمس دقائق.

function bookndishSignatureIsValid(string $payload, string $header, string $secret, int $tolerance = 300): bool
{
    $parts = [];
    foreach (explode(',', $header) as $pair) {
        [$key, $value] = array_pad(explode('=', trim($pair), 2), 2, '');
        $parts[$key][] = $value;
    }

    $timestamp = (int) ($parts['t'][0] ?? 0);
    if ($timestamp === 0 || abs(time() - $timestamp) > $tolerance) {
        return false;
    }

    $expected = hash_hmac('sha256', $timestamp.'.'.$payload, $secret);
    foreach ($parts['v1'] ?? [] as $signature) {
        if (hash_equals($expected, $signature)) {
            return true;
        }
    }

    return false;
}

البيئة التجريبية

البيئة التجريبية هي مطعم نموذجي مستقل، يضم قائمة وطاولات وضيوفًا وحجزًا وطلبًا، خلف رمز واحد بكل الصلاحيات. تستجيب على العنوان نفسه مع ترويسة BookNDish-Sandbox، ولا تمس المطعم الفعلي أبدًا، وتعود إلى بياناتها النموذجية كل ليلة.

أنشئ واحدة من الإعدادات ثم المطورون ثم البيئة التجريبية. يبدأ رمزها بـ bnd_sbx_.

الحدود والأخطاء

  • يمكن لكل رمز إرسال 60 طلبًا في الدقيقة افتراضيًا. يحمل كل رد X-RateLimit-Remaining، ويحمل الرد 429 الترويسة Retry-After.
  • يعيد الفشل رداً يحتوي على error.code وerror.message وerror.details. لا يرى الرمز إلا مطعمه فقط: وأي شيء آخر يعطي 404.
  • v1 تنمو فقط: نقاط نهاية جديدة وحقول اختيارية وأحداث. أي تغيير قد يكسر عميلًا يوضع في v2، مع إبقاء v1 إلى جانبها.