Purchases
By Maxim
By Maxim
Purchases
Purchases: buying materials and products from suppliers. A purchase adds to stock at the storage location and updates material prices. Via the API you can create, edit and delete purchases, change the status and the payment status, and get a list of purchases and a purchase'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/purchases/add | Creates a purchase | | v1/purchases/edit | Updates a purchase | | v1/purchases/delete | Deletes a purchase | | v1/purchases/update_status | Changes the purchase status | | v1/purchases/update_payment | Changes the payment status | | v1/purchases/get_list | Returns the company's purchases | | v1/purchases/get_entry | Returns a purchase's details with its lines |
Creating a Purchase
Creates a purchase. The purchase status comes from the Default status for new purchases setting in the system settings, and the status determines whether materials and products are added to stock immediately (see Changing the Purchase Status). v1/purchases/add.php Parameters | Field | Type | Required | Description | |---|---|---|---| | supplier_id | int | supplier_id or supplier_name | ID of an existing supplier | | supplier_name | string | supplier_id or supplier_name | Supplier name. If there is none, a supplier is created from the supplier_ prefixed fields | | materials | array | materials or products | Material lines | | products | array | materials or products | Product lines | | date_placed | string | No | Purchase order date. Default today | | date_received | string | No | Received date | | materials_storage_id | int | No | Materials storage location. Default from settings | | products_storage_id | int | No | Products storage location. Default from settings | | delivery_price | float | No | Delivery cost. Default 0 | | discount | float | No | Discount. Default 0 | | notes | string | No | Notes | Purchase lines The purchase's lines are passed as two arrays: materials for materials and products for products. At least one line in one of the arrays is required. | Field | Type | Required | Description | |---|---|---|---| | id | int | id or sku | Material or product 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) | Unlike sales, an unknown item is not skipped: the request is rejected with an error and the purchase is not created. Supplier A supplier is specified in one of two ways: - supplier_id: the ID of an existing supplier, e.g. from v1/suppliers/add; - supplier_name: the supplier's name. If there is no supplier with that name, Controlata creates one together with the purchase from the fields below. | Field | Type | Description | |---|---|---| | supplier_name | string | Name | | supplier_email | string | Email | | supplier_phone | string | Phone | | supplier_address | string | Address | | supplier_agreement | string | Agreement | | supplier_manager_name | string | Manager | | supplier_manager_post | string | Manager's position | | supplier_notes | string | Supplier notes | If both are passed, supplier_id is used and supplier_* are ignored. If a supplier with that name already exists, the purchase is linked to it and supplier_* are not updated. Example request { "supplier_id": 45, "date_placed": "2026-10-06", "materials_storage_id": 3998, "materials": [ { "sku": "M010", "amount": 0.2, "total": 26000 }, { "id": 2052, "amount": 10, "total": 4000 } ], "delivery_price": 1500, "notes": "Invoice No. 118" } Example response { "success": true, "purchase_id": 29645, "status": 1 } status holds the code of the status the purchase was created in. Errors | Error | Reason | |---|---| | No supplier_name or supplier_id in input | No supplier was passed | | Supplier not found or access denied | The supplier_id supplier was not found or is deleted | | Supplier name must not be empty | Empty supplier_name | | Supplier name must be a string | supplier_name is not a string | | Storage ID not found | Storage location not found or deleted | | Materials is not an array | materials is not an array (same for products) | | No products or materials in input | No line was passed | | Line 0 of materials must be an object | The line is not an object (instead of 0, the line number) | | SKU or id not set for material 0 | The line has neither id nor sku | | Material with SKU "M010" not found | The item was not found or is deleted | | Amount must be greater than 0 for material with SKU "M010" | Quantity is missing, not a number or not above 0 after rounding | | Total must be 0 or greater for material with SKU "M010" | The line total is missing, not a number or below 0 | | Date is not a valid date in format YYYY-MM-DD | Invalid order or received 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 | In line error texts, material becomes product for product lines, and instead of SKU "M010" there is id 2052 if the line was passed by id.
Editing a Purchase
Updates a purchase. Only the fields you pass are changed, the rest stay as they were. v1/purchases/edit.php Parameters | Field | Type | Required | Description | |---|---|---|---| | purchase_id | int | Yes | Purchase ID | | supplier_id | int | No | Supplier ID | | supplier_name | string | No | Supplier name and the supplier_ fields | | materials | array | No | Material lines. Replace the current lines as a whole | | products | array | No | Product lines. Replace the current lines as a whole | | date_placed | string | No | Purchase order date | | date_received | string | No | Received date. An empty string clears it | | materials_storage_id | int | No | Materials storage location | | products_storage_id | int | No | Products storage location | | 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/purchases/update_status and v1/purchases/update_payment. Purchase lines Lines are passed as the materials and products 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 | Material or product 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/purchases/get_entry response can be sent back unchanged. Example request Change the delivery cost without passing lines: { "purchase_id": 29645, "delivery_price": 3000 } The line totals stay 26000 and 4000, while the cost becomes 28600 and 4400. Material prices update to the new cost. Replace the purchase's lines: { "purchase_id": 29645, "materials": [ { "sku": "M010", "amount": 0.25, "total": 32500 } ] } Example response { "success": true } Errors | Error | Reason | |---|---| | No purchase_id in input | purchase_id is missing | | Purchase not found or access denied | Purchase not found or deleted | | The purchase total is 0, so line totals cannot be restored. Pass materials and products explicitly | The purchase total is 0 and no lines were passed | | 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. An empty date_placed is also rejected | | 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 | | Materials is not an array | materials is not an array (same for products) | | No products or materials in input | Empty materials and products were passed | | Material with SKU "M010" not found | The item was not found or is deleted | | Supplier not found or access denied | The supplier_id supplier was not found or is deleted | | Supplier name must not be empty | Empty supplier_name | Other line errors are the same as in v1/purchases/add.
Deleting a Purchase
Deletes a purchase. v1/purchases/delete.php Parameters | Field | Type | Required | Description | |---|---|---|---| | purchase_id | int | Yes | Purchase ID | Deletion cannot be undone. Material prices and product costs updated by the purchase are not reverted to their previous values. Example request { "purchase_id": 29645 } Example response { "success": true } Errors | Error | Reason | |---|---| | No purchase_id in input | purchase_id is missing | | Purchase not found or access denied | Purchase not found or already deleted |
Changing the Purchase Status
Changes the purchase status. The status determines whether the purchase's materials and products are added to stock. v1/purchases/update_status.php Parameters | Field | Type | Required | Description | |---|---|---|---| | purchase_id | int | Yes | Purchase ID | | status | int | Yes | New status: 0 — Plan, 1 — Ordered, 3 — Received | | date_received | string | No | Received date. Used when moving to the Received status | The Partially received status (2) cannot be set via the API: it appears when part of the purchase is received as shipments in the Controlata interface. date_received is used when moving to Received: it is written to the purchase and the stock entry is dated to it. In other statuses date_received is ignored. Example request { "purchase_id": 29645, "status": 3, "date_received": "2026-10-09" } Example response { "success": true } Errors | Error | Reason | |---|---| | No purchase_id in input | A required field is missing (instead of purchase_id, the field's name) | | Invalid status value. Must be one of: 0, 1, 3 | Invalid status, including 2 | | Purchase not found or access denied | Purchase not found or deleted | | Date is not a valid date in format YYYY-MM-DD | Invalid date_received |
Changing the Payment Status
Changes the purchase's payment status. The payment status only marks whether the purchase has been paid to the supplier: it does not affect stock, amounts or the purchase status. v1/purchases/update_payment.php Parameters | Field | Type | Required | Description | |---|---|---|---| | purchase_id | int | Yes | Purchase ID | | status | int | Yes | Payment status: 0 — Unpaid, 1 — Partially paid, 2 — Paid | The payment status is shown in the purchases list if Show purchase payment status is on under Settings → General. Via the API it is changed and returned regardless of that setting. Example request { "purchase_id": 29645, "status": 2 } Example response { "success": true } Errors | Error | Reason | |---|---| | No purchase_id in input | A required field is missing (instead of purchase_id, the field's name) | | Invalid payment status value. Must be one of: 0, 1, 2 | Invalid payment status | | Purchase not found or access denied | Purchase not found or deleted |
Retrieving a List of Purchases
Returns the company's purchases, from newest to oldest by purchase order date. Purchase lines are not included in the list; they are returned by v1/purchases/get_entry. v1/purchases/get_list.php Parameters | Field | Type | Required | Description | |---|---|---|---| | date_from | string | No | Purchases from this order date inclusive | | date_to | string | No | Purchases up to this order date inclusive | | supplier_id | int | No | Only this supplier's purchases | | limit | int | No | Page size, from 1 to 1000 | | offset | int | No | How many records to skip | The date filters work on date_placed. Without parameters, all the company's purchases are returned. See Common Rules for pagination. Response An array purchases with the purchase's fields: | Field | Type | Description | |---|---|---| | id | int | Purchase ID | | status | int | Status, a code from Changing the Purchase Status | | payment | int | Payment status, a code from Changing the Payment Status | | date_placed | string | Purchase order date | | date_received | string | Received date. Empty string if not set | | supplier_id | int | Supplier ID | | supplier_name | string | Supplier name | | materials_storage_id | int | Materials storage location | | materials_storage_name | string | Materials storage location name | | products_storage_id | int | Products storage location | | products_storage_name | string | Products storage location name | | subtotal | float | Sum of the lines | | delivery_price | float | Delivery cost | | discount | float | Discount | | total | float | Total: subtotal plus delivery_price minus discount | | amount | float | Total quantity if all lines share one unit, otherwise null | | lines | int | Number of lines | | notes | string | Notes | The total field in the response root holds the total number of purchases matching the filters. Example request { "date_from": "2026-10-01", "supplier_id": 45, "limit": 100, "offset": 0 } Example response { "success": true, "purchases": [ { "id": 29645, "status": 1, "payment": 0, "date_placed": "2026-10-06", "date_received": "", "supplier_id": 45, "supplier_name": "Woodtrade Ltd", "materials_storage_id": 3998, "materials_storage_name": "Main location", "products_storage_id": 3998, "products_storage_name": "Main location", "subtotal": 30000, "delivery_price": 1500, "discount": 0, "total": 31500, "amount": null, "lines": 2, "notes": "Invoice No. 118" } ], "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 Purchase
Returns a purchase's details and its lines. v1/purchases/get_entry.php Parameters | Field | Type | Required | Description | |---|---|---|---| | purchase_id | int | Yes | Purchase ID | Response An object purchase with the purchase's fields: | Field | Type | Description | |---|---|---| | id | int | Purchase ID | | status | int | Status, a code from Changing the Purchase Status | | payment | int | Payment status, a code from Changing the Payment Status | | date_placed | string | Purchase order date | | date_received | string | Received date. Empty string if not set | | supplier_id | int | Supplier ID | | supplier_name | string | Supplier name | | materials_storage_id | int | Materials storage location | | materials_storage_name | string | Materials storage location name | | products_storage_id | int | Products storage location | | products_storage_name | string | Products storage location name | | subtotal | float | Sum of the lines | | delivery_price | float | Delivery cost | | discount | float | Discount | | total | float | Total: subtotal plus delivery_price minus discount | | amount | float | Total quantity if all lines share one unit, otherwise null | | lines | int | Number of lines | | notes | string | Notes | And two arrays of purchase lines: materials and products. Line fields: | Field | Type | Description | |---|---|---| | id | int | Material or product 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 purchase card, from 0 | The position numbering is shared across both arrays: it restores the line order as in the purchase card. The lines from the response can be sent to v1/purchases/edit unchanged: extra fields are ignored. Example request { "purchase_id": 29645 } Example response { "success": true, "purchase": { "id": 29645, "status": 1, "payment": 0, "date_placed": "2026-10-06", "date_received": "", "supplier_id": 45, "supplier_name": "Woodtrade Ltd", "materials_storage_id": 3998, "materials_storage_name": "Main location", "products_storage_id": 3998, "products_storage_name": "Main location", "subtotal": 30000, "delivery_price": 1500, "discount": 0, "total": 31500, "amount": null, "lines": 2, "notes": "Invoice No. 118", "materials": [ { "id": 2051, "sku": "M010", "name": "Oak board", "amount": 0.2, "cost": 27300, "unit": "cu m", "position": 0, "total": 26000, "price": 130000 }, { "id": 2052, "sku": "M011", "name": "Wood glue", "amount": 10, "cost": 4200, "unit": "kg", "position": 1, "total": 4000, "price": 400 } ], "products": [] } } Errors | Error | Reason | |---|---| | No purchase_id in input | purchase_id is missing | | Purchase not found or access denied | Purchase not found or deleted |