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.

Response

{
  "data": {
    "id": "uuid",
    "order_number": "ORD-001",
    "status": "confirmed",
    "source": "api",
    "address": "123 Main St, Riyadh",
    "customer": { "id": "uuid", "first_name": "John", "last_name": "Doe", "email": "john@example.com", "phone": "+966500000001" },
    "branch": { "id": "uuid", "name": "Main Branch" },
    "items": [
      { "id": "uuid", "name": "Haircut", "type": "service", "price": 100.00, "quantity": 1, "discount_amount": 0, "total": 100.00 }
    ],
    "transactions": [
      { "id": "uuid", "amount": "100.00", "status": "confirmed", "date": "2026-02-25T10:30:00.000000Z" }
    ],
    "currency": "SAR",
    "total": 150.00,
    "discount_amount": 0,
    "tax_amount": 7.50,
    "pin_code": "4821",
    "created_at": "2026-02-25T10:00:00.000000Z",
    "updated_at": "2026-02-25T10:00:00.000000Z"
  }
}
FieldTypeRequiredDescription
pin_codestring | nullNoOrder PIN code (e.g. 4-digit). Set when the tenant has the "Require PIN code to close order" setting enabled, used by staff to confirm completion. null otherwise.
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.

GET/staff/{uuid}/working-hourstaff:read

Get the staff member's default working hour with intervals. Falls back to the team (branch) working hour when the staff has no custom one. Returns 404 only when neither has a working hour configured.

Response

{
  "data": {
    "id": "uuid",
    "source": "staff",
    "all_time": false,
    "active": true,
    "intervals": [
      { "id": "uuid", "day": "sun", "from": "09:00", "to": "17:00", "active": true }
    ]
  }
}
FieldTypeRequiredDescription
sourcestringNostaff if the staff has a custom working hour, branch if falling back to the branch (team) working hour
intervals[].daystringNoLowercase three-letter day code (sun, mon, tue, wed, thu, fri, sat)
intervals[].fromstringNoStart time in HH:mm (tenant timezone)
intervals[].tostringNoEnd time in HH:mm (tenant timezone)

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
  }
}