Quotations and Proforma API
Create, list, view, update, convert and delete quotations, and read the proforma invoices created with them.
Quotations are created from Billing in quotation mode (menu Quotations > Quotations, buttons Create Quotation and Create & Print Quote). They are listed in Quotations > Quotation List, where staff can view, print and convert to order. Every quotation also creates a linked proforma invoice, listed under Transactions > Proforma Invoice.
All paths are relative to {{server}}. Send X-Tenant: <your-tenant-key>, Authorization: Bearer <access-token> and Content-Type: application/json. All quotation routes require sign-in.
Endpoint summary
Method | Path | Used by |
|---|---|---|
POST |
| Create Quotation / Create & Print Quote |
GET |
| Quotation List |
GET |
| Quotation details (view, print) |
POST |
| Change status from the list |
POST |
| Convert to order |
DELETE |
| Delete (admin panel / integrations; needs |
GET |
| Proforma Invoices list |
GET |
| Proforma invoice details |
Statuses
Quotation status | Meaning |
|---|---|
| New quotation (default) |
| Customer accepted |
| Customer declined; cannot be converted |
| Converted to an order; status can no longer change |
Proforma status | Meaning |
|---|---|
| Created with the quotation |
| Paid |
| Past due date |
| Its quotation was converted |
The demo quotation
QTN-00133 for Layla Al-Harbi: 5 × family chicken bucket at 210.00 = 1050.00 (sub total 889.83, tax 160.17 at 18% inclusive), delivery Store Takeaway, terms "Valid for 7 days. Catering order for 5 family buckets.", status Pending, created 2026-10-27 and expiring 2026-11-26 (30 days later).
POST /api/v1/quotations
Creates a quotation from the current cart.
Body
Field | Required | Notes |
|---|---|---|
| no |
|
| for existing | Customer id |
| for new | Inline customer; no customer record is created yet |
| yes | Active store |
| no | Customer address |
| no | Delivery method |
| no | Sent only when the delivery charge is above 0 |
| no |
|
| no |
|
| no | Sent only when above 0 |
| no | Up to 500 characters |
| no | Note (sent by the app) |
| yes | At least one line |
| yes | Product |
| yes | ≥ 0.001 (sale-unit quantity when a pack/box unit is used) |
| no | Unit price (tax-inclusive) |
| no | Stock batch |
| no | Sale unit (pack/box) |
| no | Variant |
| no | Boolean |
{
"customer_type": "existing",
"customer_id": 2767,
"store_id": 2,
"delivery_method_id": "<store-takeaway-id>",
"quotation_date": "2026-10-27",
"expiry_date": "2026-11-26",
"comment": "",
"terms": "Valid for 7 days. Catering order for 5 family buckets.",
"items": [
{ "product_id": "<family-bucket-product-id>", "quantity": 5, "price": 210.0 }
]
}Response
{
"status": "success",
"message": "Quotation created",
"data": {
"quotation_id": 190,
"quotation_number": "QTN-00133",
"grand_total": "1050.000",
"terms": "Valid for 7 days. Catering order for 5 family buckets.",
"invoice_id": 6815
}
}*Ids are illustrative.*
Side effects
- The quotation number is
QTN-plus a 5-digit sequence. - A proforma invoice (type
proforma, statuspending) is created with the same total, tax, discount, invoice date = quotation date and due date = expiry date. Its lines are the quotation lines plus a "Delivery Charge" line when there is a shipping cost. - No stock is reserved and no ledger entry is made.
GET /api/v1/quotations
Paginated list, 15 per page.
Name | Type | Notes |
|---|---|---|
| string | Quotation # |
| integer | Customer |
| integer | Store (the app sends the active store) |
| string |
|
| date | The app sends the same day for both (exact-day filter) |
| date | Same, for the expiry date |
| integer | Page |
{
"status": "success",
"message": "List of quotations",
"data": {
"current_page": 1,
"data": [
{
"id": 190,
"quotation_number": "QTN-00133",
"customer_id": 2767,
"customer": "Layla Al-Harbi",
"customer_phone": "0551234567",
"address": null,
"kyc_info": { "cr_number": null, "vat_number": null },
"store": "متجر النجمة Store ",
"quotation_date": "2026-10-27 00:00:00",
"expiry_date": "2026-11-26 00:00:00",
"terms": "Valid for 7 days. Catering order for 5 family buckets.",
"sub_total": "889.830",
"discount": null,
"tax": "160.170",
"grand_total": "1050.000",
"status": "Pending",
"invoice_id": 6815,
"created_at": "2026-10-27T12:30:00.000000Z"
}
],
"per_page": 15
}
}*Shape from a captured response; the row is the demo quotation.*
GET /api/v1/quotations/{id}
Full quotation with lines. Used by View and Print (the print uses the "Quotation" document configuration and the Quotation printer).
{
"status": "success",
"message": "Quotation details",
"data": {
"id": 190,
"quotation_number": "QTN-00133",
"status": "Pending",
"customer": { "id": 2767, "name": "Layla Al-Harbi", "phone": "0551234567", "is_inline": false },
"kyc_info": { "cr_number": null, "vat_number": null },
"store": { "id": 2, "name": "متجر النجمة Store " },
"address_id": null,
"address": null,
"address_details": null,
"quotation_date": "2026-10-27 00:00:00",
"expiry_date": "2026-11-26 00:00:00",
"terms": "Valid for 7 days. Catering order for 5 family buckets.",
"delivery_method_id": "<store-takeaway-id>",
"delivery_method_name": "Store Takeaway",
"shipping_cost": null,
"sub_total": "889.830",
"discount": null,
"tax": "160.170",
"grand_total": "1050.000",
"invoice_id": 6815,
"items": [
{
"id": 15201,
"product_id": "<family-bucket-product-id>",
"product_name": "family chicken bucket",
"unit": "PCS",
"unit_price": "210.000",
"quantity": "5.000",
"tax_rate": "18.000",
"tax_amount_unit": 32.034,
"tax_amount": "160.170",
"total_price": "1050.000",
"product_stock_id": null,
"product_sale_unit_id": null,
"sale_unit_name": null,
"sale_unit_conversion_rate": null,
"warranty_enabled": false
}
]
}
}*Shape from a captured response; values are the demo quotation (ids and unit illustrative).* is_inline: true means the customer was typed in (new customer) and has no record yet.
POST /api/v1/quotations/update-status
{ "quotation_id": 190, "status": "Confirmed" }Field | Required | Values |
|---|---|---|
| yes | Quotation id |
| yes |
|
Response: {"status":"success","message":"Quotation status updated","data":{...quotation}}. A converted quotation returns "Quotation already converted to an order".
POST /api/v1/quotations/convert-to-order
Turns the quotation into an order (status new) using the quotation's lines and prices.
Field | Required | Notes |
|---|---|---|
| yes | Quotation |
| no |
|
| no | Use an existing customer |
| no | For an inline customer: the customer record is found or created by phone |
| no | Payment method |
| no | Default |
| no | Default: the quotation's discount |
| no | Default 0 |
| no | Delivery method |
| no | Default |
{
"quotation_id": 190,
"customer_type": "existing",
"customer_id": 2767,
"payment_method": "CASH",
"delivery_method_id": "<store-takeaway-id>"
}{
"status": "success",
"message": "Quotation converted to order",
"data": {
"order_id": 7050,
"order_number": "ORD-004860",
"quotation_id": 190,
"quotation_number": "QTN-00133"
}
}*Illustrative ids.*
Rules:
- Already converted →
"Quotation already converted to an order". - Cancelled →
"Cancelled quotations cannot be converted". - No lines →
"Quotation cart has no items". - Inline customer without a name →
"customer_name is required to create a new customer". - On success the quotation and its proforma invoice both get status
order created.
DELETE /api/v1/quotations/{id}
Deletes a quotation. The signed-in user's role needs the quotation.delete permission; no seeded role has it by default.
Status | Body |
|---|---|
200 |
|
401 |
|
403 |
|
404 |
|
The POS app has no delete button for quotations.
GET /api/v1/invoice/proforma-invoices
Used by: Transactions > Proforma Invoice (search by invoice number, name or phone; status Pending / Paid / Overdue / Order Created).
Name | Type | Notes |
|---|---|---|
| string | Partial match |
| string | Customer name or phone |
| integer | One customer |
| string |
|
| date | Invoice date |
| date | Due date |
| integer | App sends 20 (1000 for export) |
| integer | Page |
{
"status": "success",
"data": {
"current_page": 1,
"data": [
{
"id": 6815,
"invoice_number": "<proforma-number>",
"type": "proforma",
"status": "pending",
"amount": "1050.000",
"total_tax": "160.170",
"total_discount": "0.000",
"invoice_date": "2026-10-27",
"due_date": "2026-11-26",
"customer": { "id": 2767, "name": "Layla Al-Harbi", "phone": "0551234567", "is_inline": false },
"quotation": { "id": 190, "quotation_number": "QTN-00133", "status": "Pending" },
"items_count": 1
}
],
"per_page": 20
}
}*Illustrative row built from the server's field list.*
GET /api/v1/invoice/proforma-invoices/{id}
Same header fields as the list plus items:
{
"status": "success",
"data": {
"id": 6815,
"invoice_number": "<proforma-number>",
"type": "proforma",
"status": "pending",
"amount": "1050.000",
"total_tax": "160.170",
"invoice_date": "2026-10-27",
"due_date": "2026-11-26",
"customer": { "id": 2767, "name": "Layla Al-Harbi", "phone": "0551234567", "is_inline": false },
"items": [
{ "id": 9001, "item_name": "family chicken bucket", "quantity": "5.000", "unit_amount": "177.966", "tax": 160.17, "tax_rate": 18, "tax_amount": 160.17, "total_amount": "1050.000" }
]
}
}*Illustrative.* Not found → "Proforma invoice not found" in the app.