Home Integration API reference

Integration API reference

REST API for integrations and external apps

Sales

List sales

Lists finalized sales visible to the authenticated user across permitted locations. This is the same data source used by the web All Sales screen.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/sells
AuthenticationBearer token or API key + secret required.
PermissionAdmin access or any visibility path accepted by userCanListSells, including sell view, direct-sell, own-sell, commission-agent, shipment, or sales-order view permissions.
Row scopeOnly type = sell, status = final rows are returned. sub_type = project_invoice is excluded.
Visibility rulesPermitted-location scope applies. When the user lacks direct_sell.view, the list is restricted to their own sales and/or commission-agent sales based on their permissions. Non-admin users can also be narrowed by payment-status-only permissions.
CSV behaviorformat=csv streams all matching rows and ignores pagination. Nested contact and location objects are JSON-encoded in cells.
Success response200 with paginated SellListRow rows.

Query parameters

ParameterTypeRequiredDescription
per_pageintegerNoResults per page from 1 to 100. Defaults to 20.
pageintegerNoPagination page number.
location_idintegerNoOptional business location filter, still limited by permitted locations.
contact_idintegerNoOptional customer filter.
created_byintegerNoOptional creator user id filter.
start_date, end_datestringNoOptional transaction date range in Y-m-d. The filter is applied only when both are present.
payment_statusstringNopaid, due, partial, or overdue. The overdue filter uses pay-term calculations.
only_shipmentsboolean-like stringNoWhen true, only rows with a non-null shipping_status are returned. Users with access_pending_shipments_only will not see delivered rows.
is_direct_saleintegerNo1 for direct sales only; 0 for POS sales only, which also forces sub_type to null.
qstringNoMinimum 2 characters when sent. Matches ref_no, invoice_no, numeric transaction id, or linked contact name, business name, mobile, or contact code.
formatstringNojson (default) or csv.

SellListRow object

FieldTypeDescription
idintegerSale transaction id.
invoice_nostring | nullInvoice number.
transaction_datestring | nullISO-8601 transaction timestamp.
status, payment_statusstring | nullFinalized transaction status and current payment status.
final_total, total_before_tax, tax_amount, discount_amount, total_paidnumber | nullHeader totals for the sale.
is_direct_salebooleanWhether the row is a direct sale.
sub_typestring | nullSale sub-type when present.
consumption_typestringOutbound consumption classification for the sale: sales, damages, or sampling (defaults to sales when unset on older rows).
contactobject | nullContact summary with id, name, mobile, contact_id, and supplier_business_name.
locationobject | nullLocation summary with id and name.

Top-level JSON response

FieldTypeDescription
dataarray<SellListRow>Paginated finalized sale rows.
meta.current_page, meta.last_page, meta.per_page, meta.totalintegerPagination metadata.

Status codes

StatusWhen it happensResponse shape
200Sales were returned successfully.JSON { "data": [...], "meta": { ... } } or CSV download.
403The token user does not pass the sell list permission gate.{ "message": "Unauthorized" }
422The query string failed validation, such as a 1-character q.Laravel validation JSON or { "message": string }.

Check sell invoice number availability

Checks whether an invoice number is available in the current business before creating or editing a sale.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/sells/check-invoice-number
PermissionThe token user must be allowed to create either direct or POS sales: sell.create, direct_sell.access, or so.create.
Success response200 with an availability boolean and the checked invoice number.

Query parameters

ParameterTypeRequiredDescription
invoice_nostringYesInvoice number to test for uniqueness in the current business.
exclude_transaction_idintegerNoOptional transaction id to ignore, useful when editing an existing sale and keeping the same invoice number.

Success response

FieldTypeDescription
data.availablebooleantrue when no other transaction in the business uses the invoice number after applying the optional exclusion.
data.invoice_nostringEchoed invoice number that was checked.

Status codes

StatusWhen it happensResponse shape
200The invoice number check completed successfully.{ "data": { "available": boolean, "invoice_no": string } }
403The token user cannot create sales and therefore cannot use this availability check.{ "message": "Unauthorized" }
422The query string failed validation.Laravel validation JSON.

Get sale

Returns one finalized sale with header totals, line items, and payment summaries.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/sells/{id}
PermissionSame permission gate and visibility rules as List sells.
CSV behaviorformat=csv streams one row with a data_json column containing the full JSON payload.
Success response200 with a SellDetail object.

Query parameters

ParameterTypeRequiredDescription
formatstringNojson (default) or csv.

SellLine object

FieldTypeDescription
id, product_id, variation_idintegerSell line ids.
product_labelstringFormatted product and variation label used in the UI.
sub_skustring | nullVariation SKU.
quantitynumberSold quantity.
unit_price, unit_price_inc_tax, unit_price_before_discount, item_tax, line_discount_amountnumber | nullLine pricing values.
line_discount_typestring | nullfixed, percentage, or null.
line_taxobject | nullTax summary with id, name, and amount.
modifiersarray<SellLine> | nullModifier rows are only included when the sale line has modifiers.

SellPaymentSummary object

FieldTypeDescription
idintegerPayment line id.
amountnumber | nullPayment amount.
methodstring | nullStored payment method key.
paid_onstring | nullISO-8601 payment timestamp.
payment_ref_nostring | nullPayment reference number.
notestring | nullSaved payment note.
is_returnbooleanWhether the payment line is a return payment.

SellDetail object

FieldTypeDescription
id, invoice_no, ref_nointeger | string | nullCore sale identifiers.
transaction_datestring | nullISO-8601 transaction timestamp.
status, payment_statusstring | nullSale status and payment status.
final_total, total_before_tax, tax_amount, discount_amount, shipping_charges, total_paidnumber | nullHeader totals.
shipping_statusstring | nullShipping workflow status when present.
additional_notes, staff_notestring | nullSaved sale notes.
is_direct_salebooleanWhether the row is a direct sale.
sub_typestring | nullSale sub-type when present.
consumption_typestringOutbound consumption classification: sales, damages, or sampling (defaults to sales when unset on older rows).
contactobject | nullContact summary with id, name, mobile, email, contact_id, and supplier_business_name.
locationobject | nullLocation summary with id and name.
order_taxobject | nullOrder-level tax summary with id, name, and amount.
linesarray<SellLine>Sell lines in display order.
paymentsarray<SellPaymentSummary>Header payment summary rows.

Success response

FieldTypeDescription
dataSellDetailDetailed finalized sale payload.

Status codes

StatusWhen it happensResponse shape
200The sale was returned successfully.{ "data": SellDetail } or CSV download.
403The token user does not pass the sell list permission gate.{ "message": "Unauthorized" }
404The sale id is not visible in the finalized sales query for the current user.{ "message": "Not found" }

Record sale payment

Records a payment on a finalized sale. This endpoint uses the same sell-payment pipeline as the web add-payment flow and does not post cash-register lines.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/sells/{id}/payments
Permissionsell.payments plus visibility to the sale through the finalized sell query.
Demo modeReturns 403 in demo environments.
Subscription ruleReturns 402 when the business is not subscribed.
Held-sale ruleSuspended POS sales cannot receive payments here; finalize them through the suspended POS endpoint first.
Request encodingapplication/json
Success response201 with a payment summary object.

Required headers

HeaderRequiredDescription
AuthorizationYesBearer YOUR_ACCESS_TOKEN
Content-TypeYesapplication/json
AcceptNoRecommended: application/json.

Request body

FieldTypeRequiredDescription
amountnumberYesPayment amount. Must be at least 0.01.
methodstringYesPayment method key enabled for the sale location.
paid_onstring | nullNoOptional payment date/datetime. Defaults to the current time when omitted.
notestring | nullNoOptional payment note.
account_idinteger | nullNoOptional account id. It is ignored for advance payments.
card_number, card_holder_name, card_transaction_number, card_type, card_month, card_year, card_securitystring | nullNoCard-payment metadata.
cheque_number, bank_account_numberstring | nullNoCheque or bank reference data.
transaction_no_1 to transaction_no_3string | nullNoReference values for custom payment methods.

Success response

FieldTypeDescription
messagestringLocalized success message.
data.transaction_idintegerFinalized sale id from the path.
data.payment_idintegerCreated payment row id.
data.payment_ref_nostring | nullGenerated payment reference number.
data.payment_statusstring | nullUpdated sale payment status after the payment is posted.
data.amountnumberStored payment amount.
data.methodstringStored payment method key.
data.paid_onstring | nullISO-8601 payment timestamp.

Status codes

StatusWhen it happensResponse shape
201The payment was recorded successfully.{ "message": string, "data": { ... } }
402The business is not subscribed.{ "message": string }
403Demo mode is active, the token user lacks sell.payments, or the sale is not visible.{ "message": string }
404The sale id is not visible in the finalized sales query for the current user.{ "message": "Not found" }
422The sale is suspended, already fully paid, the payment method is invalid for the location, or an advance payment exceeds the contact's advance balance.Laravel validation JSON or { "message": string }.
500The payment transaction failed unexpectedly.{ "message": "something_went_wrong" }

Duplicate sale (new draft)

Duplicates a finalized sale into a new draft transaction with copied sell lines and a new draft invoice number.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/sells/{id}/duplicate
Permissionsell.create plus visibility to the source sale through the finalized sell query.
Demo modeReturns 403 in demo environments.
Subscription ruleReturns 402 when the business is not subscribed.
Copy rulesThe controller copies transaction header fields and sell lines, but clears identifiers like id, timestamps, payment_status, invoice_token, and lot_no_line_id.
Success response201 with the new draft id and invoice number.

Success response

FieldTypeDescription
messagestringLocalized duplicate success message.
data.idintegerNew draft transaction id.
data.invoice_nostring | nullNew draft invoice number.
data.statusstringAlways draft.
data.is_direct_salebooleanCopied direct-sale flag from the source transaction.

Status codes

StatusWhen it happensResponse shape
201The duplicate draft was created successfully.{ "message": string, "data": { ... } }
402The business is not subscribed.{ "message": string }
403Demo mode is active, the token user lacks sell.create, or the source sale is not visible.{ "message": string }
404The source sale id is not visible in the finalized sales query.{ "message": "Not found" }
500The duplicate transaction failed unexpectedly.{ "message": "something_went_wrong" }

Returns the public invoice and payment links used by the app for a finalized sale. If the invoice token is missing, the controller generates and stores one before returning the payload.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/sells/{id}/public-links
PermissionSame permission gate and visibility rules as List sells.
Subscription ruleNo subscription gate beyond normal sale visibility.
Success response200 with invoice and payment URLs.

Success response

FieldTypeDescription
data.invoice_urlstringTokenized customer-facing invoice URL.
data.payment_linkstring | nullTokenized online payment link when one is available for the invoice.
data.invoice_tokenstring | nullPersisted invoice token after the controller refreshes the transaction.

Status codes

StatusWhen it happensResponse shape
200The public links were returned successfully.{ "data": { "invoice_url": string, "payment_link": string | null, "invoice_token": string | null } }
403The token user does not pass the sell list permission gate.{ "message": "Unauthorized" }
404The sale id is not visible in the finalized sales query for the current user.{ "message": "Not found" }

Delete finalized sale

Deletes a finalized sale using the same core delete pipeline as the web app.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/sells/{id}
PermissionAt least one of sell.delete, direct_sell.delete, or so.delete.
Demo modeReturns 403 in demo environments.
Subscription ruleReturns 402 when the business is not subscribed.
Visibility rulesThe sale must still be visible through the finalized sell query for the authenticated user.
Delete behaviorDelegates to TransactionUtil::deleteSale, so stock reversal, purchase/sell unmapping, payment cleanup, register cleanup, return guards, and ZATCA or sync blockers are enforced exactly as they are in the app.
Success response200 with the deleted sale id.

Status codes

StatusWhen it happensResponse shape
200The sale was deleted successfully.{ "message": string, "data": { "id": integer } }
402The business is not subscribed.{ "message": string }
403Demo mode is active or the token user lacks delete permission.{ "message": string }
404The sale id is not visible in the finalized sales query for the current user.{ "message": "Not found" }
422deleteSale rejected the delete because of a business rule such as linked returns or external compliance constraints.{ "message": string }
500The delete transaction failed unexpectedly.{ "message": "something_went_wrong" }

Get transaction payment

Returns one payment line by transaction_payments.id. The endpoint supports both invoice-linked payments and standalone contact payments such as advance rows.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/transaction-payments/{id}
AuthenticationBearer token or API key + secret required.
Visibility rulesInvoice-linked rows use the same visibility checks as the parent integration transaction. Standalone contact payments require contact-payment visibility and contact access.
CSV behaviorformat=csv streams one row with a data_json column containing the full JSON payload.
Success response200 with a TransactionPaymentDetail object.

Query parameters

ParameterTypeRequiredDescription
formatstringNojson (default) or csv.

TransactionPaymentNode object

FieldTypeDescription
idintegerPayment line id.
amountnumberPayment amount.
method, method_labelstringStored payment method key and resolved label.
payment_ref_nostring | nullPayment reference number.
paid_onstring | nullISO-8601 payment timestamp.

TransactionPaymentDetail object

FieldTypeDescription
id, parent_id, payment_forinteger | nullCore payment identifiers.
amountnumberPayment amount.
is_returnbooleanWhether the payment line is recorded as a return.
method, method_labelstringStored payment method key and resolved label.
paid_onstring | nullISO-8601 payment timestamp.
payment_ref_no, transaction_no, notestring | nullSaved payment references and note.
account_idinteger | nullLinked account id.
card_number, card_holder_name, card_transaction_number, card_type, card_month, card_year, cheque_number, bank_account_numberstring | nullStored card, cheque, and bank metadata.
documentobject | nullUploaded document summary with name and url.
parent_paymentTransactionPaymentNode | nullParent payment when the row is part of a split or adjustment.
child_paymentsarray<TransactionPaymentNode>Loaded child payment rows for split payments.
transaction_idinteger | nullParent transaction id for invoice-linked rows; null for standalone contact payments.
transactionobject | nullWhen invoice-linked: id, type, invoice_no, ref_no, and payment_status.
contactobject | nullWhen standalone: id, name, type, contact_id, and supplier_business_name.

Status codes

StatusWhen it happensResponse shape
200The payment line was returned successfully.{ "data": TransactionPaymentDetail } or CSV download.
403The token user lacks permission to view the parent transaction or standalone contact payment.{ "message": "Unauthorized" }
404The payment line does not exist, its parent transaction is not visible, or a standalone payment is missing contact linkage.{ "message": "Not found" }

Update transaction payment

Updates an existing invoice-linked payment. Standalone advance-style rows are intentionally rejected by this endpoint.

Endpoint summary

PropertyValue
MethodPUT or PATCH
Path/api/v1/integration/transaction-payments/{id}
Permissionedit_sell_payment for sell and sell-return parents, edit_purchase_payment for purchase parents, or expense access permissions for expense parents.
Demo modeReturns 403 in demo environments.
Supported rowsOnly rows with a non-null transaction_id. Payments whose stored method is advance are rejected.
Request encodingapplication/json or multipart/form-data when uploading document.
Success response200 with updated payment metadata.

Request body

FieldTypeRequiredDescription
amountnumberYesUpdated payment amount. Must be at least 0.01.
methodstringYesPayment method key that must be enabled for the parent transaction location.
paid_onstring | nullNoOptional payment date/datetime. Defaults to now when omitted.
notestring | nullNoOptional note.
account_idinteger | nullNoOptional account id.
card_number, card_holder_name, card_transaction_number, card_type, card_month, card_year, card_securitystring | nullNoCard-payment metadata.
cheque_number, bank_account_numberstring | nullNoCheque or bank reference data.
transaction_no_1 to transaction_no_3string | nullNoCustom payment-method references.
denominationsarray | nullNoOptional cash denomination payload for installations that use denomination tracking.
documentfile | nullNoOptional uploaded document. The configured MIME list and size limit from config/constants.php are enforced.

Success response

FieldTypeDescription
messagestringLocalized update success message.
data.idintegerUpdated payment id.
data.transaction_idintegerParent transaction id.
data.payment_ref_nostring | nullPayment reference number.
data.payment_statusstring | nullUpdated parent transaction payment status.
data.amountnumberStored amount after the update.
data.methodstringStored method key after the update.
data.paid_onstring | nullISO-8601 payment timestamp.
data.documentobject | nullIncluded only when a document is stored, with name and url.

Status codes

StatusWhen it happensResponse shape
200The payment was updated successfully.{ "message": string, "data": { ... } }
403Demo mode is active or the token user lacks permission for the parent transaction type.{ "message": string }
404The payment row does not exist or the parent transaction is not visible.{ "message": "Not found" }
422The row is standalone, the stored method is advance, the requested method is invalid for the location, the upload is invalid, or validation failed.Laravel validation JSON or { "message": string }.
500The payment update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete transaction payment

Deletes an invoice-linked payment row by id.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/transaction-payments/{id}
Permissiondelete_sell_payment for sell and sell-return parents, delete_purchase_payment for purchase parents, or expense access permissions for expense parents.
Demo modeReturns 403 in demo environments.
Supported rowsOnly rows with a non-null transaction_id.
Success response200 with the deleted payment id and refreshed parent payment status.

Status codes

StatusWhen it happensResponse shape
200The payment row was deleted successfully.{ "message": string, "data": { "id": integer, "transaction_id": integer, "payment_status": string | null } }
403Demo mode is active or the token user lacks delete permission for the parent transaction type.{ "message": string }
404The payment row does not exist or the parent transaction is not visible.{ "message": "Not found" }
422The payment row is standalone and cannot be deleted by this endpoint.{ "message": string }
500The delete transaction failed unexpectedly.{ "message": "something_went_wrong" }

Create sale (Add Sale)

Creates a direct sale through the Add Sale workflow. This endpoint supports draft, quotation-style, proforma, and final direct-sale creation without requiring an open cash register.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/sells
AuthenticationBearer token or API key + secret required.
Permissionsell.create or direct_sell.access.
Module requirementThe business must have the add_sale module enabled.
Subscription and quotaThe business must be subscribed and still have invoice quota available.
ModeCreates direct sales only. POS-specific register handling does not apply here.
Request encodingapplication/json
Success response201 with the created sale id, invoice number, status, payment status, and direct-sale flags.

Required headers

HeaderRequiredDescription
AuthorizationYesBearer YOUR_ACCESS_TOKEN
Content-TypeYesapplication/json
AcceptNoRecommended: application/json.

Request body

FieldTypeRequiredDescription
statusstringYesdraft or final.
draft_kindstring | nullNoFor drafts only: standard, quotation, or proforma. Defaults to standard when omitted on drafts.
contact_idintegerYesMust belong to a customer or both contact in the business.
location_idintegerYesBusiness location id. The authenticated user must have access to it.
transaction_datestringYesAny parseable date/datetime accepted by Laravel validation.
tax_rate_idinteger | nullNoOptional order tax rate id from the same business.
discount_type, discount_amountstring | number | nullNoOptional order-level discount. Type accepts fixed or percentage.
sale_note, staff_notestring | nullNoOptional saved notes.
shipping_details, shipping_address, shipping_statusstring | nullNoOptional shipping metadata.
shipping_charges, exchange_ratenumber | nullNoOptional shipping and exchange-rate values.
commission_agent, selling_price_group_id, invoice_scheme_idinteger | nullNoOptional direct-sale header links.
consumption_typestring | nullNoOutbound consumption classification for integrations: sales, damages, or sampling. Omitted or invalid values default to sales.
sales_order_idsarray<integer> | nullNoOptional linked sales-order ids. Every id must resolve to a business sales order.
is_credit_salebooleanNoWhen truthy on a final sale, payment lines are not required at creation time.
paymentsarray | nullNoOptional payment lines for final non-credit sales. Each row uses the FinalPaymentLine shape below.
change_returnnumber | nullNoOptional positive cash change row for final non-credit sales.
rp_redeemednumber | nullNoOptional reward-points redemption amount.
productsarrayYesAt least one sale line using the CreateSellLine shape below.
types_of_service_id, packing_charge, packing_charge_type, service_custom_field_1 to service_custom_field_6mixedNoOptional only when the types_of_service module is enabled.

CreateSellLine object

FieldTypeRequiredDescription
product_idintegerYesProduct id in the current business.
variation_idintegerYesVariation id for the selected product.
quantitynumberYesRequested quantity.
unit_pricenumberYesUnit price before tax.
unit_price_inc_taxnumberYesUnit price including tax.
item_taxnumberYesLine tax amount.
line_discount_type, line_discount_amountstring | number | nullNoOptional line discount details.

FinalPaymentLine object

FieldTypeRequiredDescription
amountnumberYesPayment amount.
methodstringYesPayment method key enabled for the selected location.
paid_onstring | nullNoOptional payment date/datetime.
account_idinteger | nullNoOptional account id.
notestring | nullNoOptional payment note.
is_returnbooleanNoOptional return flag; usually omitted for normal sale creation.

Success response

FieldTypeDescription
messagestringLocalized create success message.
data.idintegerCreated sale id.
data.invoice_nostring | nullGenerated invoice number.
data.statusstringStored status.
data.payment_statusstring | nullStored payment status after the create flow completes.
data.is_direct_salebooleanAlways true for this endpoint.
data.is_suspendbooleanAlways false for this endpoint.

Status codes

StatusWhen it happensResponse shape
201The direct sale was created successfully.{ "message": string, "data": { ... } }
402The business is not subscribed or has reached invoice quota.{ "message": string }
403Demo mode is active, the token user lacks create permission, the Add Sale module is disabled, or the selected location is outside the user's permitted locations.{ "message": string }
422Validation failed, the invoice total could not be calculated, the payment method is invalid for the location, a sales-order id is invalid, the customer credit limit is exceeded, a combo product has no configured components, or stock/payment business rules reject the sale.Laravel validation JSON or { "message": string }.
500The sale create transaction failed unexpectedly.{ "message": "something_went_wrong" }

List sell drafts

Lists Add Sale drafts, including standard drafts, quotation drafts, and proforma drafts, with the same split visibility rules used by the web draft datatable.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/sell-drafts
PermissionAt least one of draft.view_all, draft.view_own, quotation.view_all, or quotation.view_own.
Module requirementThe business must have the add_sale module enabled.
Visibility rulesRows are status = draft. Quotation rows use quotation permissions; standard and proforma rows use draft permissions. Permitted-location scope applies to all rows.
CSV behaviorformat=csv streams all matching rows and ignores pagination.
Success response200 with paginated SellDraftListRow rows.

Query parameters

ParameterTypeRequiredDescription
per_pageintegerNoResults per page from 1 to 100. Defaults to 20.
pageintegerNoPagination page number.
location_id, contact_id, created_byintegerNoOptional filters for location, customer, and creator.
start_date, end_datestringNoOptional transaction date range in Y-m-d. Applied only when both are present.
kindstringNostandard, quotation, proforma, or all. Defaults to all.
qstringNoMinimum 2 characters when sent. Matches ref_no, invoice_no, numeric id, and customer-facing contact fields.
formatstringNojson (default) or csv.

SellDraftListRow object

FieldTypeDescription
All SellListRow fieldsmixedThe same base row shape returned by List sells.
sub_statusstring | nullDraft sub-type such as quotation, proforma, or null for standard drafts.
is_quotationbooleanWhether the row is marked internally as a quotation draft.

Top-level JSON response

FieldTypeDescription
dataarray<SellDraftListRow>Paginated draft rows.
meta.current_page, meta.last_page, meta.per_page, meta.totalintegerPagination metadata.

Status codes

StatusWhen it happensResponse shape
200Draft rows were returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks draft or quotation list permissions, or the Add Sale module is disabled.{ "message": string }
422The query string failed validation, such as a 1-character q.Laravel validation JSON or { "message": string }.

Get sell draft

Returns one draft sale with the same detailed line and payment structure as finalized sell detail, plus draft-specific flags.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/sell-drafts/{id}
PermissionSame permission gate and draft visibility rules as List sell drafts.
CSV behaviorformat=csv streams one row with a data_json column containing the full JSON payload.
Success response200 with a SellDraftDetail object.

SellDraftDetail object

FieldTypeDescription
All SellDetail fieldsmixedThe same detailed payload used by Get sell.
is_quotationbooleanQuotation flag loaded from the draft transaction.
sub_statusstring | nullDraft subtype such as quotation or proforma.

Status codes

StatusWhen it happensResponse shape
200The draft was returned successfully.{ "data": SellDraftDetail } or CSV download.
403The token user lacks list visibility for the draft row or the Add Sale module is disabled.{ "message": string }
404The draft id is not visible in the draft query.{ "message": "Not found" }

Update sell draft

Updates a visible draft or quotation draft while keeping it in draft state.

Endpoint summary

PropertyValue
MethodPUT or PATCH
Path/api/v1/integration/sell-drafts/{id}
Permissionquotation.update when the stored sub_status is quotation; otherwise draft.update.
Module requirementThe business must have the add_sale module enabled.
Subscription ruleReturns 402 when the business is not subscribed.
Edit windowtransaction_edit_days is enforced. Existing sell returns also block updates.
Request encodingapplication/json
Success response200 with the updated draft id, invoice number, and status.

Request body differences from Create sale

FieldTypeRequiredDescription
All fields from Create salemixedMostly YesThe endpoint reuses the same core payload shape as direct sale creation.
statusstringYesMust stay draft; any other value returns 422.
products[].transaction_sell_lines_idinteger | nullNoOptional existing sell-line id when updating previously saved draft rows.

Status codes

StatusWhen it happensResponse shape
200The draft was updated successfully.{ "message": string, "data": { "id": integer, "invoice_no": string | null, "status": "draft" } }
402The business is not subscribed.{ "message": string }
403Demo mode is active, the Add Sale module is disabled, the token user lacks the correct update permission, or the chosen location is not permitted.{ "message": string }
404The draft id is not visible in the draft query.{ "message": "Not found" }
422The edit window expired, a sell return exists, the payload is invalid, the status is not draft, or invoice calculation/business rules rejected the update.Laravel validation JSON or { "message": string }.
500The draft update transaction failed unexpectedly.{ "message": string }

Delete sell draft

Deletes a visible draft row, including quotation drafts, through the draft branch of the normal sale delete pipeline.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/sell-drafts/{id}
Permissionquotation.delete when the stored sub_status is quotation; otherwise draft.delete.
Module requirementThe business must have the add_sale module enabled.
Subscription ruleReturns 402 when the business is not subscribed.
Edit windowtransaction_edit_days is enforced. Existing sell returns also block deletion.
Delete behaviorDelegates to TransactionUtil::deleteSale, so the same downstream draft-delete rules apply as in the app.
Success response200 with the deleted draft id.

Status codes

StatusWhen it happensResponse shape
200The draft was deleted successfully.{ "message": string, "data": { "id": integer } }
402The business is not subscribed.{ "message": string }
403Demo mode is active, the Add Sale module is disabled, or the token user lacks the correct delete permission.{ "message": string }
404The draft id is not visible in the draft query.{ "message": "Not found" }
422The edit window expired, a sell return exists, or the delete pipeline rejected the draft.{ "message": string }
500The draft delete transaction failed unexpectedly.{ "message": "something_went_wrong" }

List quotations

Lists quotation drafts only.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/quotations
Permissionquotation.view_all or quotation.view_own.
Module requirementThe business must have the add_sale module enabled.
Row scopeOnly draft rows where sub_status = quotation.
CSV behaviorformat=csv streams all matching rows and ignores pagination.
Success response200 with paginated SellDraftListRow rows.

Query parameters

ParameterTypeRequiredDescription
All draft-list filters except kindmixedNoUses the same filters as List sell drafts, but quotations are already isolated by the route.
formatstringNojson (default) or csv.

Status codes

StatusWhen it happensResponse shape
200Quotation rows were returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks quotation list permission or the Add Sale module is disabled.{ "message": string }
422The query string failed validation, such as a 1-character q.Laravel validation JSON or { "message": string }.

Get quotation

Returns one quotation draft with the same detailed payload shape as draft detail.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/quotations/{id}
PermissionSame permission gate and quotation visibility rules as List quotations.
CSV behaviorformat=csv streams one row with a data_json column containing the full JSON payload.
Success response200 with a SellDraftDetail object.

Status codes

StatusWhen it happensResponse shape
200The quotation was returned successfully.{ "data": SellDraftDetail } or CSV download.
403The token user lacks quotation visibility or the Add Sale module is disabled.{ "message": string }
404The quotation id is not visible in the quotation query.{ "message": "Not found" }

Create quotation

Creates a quotation draft. The controller forces status = draft and draft_kind = quotation server-side, so the client can reuse the same payload shape as direct sale creation.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/quotations
PermissionSame as Create sale: sell.create or direct_sell.access.
Module, subscription, quotaThe Add Sale module, subscription check, and invoice quota rules are all enforced the same way as direct sale creation.
Request bodyReuse the Create sale JSON body. Draft status and quotation kind are injected server-side.
Success response201 with the same response shape as Create sale.

Status codes

StatusWhen it happensResponse shape
201The quotation draft was created successfully.{ "message": string, "data": { ... } }
402The business is not subscribed or has no remaining invoice quota.{ "message": string }
403Demo mode is active, the Add Sale module is disabled, or the token user lacks create permission.{ "message": string }
422The reused create-sale payload failed validation or business-rule checks.Laravel validation JSON or { "message": string }.
500The quotation create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Convert quotation to invoice

Converts a visible quotation draft into a finalized invoice.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/quotations/{id}/convert-to-invoice
Permissionsell.create or direct_sell.access.
Module, subscription, quotaThe Add Sale module must be enabled, the business must be subscribed, and invoice quota must still be available.
Demo modeReturns 403 in demo environments.
WorkflowThe controller assigns a new final invoice number, clears quotation flags, decreases stock, updates payment status, maps purchases, runs notifications, writes activity, and dispatches the sell modified event.
Special rulesCustomer credit limits are enforced. POS/screen quotations (is_direct_sale = 0) require an open cash register before conversion.
Success response200 with finalized invoice metadata.

Success response

FieldTypeDescription
messagestringLocalized conversion success message.
data.idintegerConverted sale id.
data.invoice_nostring | nullNew finalized invoice number.
data.statusstringAlways final after success.
data.payment_statusstring | nullUpdated payment status after conversion.
data.is_direct_salebooleanPreserved direct/POS mode flag from the quotation.

Status codes

StatusWhen it happensResponse shape
200The quotation was converted successfully.{ "message": string, "data": { ... } }
402The business is not subscribed or has no remaining invoice quota.{ "message": string }
403Demo mode is active, the Add Sale module is disabled, or the token user lacks convert permission.{ "message": string }
404The quotation id is not visible in the quotation query.{ "message": "Not found" }
422The customer credit limit is exceeded, the cash register requirement is not met for a POS quotation, or stock/payment business rules reject the conversion.{ "message": string }
500The quotation conversion transaction failed unexpectedly.{ "message": "something_went_wrong" }

List sell returns

Lists finalized sell-return transactions visible to the authenticated user.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/sell-returns
Permissionaccess_sell_return or access_own_sell_return.
Visibility rulesOnly type = sell_return, status = final rows are returned. Permitted-location scope applies. Own-only users are limited to rows they created.
CSV behaviorformat=csv streams all matching rows and ignores pagination.
Success response200 with paginated SellReturnListRow rows.

Query parameters

ParameterTypeRequiredDescription
per_page, pageintegerNoPagination controls. per_page accepts 1 to 100 and defaults to 20.
location_id, contact_id, created_byintegerNoOptional location, customer, and creator filters.
start_date, end_datestringNoOptional transaction date range in Y-m-d. Applied only when both are present.
qstringNoMinimum 2 characters when sent. Matches the return invoice/ref, numeric id, customer-facing contact fields, and the parent sale invoice/ref.
formatstringNojson (default) or csv.

SellReturnListRow object

FieldTypeDescription
idintegerSell-return transaction id.
invoice_no, ref_nostring | nullReturn identifiers.
transaction_datestring | nullISO-8601 return timestamp.
status, payment_statusstring | nullReturn status and payment status.
final_total, total_before_tax, tax_amount, total_paidnumber | nullReturn totals.
return_parent_idinteger | nullParent finalized sale id.
parent_saleobject | nullParent sale summary with id, invoice_no, and ref_no.
contactobject | nullContact summary with id, name, mobile, contact_id, and supplier_business_name.
locationobject | nullLocation summary with id and name.

Status codes

StatusWhen it happensResponse shape
200Sell returns were returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks sell-return list permission.{ "message": "Unauthorized" }
422The query string failed validation, such as a 1-character q.Laravel validation JSON or { "message": string }.

Get sell return

Returns one sell-return transaction with parent-sale linkage, returned lines, and payment summaries.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/sell-returns/{id}
PermissionSame permission gate and visibility rules as List sell returns.
CSV behaviorformat=csv streams one row with a data_json column containing the full JSON payload.
Success response200 with a SellReturnDetail object.

SellReturnLine object

FieldTypeDescription
sell_line_id, product_id, variation_idintegerIdentifiers for the original parent sale line.
product_labelstringFormatted parent sale product label.
sub_skustring | nullVariation SKU.
quantity_returnednumberReturned quantity for that line.
unit_price_inc_taxnumber | nullStored unit price including tax from the parent sale line.
line_totalnumberReturned quantity multiplied by unit_price_inc_tax.

SellReturnDetail object

FieldTypeDescription
id, invoice_no, ref_nointeger | string | nullCore return identifiers.
transaction_datestring | nullISO-8601 return timestamp.
status, payment_statusstring | nullReturn status and payment status.
final_total, total_before_tax, tax_amount, total_paidnumber | nullReturn totals.
return_parent_idinteger | nullParent finalized sale id.
parent_saleobject | nullParent sale summary with id, invoice_no, ref_no, and transaction_date.
contactobject | nullContact summary with id, name, mobile, email, contact_id, and supplier_business_name.
locationobject | nullLocation summary with id and name.
order_taxobject | nullTax summary with id, name, amount, and is_tax_group.
linesarray<SellReturnLine>Returned parent-sale lines where quantity_returned > 0.
paymentsarray<SellPaymentSummary>Return payment summaries using the same shape as sell detail payments.

Status codes

StatusWhen it happensResponse shape
200The sell return was returned successfully.{ "data": SellReturnDetail } or CSV download.
403The token user lacks sell-return visibility.{ "message": "Unauthorized" }
404The sell return id is not visible in the sell-return query.{ "message": "Not found" }

Create or update sell return

Creates or updates a sell return against a finalized sale using the same return pipeline as the web app.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/sell-returns
Permissionaccess_sell_return or access_own_sell_return.
Subscription ruleReturns 402 when the business is not subscribed.
Parent sale rulesThe parent sale must be a finalized sell in the current business, within permitted locations, and visible to the current user under the sell-return access rules.
Success response201 with the created sell-return id, invoice number, and parent sale id.

Request body

FieldTypeRequiredDescription
transaction_idintegerYesParent finalized sale id.
returnsarrayYesAt least one returned line using the SellReturnRequestLine shape below.
discount_type, discount_amountstring | number | nullNoOptional return-level discount. Defaults from the parent sale when omitted.
tax_idinteger | nullNoOptional tax id from the same business. Defaults from the parent sale when omitted.
invoice_nostring | nullNoOptional custom return invoice number.
transaction_datestring | nullNoOptional return date/datetime.

SellReturnRequestLine object

FieldTypeRequiredDescription
sell_line_idintegerYesMust belong to the parent sale identified by transaction_id.
quantitynumberYesRequested return quantity in the same unit context as the sale line. The controller converts sub-units before enforcing the sold-quantity ceiling.

Status codes

StatusWhen it happensResponse shape
201The sell return was created successfully.{ "message": string, "data": { "id": integer, "invoice_no": string | null, "parent_sale_id": integer } }
402The business is not subscribed.{ "message": string }
403Demo mode is active or the token user lacks sell-return access.{ "message": string }
404The parent sale is outside the user's sell-return visibility scope.{ "message": "Not found" }
422A return line does not belong to the parent sale, all requested quantities are zero on a new return, a quantity exceeds the sold quantity, validation fails, or the core return pipeline rejects the request.Laravel validation JSON or { "message": string }.
500The sell-return create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Record sell return payment

Records a refund payment against a sell-return transaction.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/sell-returns/{id}/payments
Permissionsell.payments plus sell-return visibility through the finalized sell-return query.
Subscription ruleReturns 402 when the business is not subscribed.
Success response201 with the created payment summary.

Request body

FieldTypeRequiredDescription
amountnumberYesRefund amount. Must be at least 0.01.
methodstringYesPayment method key enabled for the return location.
paid_on, notestring | nullNoOptional payment datetime and note.
account_idinteger | nullNoOptional account id. It is ignored for advance payments.
card_number, card_holder_name, card_transaction_number, card_type, card_month, card_year, card_securitystring | nullNoCard-payment metadata.
cheque_number, bank_account_numberstring | nullNoCheque or bank reference data.
transaction_no_1 to transaction_no_3string | nullNoReference values for custom payment methods.

Status codes

StatusWhen it happensResponse shape
201The refund payment was recorded successfully.{ "message": string, "data": { ... } }
402The business is not subscribed.{ "message": string }
403Demo mode is active, the token user lacks sell.payments, or the return is not visible.{ "message": string }
404The sell return id is not visible in the sell-return query.{ "message": "Not found" }
422The return is already fully paid, the payment method is invalid for the location, validation fails, or an advance payment exceeds the contact's available balance.Laravel validation JSON or { "message": string }.
500The refund payment transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete sell return

Deletes a sell return and reverses the stored return quantities and stock effects.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/sell-returns/{id}
PermissionSame permission gate and visibility rules as List sell returns.
Subscription ruleReturns 402 when the business is not subscribed.
Delete behaviorThe controller clears quantity_returned on parent sale lines, updates sold quantities, restores stock at the return location, deletes the return row, and dispatches TransactionPaymentDeleted for loaded payment rows.
Success response200 with the deleted sell-return id.

Status codes

StatusWhen it happensResponse shape
200The sell return was deleted successfully.{ "message": string, "data": { "id": integer } }
402The business is not subscribed.{ "message": string }
403Demo mode is active or the token user lacks sell-return access.{ "message": string }
404The sell return id is not visible in the sell-return query.{ "message": "Not found" }
422The core reversal pipeline rejects the delete, such as on purchase/sell mismatch conditions.{ "message": string }
500The delete transaction failed unexpectedly.{ "message": "something_went_wrong" }

Response 200message and data.id. 404 if not found or not visible. 422 on purchase/sell mapping errors.

List shipments

Lists finalized sales that are currently in the shipping workflow.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/shipments
PermissionAdmin access or any of access_shipping, access_own_shipping, or access_commission_agent_shipping.
Row scopeOnly finalized sells with a non-null shipping_status. project_invoice rows are excluded.
Visibility rulesPermitted-location scope applies. Own and commission-agent shipment permissions use the same scoping as the web shipment screen. Users with access_pending_shipments_only never receive delivered rows.
CSV behaviorformat=csv streams all matching rows and ignores pagination.
Success response200 with paginated ShipmentRow rows.

Query parameters

ParameterTypeRequiredDescription
per_page, pageintegerNoPagination controls. per_page accepts 1 to 100 and defaults to 20.
location_id, contact_id, created_byintegerNoOptional location, customer, and creator filters.
start_date, end_datestringNoOptional transaction date range in Y-m-d. Applied only when both are present.
shipping_statusstringNoOne of ordered, packed, shipped, delivered, or cancelled.
delivery_personintegerNoOptional delivery-person user id filter.
only_pendingboolean-like stringNoWhen true, excludes delivered rows even if the user normally has access to them.
qstringNoMinimum 2 characters when sent. Matches the sale invoice/ref, numeric id, and linked customer-facing contact fields.
formatstringNojson (default) or csv.

ShipmentRow object

FieldTypeDescription
idintegerUnderlying finalized sale id.
invoice_no, ref_nostring | nullSale identifiers.
transaction_datestring | nullISO-8601 sale timestamp.
status, payment_statusstring | nullSale status and payment status.
final_total, total_before_tax, tax_amount, discount_amount, total_paidnumber | nullSale totals.
is_direct_salebooleanWhether the shipment comes from a direct sale.
sub_typestring | nullSale sub-type when present.
shipping_statusstring | nullCurrent shipment workflow status.
shipping_details, shipping_address, delivered_tostring | nullShipping text fields from the sale.
delivery_personinteger | nullAssigned delivery-person user id.
shipping_custom_field_1 to shipping_custom_field_5string | nullCustom shipment fields.
contactobject | nullContact summary with id, name, mobile, contact_id, and supplier_business_name.
locationobject | nullLocation summary with id and name.

Status codes

StatusWhen it happensResponse shape
200Shipment rows were returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks shipment access.{ "message": "Unauthorized" }
422The query string failed validation, such as a 1-character q.Laravel validation JSON or { "message": string }.

Get shipment (sale with shipping)

Returns one shipment row by finalized sale id.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/shipments/{id}
PermissionSame permission gate and visibility rules as List shipments.
CSV behaviorformat=csv streams one row with a data_json column containing the full JSON payload.
Success response200 with one ShipmentRow object.

Status codes

StatusWhen it happensResponse shape
200The shipment row was returned successfully.{ "data": ShipmentRow } or CSV download.
403The token user lacks shipment access.{ "message": "Unauthorized" }
404The finalized sale is outside the shipment list visibility scope.{ "message": "Not found" }

Shipping status labels

Returns the allowed shipping-status keys and labels used by the shipment list and update endpoints.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/shipments/shipping-statuses
PermissionSame shipment-access gate used by the shipment list.
CSV behaviorformat=csv streams the same rows as JSON with key and label columns.
Success response200 with data[] status rows.

ShippingStatusRow object

FieldTypeDescription
keystringStatus key used by the API, such as ordered or delivered.
labelstringTranslated display label.

Status codes

StatusWhen it happensResponse shape
200The shipping statuses were returned successfully.{ "data": [ { "key": string, "label": string } ] } or CSV download.
403The token user lacks shipment access.{ "message": "Unauthorized" }

Update shipment (shipping fields)

Updates shipment-related fields on a finalized sale. This endpoint uses shipment-context visibility, which means the sale does not have to already be in the shipment list as long as it is a visible finalized sale.

Endpoint summary

PropertyValue
MethodPUT or PATCH
Path/api/v1/integration/shipments/{id}
PermissionSame shipment-access gate used by the shipment list.
Demo modeReturns 403 in demo environments.
Request encodingapplication/json
Activity noteshipping_note is written only into the activity log payload. It is not a transaction column.
Success response200 with the sale id, resulting shipping_status, and delivery_person.

Request body

FieldTypeRequiredDescription
shipping_details, shipping_addressstring | nullNoOptional shipping text fields.
shipping_statusstring | nullNoOne of ordered, packed, shipped, delivered, or cancelled.
delivered_tostring | nullNoOptional recipient text.
delivery_personinteger | nullNoOptional delivery-person user id.
shipping_custom_field_1 to shipping_custom_field_5string | nullNoOptional custom shipment fields.
shipping_notestring | nullNoOptional activity-log note. Sending only this field is not enough; at least one persisted shipment field must also be present.

Status codes

StatusWhen it happensResponse shape
200The shipment fields were updated successfully.{ "message": string, "data": { "id": integer, "shipping_status": string | null, "delivery_person": integer | null } }
403Demo mode is active or the token user lacks shipment access.{ "message": string }
404The finalized sale is outside the shipment update visibility scope.{ "message": "Not found" }
422No persisted shipment fields were supplied or validation failed.Laravel validation JSON or { "message": string }.
500The shipment update failed unexpectedly.{ "message": "something_went_wrong" }

List discounts

Lists discounts configured for the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/discounts
Permissiondiscount.access.
ScopeBusiness-scoped discounts from the Discounts screen, joined with brand, category, and location names.
CSV behaviorformat=csv streams all matching rows and ignores pagination. The variations array is JSON-encoded in CSV cells.
Success response200 with paginated DiscountRow rows.

Query parameters

ParameterTypeRequiredDescription
per_page, pageintegerNoPagination controls. per_page accepts 1 to 100 and defaults to 20.
is_activeboolean-like stringNoOptional active-state filter. Accepts 0, 1, true, or false.
qstringNoMinimum 2 characters when sent. Matches discount name or numeric id.
formatstringNojson (default) or csv.

VariationSummary object

FieldTypeDescription
id, product_idintegerVariation and product ids.
sub_skustring | nullVariation SKU.
labelstringResolved product and variation label used by the discount API.

DiscountRow object

FieldTypeDescription
id, business_idintegerDiscount identifiers.
namestringDiscount name.
brand_id, category_id, location_idinteger | nullLinked brand, category, and location ids.
priorityinteger | nullDiscount priority.
discount_typestring | nullfixed or percentage.
discount_amountnumber | nullConfigured discount amount.
starts_at, ends_atstring | nullISO-8601 start and end timestamps.
is_active, applicable_in_cgbooleanActivation and customer-group flags.
spgstring | nullSelling price group id stored as a string.
brand_name, category_name, location_namestring | nullJoined display names.
variationsarray<VariationSummary>Resolved variation list attached to the discount.

Status codes

StatusWhen it happensResponse shape
200Discount rows were returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks discount.access.{ "message": "Unauthorized" }
422The query string failed validation, such as a 1-character q.Laravel validation JSON or { "message": string }.

Get discount

Returns one discount row by id.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/discounts/{id}
Permissiondiscount.access.
CSV behaviorformat=csv streams one row with a data_json column containing the full JSON payload.
Success response200 with one DiscountRow object.

Status codes

StatusWhen it happensResponse shape
200The discount was returned successfully.{ "data": DiscountRow } or CSV download.
403The token user lacks discount.access.{ "message": "Unauthorized" }
404No discount with that id exists in the current business.{ "message": "Not found" }

Create discount

Creates a new discount for the current business.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/discounts
Permissiondiscount.access.
Demo modeReturns 403 in demo environments.
Request encodingapplication/json
Success response201 with the full created DiscountRow object.

Request body

FieldTypeRequiredDescription
namestringYesDiscount name.
location_idintegerYesBusiness location id.
priorityintegerYesPriority value, minimum 0.
discount_typestringYesfixed or percentage.
discount_amountnumberYesDiscount amount, minimum 0.
brand_id, category_idinteger | nullNoOptional brand and category ids from the current business.
starts_at, ends_atstring | nullNoOptional date or datetime values. On create, ends_at must be on or after starts_at.
is_active, applicable_in_cgbooleanNoOptional booleans. Defaults are true and false respectively when omitted.
spginteger | string | nullNoOptional selling price group id from the current business.
variation_idsarray<integer> | nullNoOptional attached variation ids. Every id must belong to a variation inside the current business. When this array is non-empty, the controller clears brand_id and category_id before saving.

Status codes

StatusWhen it happensResponse shape
201The discount was created successfully.{ "message": string, "data": DiscountRow }
403Demo mode is active or the token user lacks discount.access.{ "message": string }
422Validation failed or one or more variation_ids do not belong to this business. Invalid variation ids are returned in invalid_variation_ids.Laravel validation JSON or { "message": string, "invalid_variation_ids": [...] }.
500The discount create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Update discount

Partially updates an existing discount.

Endpoint summary

PropertyValue
MethodPUT or PATCH
Path/api/v1/integration/discounts/{id}
Permissiondiscount.access.
Demo modeReturns 403 in demo environments.
Request stylePartial update. Omit keys you want to keep unchanged.
Success response200 with the full updated DiscountRow object.

Request body notes

FieldTypeDescription
Any field from Add discountmixedAll create fields are reusable on update as optional fields.
variation_idsarray<integer> | nullWhen the key is omitted, attached variations stay unchanged. When the key is sent, including [], the relation is synced to that exact array. A non-empty array clears brand_id and category_id before saving.
ends_atstring | nullOn update, the controller compares it against the incoming or existing starts_at and rejects an earlier end date.

Status codes

StatusWhen it happensResponse shape
200The discount was updated successfully.{ "message": string, "data": DiscountRow }
403Demo mode is active or the token user lacks discount.access.{ "message": string }
404No discount with that id exists in the current business.{ "message": "Not found" }
422Validation failed, the end date is earlier than the effective start date, or one or more variation_ids do not belong to this business.Laravel validation JSON or { "message": string, "invalid_variation_ids": [...] }.
500The discount update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete discount

Deletes a discount by id.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/discounts/{id}
Permissiondiscount.access.
Demo modeReturns 403 in demo environments.
Success response200 with the deleted discount id.

Status codes

StatusWhen it happensResponse shape
200The discount was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode is active or the token user lacks discount.access.{ "message": string }
404No discount with that id exists in the current business.{ "message": "Not found" }
500The discount delete transaction failed unexpectedly.{ "message": "something_went_wrong" }

Activate discount

Reactivates a discount by forcing is_active back to true.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/discounts/{id}/activate
Permissiondiscount.access.
Demo modeReturns 403 in demo environments.
Success response200 with the activated discount id and is_active = true.

Status codes

StatusWhen it happensResponse shape
200The discount was activated successfully.{ "message": string, "data": { "id": integer, "is_active": true } }
403Demo mode is active or the token user lacks discount.access.{ "message": string }
404No discount with that id exists in the current business.{ "message": "Not found" }

List POS sales

Lists finalized POS sales only.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/pos/sells
PermissionSame permission gate as List sells.
Module requirementThe business must have the pos_sale module enabled.
Row scopeUses the finalized sell list query but forces is_direct_sale = 0 and sub_type = null.
Query and responseUses the same filters, pagination, CSV behavior, and SellListRow response shape as List sells.

Status codes

StatusWhen it happensResponse shape
200POS sales were returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks sell-list access or the pos_sale module is disabled.{ "message": string }
422The query string failed validation.Laravel validation JSON or { "message": string }.

Recent POS transactions (current user)

Returns recent POS transactions created by the authenticated user.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/pos/recent-transactions
PermissionSame permission gate as List POS sales.
Module requirementThe business must have the pos_sale module enabled.
Row scopeOnly rows created by the current user, with type = sell and is_direct_sale = 0.
Success response200 with PosRecentRow rows and lightweight meta fields.

Query parameters

ParameterTypeRequiredDescription
statusstringNoDefaults to final. Use draft for non-quotation drafts, quotation for quotation drafts, or any other stored transaction status value.
transaction_sub_typestring | nullNoWhen omitted, only rows with sub_type = null are returned. When present, filters to that exact sub_type.
limitintegerNoMaximum rows to return, from 1 to 50. Defaults to the configured POS recent-transactions limit, capped at 50.
formatstringNojson (default) or csv.

PosRecentRow object

FieldTypeDescription
All SellListRow fieldsmixedThe same base fields returned by the finalized sell list.
sub_statusstring | nullDraft subtype, such as quotation, when applicable.
is_suspendbooleanWhether the POS sale is held.
tableobject | nullRestaurant table summary with id and name when set.
created_atstring | nullISO-8601 creation timestamp.

Top-level JSON response

FieldTypeDescription
dataarray<PosRecentRow>Recent POS rows, capped by limit.
meta.status, meta.transaction_sub_typestring | nullEchoes the effective filters.
meta.limit, meta.countintegerRequested limit and returned row count.

Status codes

StatusWhen it happensResponse shape
200Recent POS transactions were returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks sell-list access or the pos_sale module is disabled.{ "message": string }
422The query string failed validation.Laravel validation JSON.

Create POS sale

Creates a POS sale or held POS sale. The request body reuses the direct-sale create payload, but the controller enforces POS-specific register behavior.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/pos/sells
Permissionsell.create, direct_sell.access, or so.create.
Module requirementThe business must have the pos_sale module enabled.
Subscription and quotaThe business must be subscribed and still have invoice quota available.
Register ruleAn open cash register is required before creating POS sales. Held final POS sales skip immediate payment posting but still require the module and normal create permissions.
Request bodyReuse the Create sale JSON body, plus optional is_suspend.
POS behaviorThe controller forces is_direct_sale = 0. Final non-suspended non-credit sales also post payment rows to the open cash register.
Success response201 with the same response shape as Create sale, but is_direct_sale is always false.

POS-only request field

FieldTypeRequiredDescription
is_suspendbooleanNoWhen true on a final POS sale, the sale is created as held. Payment lines and cash-register rows are deferred until finalization, but stock movement still follows the normal final-sale flow.
consumption_typestring | nullNoSame optional field as Create sale (sales, damages, or sampling).

Status codes

StatusWhen it happensResponse shape
201The POS sale was created successfully.{ "message": string, "data": { ... } }
402The business is not subscribed or has reached invoice quota.{ "message": string }
403Demo mode is active, the token user lacks POS create permission, the pos_sale module is disabled, or the chosen location is not permitted.{ "message": string }
422The cash register requirement is not met, the create-sale payload is invalid, a payment method is invalid for the location, or other stock/payment business rules reject the POS sale.Laravel validation JSON or { "message": string }.
500The POS create transaction failed unexpectedly.{ "message": "something_went_wrong" }

List suspended POS sales

Lists held POS invoices only.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/pos/suspended-sales
PermissionSame permission gate as List sells.
Module requirementThe business must have the pos_sale module enabled.
Row scopeOnly finalized rows where is_direct_sale = 0, sub_type = null, and is_suspend = 1.
Success response200 with paginated SuspendedPosRow rows.

Query parameters

ParameterTypeRequiredDescription
per_page, pageintegerNoPagination controls. per_page accepts 1 to 100 and defaults to 20.
location_idintegerNoOptional held-sale location filter.
qstringNoMinimum 2 characters when sent. Matches invoice/ref, numeric id, and customer-facing contact fields.
formatstringNojson (default) or csv.

SuspendedPosRow object

FieldTypeDescription
All SellListRow fieldsmixedThe same base sell-list shape.
is_suspendbooleanAlways true for this endpoint.

Status codes

StatusWhen it happensResponse shape
200Held POS rows were returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks sell-list access or the pos_sale module is disabled.{ "message": string }
422The query string failed validation.Laravel validation JSON or { "message": string }.

Get suspended POS sale

Returns one held POS sale with full sell detail and is_suspend = true.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/pos/suspended-sales/{id}
PermissionSame permission gate and visibility rules as List suspended POS sales.
CSV behaviorformat=csv streams one row with a data_json column containing the full JSON payload.
Success response200 with the SellDetail payload plus is_suspend = true.

Status codes

StatusWhen it happensResponse shape
200The held POS sale was returned successfully.{ "data": SellDetail & { is_suspend: true } } or CSV download.
403The token user lacks sell-list access or the pos_sale module is disabled.{ "message": string }
404The held POS sale id is not visible in the suspended-sale query.{ "message": "Not found" }

Finalize suspended POS sale

Finalizes a held POS sale by clearing the suspended flag and recording checkout payments or credit-sale state.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/pos/suspended-sales/{id}/finalize
PermissionAt least one of sell.update, direct_sell.access, so.update, or edit_pos_payment.
Module and subscriptionThe business must have the pos_sale module enabled and an active subscription.
Demo modeReturns 403 in demo environments.
Target rowThe sale must be visible, held, POS-only, and have no existing payment lines.
Register ruleAn open cash register is required unless is_credit_sale is true.
Success response200 with finalized invoice metadata.

Request body

FieldTypeRequiredDescription
is_credit_salebooleanNoWhen true, the suspended sale is finalized without payment rows.
paymentsarray | nullConditionalRequired when is_credit_sale is false. Uses the same payment row shape as FinalPaymentLine.
change_returnnumber | nullNoOptional cash change row appended as a cash is_return payment line.

Status codes

StatusWhen it happensResponse shape
200The held POS sale was finalized successfully.{ "message": string, "data": { ... } }
402The business is not subscribed.{ "message": string }
403Demo mode is active, the token user lacks finalize permission, or the pos_sale module is disabled.{ "message": string }
404The held POS sale id is not visible in the finalize query.{ "message": "Not found" }
422The held sale already has payment lines, the request omitted required payments for non-credit checkout, the payment method is invalid for the location, the cash register requirement is not met, or an advance payment exceeds the contact balance.Laravel validation JSON or { "message": string }.
500The finalize transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete suspended POS sale

Deletes a held POS sale that has not yet collected any payment lines.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/pos/suspended-sales/{id}
PermissionAt least one of sell.delete, direct_sell.delete, or so.delete.
Module and subscriptionThe business must have the pos_sale module enabled and an active subscription.
Demo modeReturns 403 in demo environments.
Delete rulesThe row must still be a visible held POS sale and must have zero payment-line balance. The delete then delegates to TransactionUtil::deleteSale.
Success response200 with the deleted held-sale id.

Status codes

StatusWhen it happensResponse shape
200The held POS sale was deleted successfully.{ "message": string, "data": { "id": integer } }
402The business is not subscribed.{ "message": string }
403Demo mode is active, the token user lacks delete permission, or the pos_sale module is disabled.{ "message": string }
404The held POS sale id is not visible in the delete query.{ "message": "Not found" }
422The held sale already has payment lines or the delete pipeline rejects the sale because of business rules such as returns or compliance blockers.{ "message": string }
500The delete transaction failed unexpectedly.{ "message": "something_went_wrong" }

List cash registers

Lists open and closed cash-register sessions visible to the token user.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/cash-registers
Permissionview_cash_register.
Module requirementThe business must have the pos_sale module enabled.
VisibilityRows are limited to the token user's permitted locations. When the user is location-restricted, sessions with location_id = null still remain visible.
CSV behaviorformat=csv streams all matching rows with a UTF-8 BOM and ignores page and per_page.
Success response200 with paginated CashRegisterRow rows.

Query parameters

ParameterTypeRequiredDescription
per_page, pageintegerNoPagination controls. per_page accepts 1 to 100 and defaults to 20.
location_idintegerNoFilters by the register session location id.
user_idintegerNoFilters sessions opened by a specific user id.
statusstringNoRestricts rows to open or close.
start_date, end_datestringConditionalOptional register-opened date range. Both values must be sent together, are parsed with the business date format, and start_date must not be after end_date.
formatstringNojson (default) or csv.

CashRegisterRow object

FieldTypeDescription
idintegerCash register session id.
user_id, location_idinteger | nullUser and business-location ids linked to the session.
user_name, user_email, location_namestring | nullJoined display fields for the session owner and location.
statusstringopen or close.
opened_at, closed_atstring | nullISO-8601 timestamps for when the session opened and closed.
closing_amountnumber | nullRecorded closing cash amount after the register is closed.
total_card_slips, total_chequesinteger | nullCounts recorded during closing.

Top-level JSON response

FieldTypeDescription
dataarray<CashRegisterRow>The current result page.
meta.current_page, meta.last_page, meta.per_page, meta.totalintegerLaravel paginator metadata.

Status codes

StatusWhen it happensResponse shape
200The register list was returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks view_cash_register or the pos_sale module is disabled.{ "message": string }
422The query string failed validation, only one date boundary was sent, or start_date is after end_date.Laravel validation JSON or { "message": string }.

Get cash register

Returns one visible cash-register session by id.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/cash-registers/{id}
PermissionSame permission and visibility rules as List cash registers.
CSV behaviorformat=csv streams one UTF-8 BOM row whose data_json column matches the JSON data payload.
Success response200 with one CashRegisterRow object.

Status codes

StatusWhen it happensResponse shape
200The cash register session was returned successfully.{ "data": CashRegisterRow } or CSV download.
403The token user lacks view_cash_register or the pos_sale module is disabled.{ "message": string }
404The register id does not exist in the visible session query.{ "message": "Not found" }
422The query string failed validation.Laravel validation JSON.

Get cash register details

Returns the visible register row, aggregate tender totals, and POS sales breakdown used by the register-details modal.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/cash-registers/{id}/details
PermissionSame permission and visibility rules as Get cash register.
Calculation basisUses CashRegisterUtil::getRegisterDetails() and CashRegisterUtil::getRegisterTransactionDetails(). The POS breakdown only includes finalized POS sells (type=sell, status=final, is_direct_sale=0) created by the register user between open and close time.
Types of service breakdowntypes_of_service_details is only populated when the types_of_service module is enabled for the business.
CSV behaviorformat=csv streams one UTF-8 BOM row whose data_json column matches the JSON data payload.
Success response200 with register detail objects.

CashRegisterDetailsSummary object

FieldTypeDescription
user_id, location_idinteger | nullUser and location ids for the register session.
user_name, email, location_name, closing_notestring | nullJoined display fields and the saved close note.
open_time, closed_atstring | nullISO-8601 timestamps for the session boundaries.
denominationsarray | object | string | nullDecoded denominations JSON when available, otherwise the raw stored value.
cash_in_handnumber | nullOpening float recorded through the initial cash-register transaction.
total_sale, total_expense, total_refundnumber | nullAggregated sell, expense, and refund movement totals for the register.
total_cash, total_cheque, total_card, total_bank_transfer, total_other, total_advance, total_custom_pay_1 ... total_custom_pay_7number | nullSales totals grouped by payment method.
total_cash_expense, total_cheque_expense, total_card_expense, total_bank_transfer_expense, total_other_expense, total_advance_expense, total_custom_pay_1_expense ... total_custom_pay_7_expensenumber | nullExpense totals grouped by payment method.
total_cash_refund, total_cheque_refund, total_card_refund, total_bank_transfer_refund, total_other_refund, total_advance_refund, total_custom_pay_1_refund ... total_custom_pay_7_refundnumber | nullRefund totals grouped by payment method.
total_cheques, total_card_slipsinteger | nullCounts of cheque and card-slip entries recorded in the session.

CashRegisterTransactionBreakdown object

FieldTypeDescription
product_details_by_brandarray<object>Each row contains brand_name, total_quantity, and total_amount for finalized POS sales grouped by brand.
product_detailsarray<object>Each row contains product_name, product_type, variation_name, product_variation_name, sku, total_quantity, and total_amount.
types_of_service_detailsarray<object> | nullWhen enabled, each row contains types_of_service_name and total_sales.
transaction_detailsobject | nullContains aggregated finalized POS totals: total_tax, total_discount, total_sales, and total_shipping_charges.

Top-level JSON response

FieldTypeDescription
data.registerCashRegisterRowThe same row shape returned by Get cash register.
data.summaryCashRegisterDetailsSummaryAggregate tender and refund totals for the register.
data.transaction_breakdownCashRegisterTransactionBreakdownPOS sales breakdown grouped by brand, product variation, and optionally type of service.

Status codes

StatusWhen it happensResponse shape
200The cash register details were returned successfully.{ "data": { "register": { ... }, "summary": { ... }, "transaction_breakdown": { ... } } } or CSV download.
403The token user lacks view_cash_register or the pos_sale module is disabled.{ "message": string }
404The register id is not visible or the register summary could not be generated.{ "message": "Not found" }
422The query string failed validation.Laravel validation JSON.

List cash register transactions

Lists the raw cash-register transaction lines for one visible session.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/cash-registers/{id}/transactions
PermissionSame permission and visibility rules as Get cash register.
Included rowsOrdered cash_register_transactions lines such as opening float, sell payments, transfers, expenses, and refunds.
CSV behaviorformat=csv streams all matching lines with a UTF-8 BOM and ignores page and per_page.
Success response200 with paginated CashRegisterTransactionRow rows.

Query parameters

ParameterTypeRequiredDescription
per_page, pageintegerNoPagination controls. per_page accepts 1 to 100 and defaults to 20.
formatstringNojson (default) or csv.

CashRegisterTransactionRow object

FieldTypeDescription
id, cash_register_idintegerTransaction-line id and owning register session id.
amountnumber | nullSigned amount stored on the register line.
pay_methodstring | nullPayment method such as cash, card, cheque, bank_transfer, or custom payment keys.
typestring | nullcredit or debit.
transaction_typestring | nullRegister line source, such as initial, sell, transfer, expense, or refund.
transaction_idinteger | nullOptional linked transaction id when the line came from a sale or another business transaction.
created_at, updated_atstring | nullISO-8601 timestamps for the register line.

Top-level JSON response

FieldTypeDescription
dataarray<CashRegisterTransactionRow>The current result page.
meta.cash_register_idintegerEchoes the requested register id.
meta.current_page, meta.last_page, meta.per_page, meta.totalintegerLaravel paginator metadata.

Status codes

StatusWhen it happensResponse shape
200The register transaction lines were returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks view_cash_register or the pos_sale module is disabled.{ "message": string }
404The register id is not visible in the register-session query.{ "message": "Not found" }
422The query string failed validation.Laravel validation JSON.

Open cash register

Opens a new cash-register session for the authenticated user.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/cash-registers/open
Permissionview_cash_register.
Module requirementThe business must have the pos_sale module enabled.
Demo modeReturns 403 in demo environments.
Open-session ruleThe authenticated user must not already have an open cash register.
Location rulelocation_id must belong to the business and be inside the token user's permitted locations when location restrictions apply.
Success response201 with the newly opened register id and location metadata.

Request body

FieldTypeRequiredDescription
location_idintegerYesBusiness location id for the register session.
initial_amountnumber | nullNoOptional opening float. When greater than zero, the controller creates an initial cash credit line.

Top-level JSON response

FieldTypeDescription
messagestringLocalized success message.
data.id, data.user_id, data.location_idintegerIdentifiers for the newly opened session.
data.location_namestring | nullResolved business-location name.
data.statusstringAlways open.
data.opened_atstring | nullISO-8601 timestamp for the session start.

Status codes

StatusWhen it happensResponse shape
201The cash register session was opened successfully.{ "message": string, "data": { ... } }
403Demo mode is active, the token user lacks view_cash_register, the pos_sale module is disabled, or the requested location is not allowed for the user.{ "message": string }
422The user already has an open cash register or the request body failed validation.Laravel validation JSON or { "message": string }.
500The open-register transaction failed unexpectedly.{ "message": "something_went_wrong" }

Close cash register

Closes an open cash-register session for the authenticated user or for a specified business user.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/cash-registers/close
Permissionclose_cash_register.
Module requirementThe business must have the pos_sale module enabled.
Demo modeReturns 403 in demo environments.
Target register ruleIf user_id is omitted, the controller closes the authenticated user's open register. When provided, it looks for an open register belonging to that business user.
Success response200 with the closed register summary row.

Request body

FieldTypeRequiredDescription
user_idinteger | nullNoOptional business user id whose open register should be closed.
closing_amountnumberYesClosing cash amount saved on the register.
total_card_slips, total_chequesinteger | nullNoOptional non-negative counts recorded during closing.
closing_notestring | nullNoOptional note up to 1000 characters.
denominationsarray | nullNoOptional denominations payload stored as JSON.

Top-level JSON response

FieldTypeDescription
messagestringLocalized close-success message.
data.id, data.user_id, data.location_idinteger | nullIdentifiers for the closed register session.
data.statusstringAfter success this is close.
data.opened_at, data.closed_atstring | nullISO-8601 timestamps for the register session.
data.closing_amountnumber | nullRecorded closing amount.
data.total_card_slips, data.total_chequesinteger | nullSaved closing counts.

Status codes

StatusWhen it happensResponse shape
200The cash register session was closed successfully.{ "message": string, "data": { ... } }
403Demo mode is active, the token user lacks close_cash_register, or the pos_sale module is disabled.{ "message": string }
422No open cash register exists for the target user or the request body failed validation.Laravel validation JSON or { "message": string }.
500The close-register transaction failed unexpectedly.{ "message": "something_went_wrong" }

List sales orders

Lists sales-order transactions that are visible to the token user.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/sales-orders
PermissionAt least one of so.view_all, so.view_own, or so.create.
Module requirementThe business must have POS settings -> Enable sales order enabled.
VisibilityRows are always limited to the token user's permitted locations. If the user lacks so.view_all but has so.view_own, only rows created by that user are returned.
CSV behaviorformat=csv streams all matching rows with a UTF-8 BOM and ignores page and per_page.
Success response200 with paginated SalesOrderRow rows.

Query parameters

ParameterTypeRequiredDescription
per_page, pageintegerNoPagination controls. per_page accepts 1 to 100 and defaults to 20.
location_idintegerNoFilters rows by business-location id.
contact_idintegerNoFilters rows by customer contact id.
statusstringNoRestricts rows to ordered, partial, or completed.
shipping_statusstringNoExact match against the stored sales-order shipping status.
start_date, end_datestringNoOptional Y-m-d date bounds applied to transaction_date only when both are present. end_date must be on or after start_date.
qstringNoMinimum 2 characters when sent. Matches invoice number, document, numeric id, and linked contact fields such as name, business name, mobile, and contact_id.
formatstringNojson (default) or csv.

SalesOrderRow object

FieldTypeDescription
idintegerSales-order transaction id.
invoice_no, documentstring | nullOrder reference fields stored on the transaction.
transaction_datestring | nullISO-8601 transaction timestamp.
status, shipping_statusstring | nullOrder workflow state and shipping state.
final_total, total_before_tax, tax_amount, discount_amountnumber | nullOrder totals from the transaction header.
so_qty_remainingnumber | nullRemaining uninvoiced quantity across parent sell lines.
contactobject | nullCustomer summary with id, name, mobile, contact_id, and supplier_business_name.
locationobject | nullBusiness-location summary with id and name.

Top-level JSON response

FieldTypeDescription
dataarray<SalesOrderRow>The current result page.
meta.current_page, meta.last_page, meta.per_page, meta.totalintegerLaravel paginator metadata.

Status codes

StatusWhen it happensResponse shape
200The sales-order list was returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks the required sales-order permission or sales orders are disabled in POS settings.{ "message": string }
422The query string failed validation or q was shorter than 2 characters.Laravel validation JSON or { "message": string }.

Get sales order

Returns one visible sales order with parent sell lines and remaining uninvoiced quantities.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/sales-orders/{id}
PermissionSame permission and visibility rules as List sales orders.
CSV behaviorformat=csv streams one UTF-8 BOM row whose data_json column matches the JSON data payload.
Success response200 with one SalesOrderDetail object.

SalesOrderLine object

FieldTypeDescription
id, product_id, variation_idintegerIdentifiers for the sales-order line and linked catalog records.
product_labelstringComputed product display label, including variation labels when available.
quantity, so_quantity_invoiced, quantity_remainingnumberOrdered quantity, already invoiced quantity, and remaining quantity for the line.
unit_price, unit_price_inc_taxnumber | nullStored unit prices excluding and including tax.
line_taxobject | nullOptional tax summary with id, name, and amount.

SalesOrderDetail object

FieldTypeDescription
idintegerSales-order transaction id.
invoice_no, documentstring | nullStored reference fields for the order.
transaction_datestring | nullISO-8601 transaction timestamp.
status, payment_status, shipping_statusstring | nullOrder workflow, payment state, and shipping state.
final_total, total_before_tax, tax_amount, discount_amountnumber | nullOrder totals from the transaction header.
additional_notesstring | nullSaved order notes.
contactobject | nullCustomer summary with id, name, mobile, email, contact_id, and supplier_business_name.
locationobject | nullBusiness-location summary with id and name.
order_taxobject | nullOrder-level tax summary with id, name, amount, and is_tax_group.
linesarray<SalesOrderLine>Parent sales-order lines sorted by line id.

Top-level JSON response

FieldTypeDescription
dataSalesOrderDetailThe requested sales-order payload.

Status codes

StatusWhen it happensResponse shape
200The sales order was returned successfully.{ "data": { ... } } or CSV download.
403The token user lacks the required sales-order permission or sales orders are disabled in POS settings.{ "message": string }
404The sales-order id does not exist in the visible query.{ "message": "Not found" }
422The query string failed validation.Laravel validation JSON.

Create sales order

Creates a new sales order. Fulfilment happens later when a normal sale references the order.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/sales-orders
Permissionso.create.
Module requirementThe business must have POS settings -> Enable sales order enabled.
Subscription and quotaThe business must be subscribed and still have invoice quota available.
Created transactionThe controller creates type = sales_order, status = ordered, and is_direct_sale = 1, then writes the sales-order lines and activity log.
Product ruleCombo products are rejected with 422.
Success response201 with the created id and invoice number.

Request body

FieldTypeRequiredDescription
contact_idintegerYesCustomer contact id. The contact must belong to the business and have type customer or both.
location_idintegerYesBusiness location id.
transaction_datestringYesOrder date accepted by Laravel date validation and normalized with uf_date(..., true).
tax_rate_idinteger | nullNoOptional business tax-rate id.
discount_typestring | nullNofixed or percentage. Defaults to fixed when omitted.
discount_amountnumber | nullNoOptional order-level discount amount.
sale_notestring | nullNoOptional internal note saved on the transaction.
invoice_nostring | nullNoOptional custom invoice/reference number, up to 191 characters.
shipping_details, shipping_addressstring | nullNoOptional shipping text fields.
shipping_statusstring | nullNoOptional shipping status string up to 64 characters.
shipping_chargesnumber | nullNoOptional shipping charge total.
productsarray<SalesOrderCreateLine>YesAt least one order line.

SalesOrderCreateLine object

FieldTypeRequiredDescription
product_idintegerYesProduct id.
variation_idintegerYesVariation id for the selected product.
quantitynumberYesOrdered quantity.
unit_pricenumberYesUnit price excluding tax.
unit_price_inc_taxnumberYesUnit price including tax.
item_taxnumberYesLine tax amount.
line_discount_typestring | nullNofixed or percentage.
line_discount_amountnumber | nullNoOptional line-level discount amount.

Top-level JSON response

FieldTypeDescription
messagestringLocalized create-success message.
data.idintegerThe created sales-order id.
data.invoice_nostring | nullThe stored invoice/reference number.

Status codes

StatusWhen it happensResponse shape
201The sales order was created successfully.{ "message": string, "data": { "id": integer, "invoice_no": string | null } }
402The business is not subscribed or has reached invoice quota.{ "message": string }
403Demo mode is active, the token user lacks so.create, or sales orders are disabled in POS settings.{ "message": string }
422The request body failed validation, the invoice-total calculation rejected the product payload, or a combo product was submitted.Laravel validation JSON or { "message": string }.
500The sales-order create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Update sales order

Updates an existing visible sales order and rewrites its line set.

Endpoint summary

PropertyValue
MethodPUT or PATCH
Path/api/v1/integration/sales-orders/{id}
Permissionso.update.
Module and subscriptionSales orders must be enabled in POS settings and the business must be subscribed.
VisibilityThe target row must be visible through the same location and own/all rules used by List sales orders.
Edit blockersThe order cannot be updated when a linked return exists or when the transaction falls outside the business edit window defined by transaction_edit_days.
Request bodySame payload as Create sales order, plus optional products.*.transaction_sell_lines_id for existing lines.
Success response200 with the updated id and invoice number.

Additional update-only field

FieldTypeRequiredDescription
products.*.transaction_sell_lines_idintegerNoExisting parent sales-order line id to update in place.

Top-level JSON response

FieldTypeDescription
messagestringLocalized update-success message.
data.idintegerThe updated sales-order id.
data.invoice_nostring | nullThe saved invoice/reference number after the update.

Status codes

StatusWhen it happensResponse shape
200The sales order was updated successfully.{ "message": string, "data": { "id": integer, "invoice_no": string | null } }
402The business is not subscribed.{ "message": string }
403Demo mode is active, the token user lacks so.update, or sales orders are disabled in POS settings.{ "message": string }
404The sales-order id does not exist in the visible query.{ "message": "Not found" }
422A linked return exists, the order is outside the allowed edit window, the request body failed validation, the invoice-total calculation rejected the product payload, or a combo product was submitted.Laravel validation JSON or { "message": string }.
500The sales-order update transaction failed unexpectedly.{ "message": string }

Delete sales order

Deletes a visible sales order by delegating to the normal sell-delete pipeline.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/sales-orders/{id}
Permissionso.delete.
Module requirementThe business must have sales orders enabled in POS settings.
Delete behaviorThe controller first verifies that the row is visible, then delegates to TransactionUtil::deleteSale().
Success response200 with the deleted id.

Top-level JSON response

FieldTypeDescription
messagestringDelete result message returned by the delete pipeline.
data.idintegerThe deleted sales-order id.

Status codes

StatusWhen it happensResponse shape
200The sales order was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode is active, the token user lacks so.delete, or sales orders are disabled in POS settings.{ "message": string }
404The sales-order id does not exist in the visible query.{ "message": "Not found" }
422The delete pipeline rejected the sales order because of business rules such as linked returns or other delete blockers.{ "message": string }

Update sales order status

Updates the workflow status on a visible sales order.

Endpoint summary

PropertyValue
MethodPUT or PATCH
Path/api/v1/integration/sales-orders/{id}/status
PermissionBusiness admin only.
Module requirementThe business must have sales orders enabled in POS settings.
Demo modeReturns 403 in demo environments.
Allowed statusesordered, partial, or completed.
Fulfilment noteStatus changes do not invoice the order. Fulfilment still happens when a normal sell references sales_order_ids.
Success response200 with the updated id and status.

Request body

FieldTypeRequiredDescription
statusstringYesNew workflow status: ordered, partial, or completed.

Top-level JSON response

FieldTypeDescription
messagestringLocalized success message.
data.idintegerThe sales-order id.
data.statusstringThe saved workflow status.

Status codes

StatusWhen it happensResponse shape
200The sales-order status was updated successfully.{ "message": string, "data": { "id": integer, "status": string } }
403Demo mode is active, the token user is not a business admin, or sales orders are disabled in POS settings.{ "message": string }
404The sales-order id does not exist in the visible query.{ "message": "Not found" }
422The request body failed validation.Laravel validation JSON.

List types of service

Lists types of service configured for the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/types-of-service
Permissionaccess_types_of_service.
Module requirementThe business must have the types_of_service module enabled.
Search behaviorWhen q is present it takes precedence over the legacy search parameter.
CSV behaviorformat=csv streams all matching rows with a UTF-8 BOM and ignores page and per_page.
Success response200 with paginated TypesOfServiceRow rows.

Query parameters

ParameterTypeRequiredDescription
per_page, pageintegerNoPagination controls. per_page accepts 1 to 100 and defaults to 20.
qstringNoPreferred search parameter. When sent, it must be at least 2 characters and matches name, description, or numeric id.
searchstringNoLegacy search parameter used only when q is absent.
sortstringNoname, packing_charge_type, or created_at. Defaults to name.
directionstringNoasc or desc. Defaults to asc.
formatstringNojson (default) or csv.

TypesOfServiceRow object

FieldTypeDescription
idintegerType-of-service id.
namestringConfigured type-of-service name.
descriptionstring | nullOptional long-form description.
location_price_grouparray | object | nullLocation-to-selling-price-group mapping, decoded from stored JSON when needed.
packing_chargenumber | nullPacking charge value normalized with the business number format.
packing_charge_typestring | nullfixed or percent.
enable_custom_fieldsbooleanWhether custom fields are enabled for this service type.
created_at, updated_atstring | nullISO-8601 timestamps.

Top-level JSON response

FieldTypeDescription
dataarray<TypesOfServiceRow>The current result page.
meta.current_page, meta.last_page, meta.per_page, meta.totalintegerLaravel paginator metadata.

Status codes

StatusWhen it happensResponse shape
200The type-of-service list was returned successfully.{ "data": [...], "meta": { ... } } or CSV download.
403The token user lacks access_types_of_service or the module is disabled.{ "message": string }
422The query string failed validation or q was shorter than 2 characters.Laravel validation JSON or { "message": string }.
500The list query failed unexpectedly.{ "message": "Could not list types of service" }

Get type of service

Returns one type of service by id.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/types-of-service/{id}
PermissionSame permission and module rules as List types of service.
CSV behaviorformat=csv streams one UTF-8 BOM row whose data_json column matches the JSON data payload.
Success response200 with one TypesOfServiceRow object.

Top-level JSON response

FieldTypeDescription
dataTypesOfServiceRowThe requested type-of-service payload.

Status codes

StatusWhen it happensResponse shape
200The type of service was returned successfully.{ "data": { ... } } or CSV download.
403The token user lacks access_types_of_service or the module is disabled.{ "message": string }
404The type-of-service id does not exist for this business.{ "message": "Not found" }
422The query string failed validation.Laravel validation JSON.

Create type of service

Creates a new type of service for the current business.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/types-of-service
Permissionaccess_types_of_service.
Module requirementThe business must have the types_of_service module enabled.
Demo modeReturns 403 in demo environments.
Default behaviorWhen packing_charge is omitted it is stored as 0. When enable_custom_fields is omitted it defaults to false.
Success response201 with one TypesOfServiceRow object.

Request body

FieldTypeRequiredDescription
namestringYesService-type name, up to 255 characters.
descriptionstring | nullNoOptional description, up to 5000 characters.
location_price_grouparray | object | nullNoOptional mapping where keys are location ids and values are selling-price-group ids.
packing_charge_typestring | nullNofixed or percent.
packing_chargestring | number | nullNoOptional charge value parsed with Util::num_uf().
enable_custom_fieldsbooleanNoWhether custom fields should be enabled for this service type.

Top-level JSON response

FieldTypeDescription
dataTypesOfServiceRowThe created type-of-service payload.

Status codes

StatusWhen it happensResponse shape
201The type of service was created successfully.{ "data": { ... } }
403Demo mode is active, the token user lacks access_types_of_service, or the module is disabled.{ "message": string }
422The request body failed validation.Laravel validation JSON.
500The type-of-service create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Update type of service

Updates an existing type of service for the current business.

Endpoint summary

PropertyValue
MethodPUT or PATCH
Path/api/v1/integration/types-of-service/{id}
Permissionaccess_types_of_service.
Module requirementThe business must have the types_of_service module enabled.
Demo modeReturns 403 in demo environments.
Update behaviorUses the same payload as Add type of service. When enable_custom_fields is omitted the existing value is left unchanged.
Success response200 with one TypesOfServiceRow object.

Request body

FieldTypeRequiredDescription
All Add type of service fieldsmixedSee aboveThe same request-body schema is reused for updates.

Top-level JSON response

FieldTypeDescription
dataTypesOfServiceRowThe updated type-of-service payload.

Status codes

StatusWhen it happensResponse shape
200The type of service was updated successfully.{ "data": { ... } }
403Demo mode is active, the token user lacks access_types_of_service, or the module is disabled.{ "message": string }
404The type-of-service id does not exist for this business.{ "message": "Not found" }
422The request body failed validation.Laravel validation JSON.
500The type-of-service update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete type of service

Deletes a type of service when it is not referenced by any transaction in the business.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/types-of-service/{id}
Permissionaccess_types_of_service.
Module requirementThe business must have the types_of_service module enabled.
Demo modeReturns 403 in demo environments.
Delete blockerThe delete is rejected when any transaction in the business already references transactions.types_of_service_id = {id}.
Success response200 with the deleted id.

Top-level JSON response

FieldTypeDescription
messagestringLocalized delete-success message.
data.idintegerThe deleted type-of-service id.

Status codes

StatusWhen it happensResponse shape
200The type of service was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode is active, the token user lacks access_types_of_service, or the module is disabled.{ "message": string }
404The type-of-service id does not exist for this business.{ "message": "Not found" }
422The service type is already referenced by at least one transaction.{ "message": string }
500The type-of-service delete transaction failed unexpectedly.{ "message": "something_went_wrong" }