Sales
By Maxim
By Maxim
Sales
Sales: selling products, and materials when the setting is on, to your customers. Via the API you can create, edit and delete them, change the status and the payment status, and get a list of sales and a sale's details. Method paths are given relative to the base API URL; the rules common to all methods (authorization, request format, partial editing, pagination) are described in API Overview and Common Rules. Methods | Method | What it does | |---|---| | v1/orders/add | Creates a sale | | v1/orders/edit | Updates a sale | | v1/orders/delete | Deletes a sale | | v1/orders/update_status | Changes the sale status | | v1/orders/update_payment | Changes the payment status | | v1/orders/get_list | Returns the company's sales | | v1/orders/get_entry | Returns a sale's details with its lines |
Creating a Sale
Creates a sale. The sale status comes from the Default status for new sales setting in the system settings, and the status determines whether products and materials are deducted immediately (see Changing the Sale Status). v1/orders/add.php Parameters | Field | Type | Required | Description | |---|---|---|---| | num | string | Yes | Sale number without the prefix | | products | array | products or materials | Product lines | | materials | array | products or materials | Material lines. Accepted when the Enable ordering materials setting is on | | customer_id | int | customer_id or customer_name | ID of an existing customer | | customer_name | string | customer_id or customer_name | Customer name. If there is none, a customer is created from the customer_ prefixed fields | | date_placed | string | No | Sale date. Default today | | date_shipped | string | No | Shipping date | | products_storage_id | int | No | Products storage location. Default from settings | | materials_storage_id | int | No | Materials storage location. Default from settings | | production | int | No | 1 to fulfill the sale by made-to-order production. Default 0 | | delivery_price | float | No | Delivery cost. Default 0 | | discount | float | No | Discount. Default 0 | | notes | string | No | Notes | The Default fulfillment method for new sales setting does not affect the API: without production, the sale is fulfilled from stock. Sale lines The sale's lines are passed as two arrays: products for products and materials for materials. At least one line in one of the arrays is required. | Field | Type | Required | Description | |---|---|---|---| | id | int | id or sku | Product or material ID | | sku | string | id or sku | SKU. A product is also matched by its alternative SKUs | | amount | float | Yes | Quantity | | total | float | Yes | Line total (not the price per unit) | If an item is not found, the line is skipped and a message is appended to the sale notes, for example: Product with SKU "P099", amount 2, total 5000 not found in Controlata, check sku. If no line is found, the sale is not created. Materials in sales are accepted only if Enable ordering materials is on under Settings → General. Otherwise a request with materials is rejected. Customer A customer is specified in one of two ways: - customer_id: the ID of an existing customer, e.g. from v1/customers/add; - customer_name: the customer's name. If there is no customer with that name, Controlata creates one together with the sale from the fields below. | Field | Type | Description | |---|---|---| | customer_name | string | Name | | customer_email | string | Email | | customer_phone | string | Phone | | customer_address_real | string | Delivery address | | customer_address_legal | string | Legal address | | customer_agreement | string | Agreement | | customer_manager_name | string | Manager | | customer_manager_post | string | Manager's position | | customer_notes | string | Customer notes | If both are passed, customer_id is used and customer_* are ignored. If a customer with that name already exists, the sale is linked to it and customer_* are not updated. Sale number Controlata adds a prefix from the Sale number prefix setting in the API connection. With the prefix "A-", the number "1001" you pass is saved as "A-1001" and returned as "A-1001". Example request { "num": "1001", "date_placed": "2026-10-06", "customer_name": "John Smith", "customer_phone": "+1 917 555-0145", "customer_email": "john@example.com", "customer_address_real": "5 Forest St, Apt 12, New York", "products": [ { "sku": "P001", "amount": 2, "total": 26000 }, { "id": 816, "amount": 1, "total": 4500 } ], "delivery_price": 500, "discount": 1000, "notes": "Order from the website" } Example response { "success": true, "order_id": 43727, "customer_id": 512 } customer_id holds the sale's customer ID: the one found by customer_id or created from customer_name. Errors | Error | Reason | |---|---| | No num in input | num is missing or empty | | Products is not an array | products is not an array (same for materials) | | No products or materials in input | No line was passed | | Materials in orders are disabled in company settings | Materials were passed, but the Enable ordering materials setting is off | | Storage ID is not set | An empty storage location was passed | | Storage ID not found | Storage location not found or deleted | | Date is not a valid date in format YYYY-MM-DD | Invalid sale or shipping date | | Invalid delivery_price value. Must be a number, 0 or greater | Delivery cost is not a number or below 0 | | Invalid discount value. Must be a number, 0 or greater | Discount is not a number or below 0 | | Line 0 of products must be an object | The line is not an object (instead of 0, the line number) | | SKU or id not set for product 0 | The line has neither id nor sku | | Amount not set for product with SKU "P001" | The line has no amount | | Total not set for product with SKU "P001" | The line has no total | | Amount must be greater than 0 for product with SKU "P001" | Quantity is not a number or not above 0 after rounding | | Total must be a number for product with SKU "P001" | The line total is not a number | | All products have wrong SKU or id | No line was found. If materials were passed, the text is "All products and materials have wrong SKU or id" | | No customer_name or customer_id in input | No customer was passed | | Customer not found | The customer_id customer was not found or is deleted | | Customer name must not be empty | Empty customer_name | | Customer name must be a string | customer_name is not a string | In line error texts, product becomes material for material lines, and instead of SKU "P001" there is id 816 if the line was passed by id.
Editing a Sale
Updates a sale. Only the fields you pass are changed, the rest stay as they were. v1/orders/edit.php Parameters | Field | Type | Required | Description | |---|---|---|---| | order_id | int | Yes | Sale ID | | num | string | No | Sale number without the prefix | | products | array | No | Product lines. Replace the current lines as a whole | | materials | array | No | Material lines. Replace the current lines as a whole | | customer_id | int | No | Customer ID | | customer_name | string | No | Customer name and the customer_ fields | | date_placed | string | No | Sale date | | date_shipped | string | No | Shipping date. An empty string clears it | | products_storage_id | int | No | Products storage location | | materials_storage_id | int | No | Materials storage location | | production | int | No | 1 turns on made-to-order production, 0 turns it off | | delivery_price | float | No | Delivery cost | | discount | float | No | Discount | | notes | string | No | Notes | The status and the payment status are not changed by this method; use v1/orders/update_status and v1/orders/update_payment. Sale lines Lines are passed as the products and materials arrays. If at least one of the arrays is passed, the lines are replaced as a whole, and the array that is not passed is treated as empty. If neither is passed, the lines are unchanged. | Field | Type | Required | Description | |---|---|---|---| | id | int | id or sku | Product or material ID | | sku | string | id or sku | SKU. A product is also matched by its alternative SKUs | | amount | float | Yes | Quantity | | total | float | Yes | Line total (not the price per unit) | The lines from the v1/orders/get_entry response can be sent back unchanged. Example request Add a discount and change the notes without touching the lines or the customer: { "order_id": 43727, "discount": 1500, "notes": "Discount by promo code" } Replace the sale's lines: { "order_id": 43727, "products": [ { "sku": "P001", "amount": 3, "total": 39000 } ] } Example response { "success": true } Errors | Error | Reason | |---|---| | No order_id in input | order_id is missing | | Order not found or access denied | Sale not found or deleted | | Products is not an array | products is not an array (same for materials) | | No products or materials in input | Empty products and materials were passed | | Materials in orders are disabled in company settings | Materials were passed, but the Enable ordering materials setting is off | | All products have wrong SKU or id | No line was found (with materials, "All products and materials have wrong SKU or id") | | Storage ID is not set | An empty storage location was passed | | Storage ID not found | Storage location not found or deleted | | Date is not a valid date in format YYYY-MM-DD | Invalid date | | Invalid delivery_price value. Must be a number, 0 or greater | Delivery cost is not a number or below 0 | | Invalid discount value. Must be a number, 0 or greater | Discount is not a number or below 0 | | Customer not found | The customer_id customer was not found or is deleted | | Customer name must not be empty | Empty customer_name | Errors in individual lines (no amount or total, invalid quantity) are the same as in v1/orders/add.
Deleting a Sale
Deletes a sale. v1/orders/delete.php Parameters | Field | Type | Required | Description | |---|---|---|---| | order_id | int | Yes | Sale ID | Deletion cannot be undone. To keep the sale in history but remove its effect on stock, move it to the Canceled status with v1/orders/update_status. Example request { "order_id": 43727 } Example response { "success": true } Errors | Error | Reason | |---|---| | No order_id in input | order_id is missing | | Order not found or access denied | Sale not found or already deleted |
Changing the Sale Status
Changes the sale status. The status determines whether the sale's products and materials are deducted from stock. v1/orders/update_status.php Parameters | Field | Type | Required | Description | |---|---|---|---| | order_id | int | Yes | Sale ID | | status | int | Yes | New status: 0 — New, 1 — Packed, 3 — Shipped, 4 — Canceled | | date_shipped | string | No | Shipping date. Used when moving to the Shipped status | The Partially shipped status (2) cannot be set via the API: it appears when part of the sale is shipped in the Controlata interface. date_shipped is used when moving to Shipped: it is written to the sale and the deduction is dated to it. In other statuses date_shipped is ignored. Example request { "order_id": 43727, "status": 3, "date_shipped": "2026-10-08" } Example response { "success": true } Errors | Error | Reason | |---|---| | No order_id in input | A required field is missing (instead of order_id, the field's name) | | Invalid status value. Must be one of: 0, 1, 3, 4 | Invalid status, including 2 | | Order not found or access denied | Sale not found or deleted | | Date is not a valid date in format YYYY-MM-DD | Invalid date_shipped |
Changing the Payment Status
Changes the sale's payment status. The payment status only marks whether money has been received from the customer: it does not affect stock, amounts or the sale status. v1/orders/update_payment.php Parameters | Field | Type | Required | Description | |---|---|---|---| | order_id | int | Yes | Sale ID | | status | int | Yes | Payment status: 0 — Unpaid, 1 — Partially paid, 2 — Paid | The payment status is shown in the sales list if Show sale payment status is on under Settings → General. Via the API it is changed and returned regardless of that setting. Example request { "order_id": 43727, "status": 2 } Example response { "success": true } Errors | Error | Reason | |---|---| | No order_id in input | A required field is missing (instead of order_id, the field's name) | | Invalid payment status value. Must be one of: 0, 1, 2 | Invalid payment status | | Order not found or access denied | Sale not found or deleted |
Retrieving a List of Sales
Returns the company's sales, from newest to oldest: by sale date, and within one date by number. Sale lines are not included in the list; they are returned by v1/orders/get_entry. v1/orders/get_list.php Parameters | Field | Type | Required | Description | |---|---|---|---| | date_from | string | No | Sales from this sale date inclusive | | date_to | string | No | Sales up to this sale date inclusive | | limit | int | No | Page size, from 1 to 1000 | | offset | int | No | How many records to skip | The filters work on date_placed. Without parameters, all the company's sales are returned. See Common Rules for pagination. Response An array orders. Each object holds the sale's fields: | Field | Type | Description | |---|---|---| | id | int | Sale ID | | num | string | Sale number with the prefix | | status | int | Status, a code from Changing the Sale Status | | payment | int | Payment status, a code from Changing the Payment Status | | date_placed | string | Sale date | | date_shipped | string | Shipping date. Empty string if not set | | subtotal | float | Sum of the lines | | delivery_price | float | Delivery cost | | discount | float | Discount | | total | float | Total: subtotal plus delivery_price minus discount | | cost | float | Sale cost | | amount | float | Total quantity if all lines share one unit, otherwise null | | lines | int | Number of lines | | production | int | 1 if the sale is fulfilled by made-to-order production | | production_num | string | Linked production number, null without production | | source | string | Where the sale came from: "manual", "api", "shopify" | | notes | string | Notes | | products_storage_id | int | Products storage location | | products_storage_name | string | Products storage location name | | materials_storage_id | int | Materials storage location | | materials_storage_name | string | Materials storage location name | | customer_id | int | Customer ID | | customer_name | string | Name | | customer_email | string | Email | | customer_phone | string | Phone | | customer_address_real | string | Delivery address | | customer_address_legal | string | Legal address | | customer_agreement | string | Agreement | | customer_manager_name | string | Manager | | customer_manager_post | string | Manager's position | | customer_notes | string | Customer notes | The total field in the response root holds the total number of sales matching the filters. Example request { "date_from": "2026-10-01", "date_to": "2026-10-31", "limit": 100, "offset": 0 } Example response { "success": true, "orders": [ { "id": 43727, "num": "A-1001", "status": 3, "payment": 2, "date_placed": "2026-10-06", "date_shipped": "2026-10-08", "subtotal": 30500, "delivery_price": 500, "discount": 1000, "total": 30000, "cost": 17800, "amount": 3, "lines": 2, "production": 0, "production_num": null, "source": "api", "notes": "Order from the website", "products_storage_id": 3998, "products_storage_name": "Main location", "materials_storage_id": 3998, "materials_storage_name": "Main location", "customer_id": 512, "customer_name": "John Smith", "customer_email": "john@example.com", "customer_phone": "+1 917 555-0145", "customer_address_real": "5 Forest St, Apt 12, New York", "customer_address_legal": "", "customer_agreement": "", "customer_manager_name": "", "customer_manager_post": "", "customer_notes": "" } ], "total": 1 } Errors | Error | Reason | |---|---| | Date is not a valid date in format YYYY-MM-DD | Invalid date_from or date_to | | Invalid limit value. Must be between 1 and 1000 | Invalid limit | | Invalid offset value. Must be 0 or greater | Invalid offset | | Offset requires limit | offset passed without limit |
Retrieving a Sale
Returns a sale's details, the customer and the sale lines. v1/orders/get_entry.php Parameters | Field | Type | Required | Description | |---|---|---|---| | order_id | int | Yes | Sale ID | Response An object order with the sale's fields: | Field | Type | Description | |---|---|---| | id | int | Sale ID | | num | string | Sale number with the prefix | | status | int | Status, a code from Changing the Sale Status | | payment | int | Payment status, a code from Changing the Payment Status | | date_placed | string | Sale date | | date_shipped | string | Shipping date. Empty string if not set | | subtotal | float | Sum of the lines | | delivery_price | float | Delivery cost | | discount | float | Discount | | total | float | Total: subtotal plus delivery_price minus discount | | cost | float | Sale cost | | amount | float | Total quantity if all lines share one unit, otherwise null | | lines | int | Number of lines | | production | int | 1 if the sale is fulfilled by made-to-order production | | production_num | string | Linked production number, null without production | | source | string | Where the sale came from: "manual", "api", "shopify" | | notes | string | Notes | | products_storage_id | int | Products storage location | | products_storage_name | string | Products storage location name | | materials_storage_id | int | Materials storage location | | materials_storage_name | string | Materials storage location name | | customer_id | int | Customer ID | | customer_name | string | Name | | customer_email | string | Email | | customer_phone | string | Phone | | customer_address_real | string | Delivery address | | customer_address_legal | string | Legal address | | customer_agreement | string | Agreement | | customer_manager_name | string | Manager | | customer_manager_post | string | Manager's position | | customer_notes | string | Customer notes | And two arrays of sale lines: products and materials. Line fields: | Field | Type | Description | |---|---|---| | id | int | Product or material ID | | sku | string | SKU | | name | string | Name | | amount | float | Quantity | | unit | string | Unit of measure | | total | float | Line total | | price | float | Price per unit: total divided by amount | | cost | float | Line cost | | position | int | Line number in the sale, from 0 | The position numbering is shared across both arrays: it restores the line order as in the sale card. The lines from the response can be sent to v1/orders/edit unchanged: extra fields are ignored. Example request { "order_id": 43727 } Example response { "success": true, "order": { "id": 43727, "num": "A-1001", "status": 3, "payment": 2, "date_placed": "2026-10-06", "date_shipped": "2026-10-08", "subtotal": 30500, "delivery_price": 500, "discount": 1000, "total": 30000, "cost": 17800, "amount": 3, "lines": 2, "production": 0, "production_num": null, "source": "api", "notes": "Order from the website", "products_storage_id": 3998, "products_storage_name": "Main location", "materials_storage_id": 3998, "materials_storage_name": "Main location", "customer_id": 512, "customer_name": "John Smith", "customer_email": "john@example.com", "customer_phone": "+1 917 555-0145", "customer_address_real": "5 Forest St, Apt 12, New York", "customer_address_legal": "", "customer_agreement": "", "customer_manager_name": "", "customer_manager_post": "", "customer_notes": "", "products": [ { "id": 815, "sku": "P001", "name": "Oak table", "amount": 2, "unit": "pcs", "total": 26000, "price": 13000, "cost": 15400, "position": 0 }, { "id": 816, "sku": "P002", "name": "Oak stool", "amount": 1, "unit": "pcs", "total": 4500, "price": 4500, "cost": 2400, "position": 1 } ], "materials": [] } } Errors | Error | Reason | |---|---| | No order_id in input | order_id is missing | | Order not found or access denied | Sale not found or deleted |