Back to Overview

API Reference

Detailed request and response examples for every endpoint.

https://api.mahjoz.io/api/open/v1

Orders

GET/ordersorders:read

List orders with pagination and optional filters.

FieldTypeRequiredDescription
pageintegerNoPage number
per_pageintegerNoItems per page (max 100)
fromdateNoFilter from date (YYYY-MM-DD)
todateNoFilter to date (YYYY-MM-DD)
statusstringNoFilter by status

Response

{
  "data": [
    {
      "id": "uuid",
      "order_number": "ORD-001",
      "status": "confirmed",
      "source": "api",
      "customer": { "id": "uuid", "first_name": "...", "last_name": "...", "phone": "..." },
      "branch": { "id": "uuid", "name": "Main Branch" },
      "items": [
        {
          "id": "uuid",
          "name": "Haircut",
          "type": "service",
          "price": 100.00,
          "quantity": 1,
          "discount_amount": 0,
          "total": 100.00
        }
      ],
      "currency": "SAR",
      "total": 150.00,
      "created_at": "2026-02-25T10:00:00.000000Z"
    }
  ]
}
GET/orders/{uuid}orders:read

Get order detail with items, transactions, and full customer/branch details. Each item includes staff_id.

POST/ordersorders:write

Create a new order. If the order includes service items, the API validates the requested time slot before creating it.

Request

{
  "team_id": "branch-uuid",
  "customer_id": "customer-uuid",
  "start": "2026-02-25T10:00:00",
  "items": [
    {
      "id": "service-uuid",
      "type": "service",
      "staff_id": "staff-uuid",
      "quantity": 1,
      "unit_amount": 100.00,
      "discount_amount": 0,
      "option_id": "option-uuid"
    },
    {
      "id": "product-uuid",
      "type": "product",
      "quantity": 2,
      "unit_amount": 25.00
    }
  ],
  "note": "Optional note",
  "latitude": 24.7136,
  "longitude": 46.6753
}
FieldTypeRequiredDescription
team_iduuidYesBranch UUID
customer_iduuidYesCustomer UUID
startdatetimeYesRequired if items contain a service. ISO 8601.
itemsarrayYesAt least 1 item
items.*.iduuidYesService/Product/Package UUID
items.*.typestringYesservice, product, or package
items.*.quantityintegerYesMinimum 1
items.*.staff_iduuidNoStaff or staff label UUID (validated by assignment type)
items.*.unit_amountnumberNoOverride unit price
items.*.discount_amountnumberNoDiscount per item
items.*.option_iduuidNoService option UUID
notestringNoOrder note
latitudenumberNoOrder location latitude
longitudenumberNoOrder location longitude
Staff Assignment: staff_id is validated based on the service assignment type: single_provider expects a staff UUID, team expects a staff label UUID, multiple_providers does not require it.

Response (201)

{
  "data": {
    "id": "uuid",
    "order_number": "ORD-042",
    "status": "confirmed",
    "currency": "SAR",
    "total": 150.00,
    "created_at": "2026-02-25T10:30:00.000000Z"
  }
}
POST/orders/{uuid}/statusorders:write

Change an order's status. Dispatches the order.status_changed webhook. Responds with the full order detail.

Request

{
  "status": "completed",
  "note_for_status": "finished on-site"
}
FieldTypeRequiredDescription
statusstringYesOne of: confirmed, in-progress, completed, canceled
note_for_statusstringNoFree-text reason or note attached to the transition
Rules: If status equals the order's current status, the call is a no-op. 422 order_has_invoice if you try to cancel an order that already has an invoice. 422 invalid_status_transition if you try to cancel an order that has refunded children. Canceling restores item stock automatically.

Availability

POST/availabilityorders:write

Check available time slots for a service.

Request

{
  "service_id": "service-uuid",
  "date": "2026-02-25",
  "staff_id": "staff-uuid",
  "quantity": 1,
  "visible_days": 7,
  "next_availability": false
}
FieldTypeRequiredDescription
service_iduuidYesService to check
datedateYesStart date (YYYY-MM-DD)
staff_iduuidNoSpecific staff member
quantityintegerNoDefault 1
visible_daysintegerNoDays to check (1-30, default 7)
next_availabilitybooleanNoFind next available slot if none on given date

Response

{
  "data": {
    "slots": {
      "2026-02-25": ["09:00", "09:30", "10:00", "10:30"],
      "2026-02-26": ["09:00", "11:00", "14:00"]
    }
  }
}

Customers

GET/customerscustomers:read

List customers with pagination and optional filters.

FieldTypeRequiredDescription
pageintegerNoPage number
per_pageintegerNoItems per page (max 100)
namestringNoFilter by name
phonestringNoFilter by phone
emailstringNoFilter by email
GET/customers/{uuid}customers:read

Get customer detail with address, note, blocked status, tax_registration_number, and tags.

GET/customers/phonecustomers:read

Find a customer by exact phone number. Returns the customer or null.

FieldTypeRequiredDescription
phonestringYesExact phone number (URL-encode + as %2B)
POST/customerscustomers:write

Create a new customer.

Request

{
  "first_name": "John",
  "last_name": "Doe",
  "email": "john@example.com",
  "phone": "+966500000001",
  "phone_country": "SA",
  "type": "individual",
  "city": "Riyadh",
  "address": "123 Main St",
  "note": "VIP customer",
  "tax_registration_number": "300000000000003"
}
FieldTypeRequiredDescription
first_namestringYesMin 2 chars
last_namestringNoMin 2 chars
emailstringNoValid email
phonestringNoUnique per tenant
phone_countrystringNoRequired with phone, 2-char code (e.g. SA)
typestringNoindividual or company (default: individual)
citystringNoMin 3 chars
addressstringNoMin 3 chars
notestringNoFree text
tax_registration_numberstringNo15 digits

Response (201)

{
  "data": {
    "id": "uuid",
    "first_name": "John",
    "last_name": "Doe",
    "email": "john@example.com",
    "phone": "+966500000001",
    "type": "individual",
    "city": "Riyadh",
    "created_at": "2026-02-25T10:30:00.000000Z"
  }
}

Items (Services & Products)

GET/itemsitems:read

List items (services and products) with pagination. Services include duration; products include cost and stock. Each item includes options array with variants.

FieldTypeRequiredDescription
pageintegerNoPage number
per_pageintegerNoItems per page (max 100)
typestringNoservice or product
namestringNoFilter by name

Response

{
  "data": [
    {
      "id": "uuid",
      "type": "service",
      "sku": "SRV-001",
      "name": { "ar": "ู‚ุต ุดุนุฑ", "en": "Haircut" },
      "slug": "haircut",
      "status": true,
      "price": 100.00,
      "offer_price": 80.00,
      "currency": "SAR",
      "duration": 30,
      "options": [
        {
          "id": "uuid",
          "name": "Red Small",
          "price": 120.00,
          "values": [
            { "option_value_id": "uuid", "option_name": "Color", "value": "Red" },
            { "option_value_id": "uuid", "option_name": "Size", "value": "Small" }
          ]
        }
      ],
      "category": { "id": "uuid", "name": { "ar": "ุงู„ุดุนุฑ", "en": "Hair" } },
      "branch": { "id": "uuid", "name": "Main Branch" }
    }
  ]
}
GET/items/{uuid}items:read

Get item detail. For services, includes staff array with id, name, price, and duration.

Staff (Providers)

GET/staffstaff:read

List staff members with their assigned services.

FieldTypeRequiredDescription
pageintegerNoPage number
per_pageintegerNoItems per page (max 100)
namestringNoFilter by name
branch_iduuidNoFilter by branch UUID

Response

{
  "data": [
    {
      "id": "uuid",
      "name": "Ahmed Ali",
      "phone": "+966500000001",
      "email": "ahmed@example.com",
      "image": "https://...",
      "branch": { "id": "uuid", "name": "Main Branch" },
      "services": [
        { "id": "uuid", "name": "Haircut", "price": 100.00, "duration": 30 }
      ]
    }
  ]
}
GET/staff/{uuid}staff:read

Get staff member detail with branch and full services list.

Branches

GET/branchesbranches:read

List all active branches for the tenant.

FieldTypeRequiredDescription
pageintegerNoPage number
per_pageintegerNoItems per page (max 100)

Response

{
  "data": [
    {
      "id": "uuid",
      "name": "Main Branch",
      "slug": "main-branch",
      "phone": "+966500000001",
      "address": "123 Main St, Riyadh",
      "latitude": 24.7136,
      "longitude": 46.6753,
      "location": "in-store",
      "status": true
    }
  ]
}

Categories

GET/categoriescategories:read

List categories ordered by sort_order.

FieldTypeRequiredDescription
pageintegerNoPage number
per_pageintegerNoItems per page (max 100)
branch_iduuidNoFilter by branch UUID

Response

{
  "data": [
    {
      "id": "uuid",
      "name": { "ar": "ุฎุฏู…ุงุช ุงู„ุดุนุฑ", "en": "Hair Services" },
      "slug": "hair-services",
      "description": { "ar": "...", "en": "..." },
      "status": true,
      "sort_order": 1,
      "branch": { "id": "uuid", "name": "Main Branch" }
    }
  ]
}

Payment Methods

Only payment methods of type cash and other are exposed. Gateway-backed methods (card, wallets, bank transfer) are managed from the Mahjoz dashboard.

GET/payment-methodspayment_methods:read

List the tenant's manual payment methods (cash and other types only).

FieldTypeRequiredDescription
pageintegerNoPage number
per_pageintegerNoItems per page (max 100)

Response

{
  "data": [
    {
      "id": "uuid",
      "name": { "ar": "ู†ู‚ุฏูŠ", "en": "Cash" },
      "slug": "on_arrival",
      "status": true,
      "type": { "id": 1, "slug": "cash", "name": "Cash" },
      "created_at": "2026-04-10T09:00:00.000000Z",
      "updated_at": "2026-04-10T09:00:00.000000Z"
    }
  ]
}
GET/payment-methods/{uuid}payment_methods:read

Get a single payment method by UUID. Returns 404 if the method's type is not cash or other.

POST/payment-methodspayment_methods:write

Create a manual payment method restricted to cash or other types.

Request

{
  "name": "Bank Transfer - Al Rajhi",
  "type": "other",
  "status": true
}
FieldTypeRequiredDescription
namestringYesDisplay name (max 255 chars)
typestringYescash or other
statusbooleanNoEnabled (default true)

Response (201)

{
  "data": {
    "id": "uuid",
    "name": { "en": "Bank Transfer - Al Rajhi" },
    "slug": null,
    "status": true,
    "type": { "id": 4, "slug": "other", "name": "Other" },
    "created_at": "2026-04-15T18:00:00.000000Z",
    "updated_at": "2026-04-15T18:00:00.000000Z"
  }
}

Transactions

GET/transactionstransactions:read

List transactions for the tenant with optional filters.

FieldTypeRequiredDescription
pageintegerNoPage number
per_pageintegerNoItems per page (max 100)
statusstringNoFilter by transaction status (e.g. confirmed, pending)
order_iduuidNoFilter by order UUID
booking_iduuidNoFilter by booking UUID
fromdateNoFilter from date (YYYY-MM-DD)
todateNoFilter to date (YYYY-MM-DD)

Response

{
  "data": [
    {
      "id": "uuid",
      "transaction_no": "TRX-0042",
      "amount": "100.00",
      "currency": "SAR",
      "status": "confirmed",
      "is_deposit": false,
      "note": "Paid in full",
      "date": "2026-04-15T18:00:00.000000Z",
      "payment_method": { "id": "uuid", "name": "Cash", "slug": "on_arrival" },
      "order": { "id": "uuid", "order_number": "ORD-001" },
      "booking": null,
      "created_at": "2026-04-15T18:00:00.000000Z",
      "updated_at": "2026-04-15T18:00:00.000000Z"
    }
  ]
}
GET/transactions/{uuid}transactions:read

Get a single transaction by UUID with its payment method, order, and booking relations.

POST/orders/{order}/paymentstransactions:write

Record a payment against an order. The amount cannot exceed the order's remaining unpaid balance, and the order must not be in draft status.

Request

{
  "amount": 100.00,
  "payment_method_id": "payment-method-uuid",
  "note": "Paid in cash on delivery",
  "reference_number": "REF-12345",
  "date": "2026-04-15T18:00:00"
}
FieldTypeRequiredDescription
amountnumberYesPayment amount (0.01 to remaining unpaid)
payment_method_iduuidYesUUID of a payment method enabled for this tenant
notestringNoFree-text note
reference_numberstringNoExternal reference (e.g. bank transfer id)
datedatetimeNoPayment date (defaults to now)

Response (201)

{
  "data": {
    "id": "uuid",
    "transaction_no": "TRX-0043",
    "amount": "100.00",
    "currency": "SAR",
    "status": "confirmed",
    "is_deposit": false,
    "note": "Paid in cash on delivery",
    "date": "2026-04-15T18:00:00.000000Z",
    "payment_method": { "id": "uuid", "name": "Cash", "slug": "on_arrival" },
    "order": { "id": "uuid", "order_number": "ORD-001" },
    "created_at": "2026-04-15T18:00:00.000000Z",
    "updated_at": "2026-04-15T18:00:00.000000Z"
  }
}
Errors: 422 invalid_order_status if the order is in draft; 422 order_fully_paid if the remaining unpaid amount is already 0.

Webhooks

Webhooks allow your application to receive real-time notifications when order events occur.

Maximum 3 webhooks per tenant. All payloads are signed with HMAC-SHA256.

Available events:

order.createdorder.updatedorder.deletedorder.status_changedorder.invoice_createdorder.payment_accepted
POST/webhookswebhooks:write

Register a new webhook endpoint. The secret is only returned once at creation.

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint to receive payloads
eventsstring[]YesEvent names to subscribe to

Response

{
  "data": {
    "id": "uuid",
    "url": "https://your-app.com/webhooks/mahjoz",
    "events": ["order.created", "order.status_changed"],
    "is_active": true,
    "secret": "a1b2c3...64_char_secret",
    "last_triggered_at": null,
    "created_at": "2026-04-13T19:00:00.000000Z",
    "updated_at": "2026-04-13T19:00:00.000000Z"
  }
}
GET/webhookswebhooks:read

List all webhooks registered by the authenticated client.

Response

{
  "data": [
    {
      "id": "uuid",
      "url": "https://your-app.com/webhooks/mahjoz",
      "events": ["order.created", "order.status_changed"],
      "is_active": true,
      "last_triggered_at": "2026-04-13T20:30:00.000000Z",
      "created_at": "2026-04-13T19:00:00.000000Z",
      "updated_at": "2026-04-13T19:00:00.000000Z"
    }
  ]
}
GET/webhooks/{webhook_id}webhooks:read

Get a specific webhook by ID.

Response

{
  "data": {
    "id": "uuid",
    "url": "https://your-app.com/webhooks/mahjoz",
    "events": ["order.created", "order.status_changed"],
    "is_active": true,
    "last_triggered_at": "2026-04-13T20:30:00.000000Z",
    "created_at": "2026-04-13T19:00:00.000000Z",
    "updated_at": "2026-04-13T19:00:00.000000Z"
  }
}
PUT/webhooks/{webhook_id}webhooks:write

Update a webhook's URL, events, or active status.

FieldTypeRequiredDescription
urlstringNoNew HTTPS endpoint
eventsstring[]NoNew event subscriptions
is_activebooleanNoEnable or disable the webhook

Response

{
  "data": {
    "id": "uuid",
    "url": "https://your-app.com/webhooks/mahjoz",
    "events": ["order.created", "order.updated"],
    "is_active": false,
    "last_triggered_at": "2026-04-13T20:30:00.000000Z",
    "created_at": "2026-04-13T19:00:00.000000Z",
    "updated_at": "2026-04-13T19:15:00.000000Z"
  }
}
DELETE/webhooks/{webhook_id}webhooks:write

Delete a webhook. Returns 204 No Content.

Response

204 No Content

Payload Format

All webhook deliveries include a Signature header (HMAC-SHA256 of the body using your secret) and an X-Mahjoz-Event header with the event name.

Response

{
  "event": "order.created",
  "webhook_id": "uuid",
  "timestamp": "2026-04-13T19:30:00+03:00",
  "data": {
    "id": "order-uuid",
    "order_number": "ORD-001",
    "status": "confirmed",
    "customer": { "id": "uuid", "first_name": "...", "last_name": "..." },
    "branch": { "id": "uuid", "name": "Main Branch" },
    "items": [ ... ],
    "transactions": [ ... ],
    "currency": "SAR",
    "total": 50.00,
    "discount_amount": 0,
    "tax_amount": 7.50
  }
}