Home Integration API reference
REST API for integrations and external apps
Products
GET https://billdiary.com/api/v1/integration/products
Returns a paginated product index for the authenticated token’s business. This endpoint is designed for listing and search, so each product is returned as a compact summary row rather than a full nested product document.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/products |
| Authentication | Bearer token or API key + secret required. |
| Permission | product.view or product.create |
| Business scope | Only products belonging to the token user’s business_id are returned. |
| Ordering | Alphabetical by product name ascending. |
| Exclusions | Products with type = modifier are never included. |
| Pagination | JSON responses are paginated. CSV responses return all matching rows in one streamed file. |
| Response shape | JSON: top-level data + meta. CSV: flat columns only, no JSON wrapper. |
| Header | Required | Description |
|---|---|---|
Authorization |
Yes | Bearer YOUR_ACCESS_TOKEN |
Accept |
No | Recommended: application/json. CSV output is controlled by format=csv, not by the Accept header. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format |
string | No | Response format. Allowed values: json (default) or csv. When csv is used, the endpoint streams all matching rows, ignores page / per_page, and downloads a file named products-{business_id}-{timestamp}.csv. |
per_page |
integer | No | JSON page size. Minimum 1, maximum 100, default 20. |
page |
integer | No | 1-based page number for JSON pagination. |
q |
string | No | Search term. Minimum 2 characters when provided, maximum 120. Matches product name, product sku, or any variation sub_sku. SQL wildcard characters are escaped, so % and _ are treated literally. When q is present, only active products (is_inactive = false) are returned. |
| Field | Type | Description |
|---|---|---|
data |
array<ProductListItem> | The current page of product summary rows. |
meta |
object | Pagination metadata for the JSON response. This endpoint does not return Laravel’s default paginator links structure. |
Each element of data is a flat product summary row. It is intentionally smaller than the Get product response.
| Field | Type | Description |
|---|---|---|
id |
integer | Product primary key. |
name |
string | Product display name. |
sku |
string | Product-level SKU. For variable products, this may be less useful than the variation SKU returned below or in Get product. |
type |
string | Product type as stored in products.type (for example single or variable). |
enable_stock |
boolean | Whether stock tracking is enabled for the product. |
is_inactive |
boolean | Whether the product is marked inactive. |
not_for_selling |
boolean | Whether the product is blocked from sell / POS flows. |
unit_name |
string | null | The linked unit’s actual_name, if a unit exists. |
category_name |
string | null | Category name, if assigned. |
brand_name |
string | null | Brand name, if assigned. |
variation_sub_sku |
string | null | The sub_sku of the first loaded variation. For products with multiple variations, this is only a convenience field, not a complete variation list. |
sell_price_ex_tax |
PriceRange | Minimum and maximum default_sell_price across the product’s loaded variations. |
sell_price_inc_tax |
PriceRange | Minimum and maximum sell_price_inc_tax across the product’s loaded variations. |
| Field | Type | Description |
|---|---|---|
min |
number | null | Lowest value found across the product’s variations for that price field. |
max |
number | null | Highest value found across the product’s variations for that price field. |
| Field | Type | Description |
|---|---|---|
current_page |
integer | Current page number. |
last_page |
integer | Last available page number. |
per_page |
integer | Actual page size used for this response. |
total |
integer | Total number of products matching the current filter. |
q |
string | null | The applied search term, or null when no search filter was sent. |
When format=csv is sent, the response is a streamed file and the JSON wrapper is not returned.
| Column | Type in CSV cell | Description |
|---|---|---|
id | integer | Matches JSON data[].id. |
name | string | Matches JSON data[].name. |
sku | string | Matches JSON data[].sku. |
type | string | Matches JSON data[].type. |
enable_stock | boolean | Matches JSON data[].enable_stock. |
is_inactive | boolean | Matches JSON data[].is_inactive. |
not_for_selling | boolean | Matches JSON data[].not_for_selling. |
unit_name | string | null | Matches JSON data[].unit_name. |
category_name | string | null | Matches JSON data[].category_name. |
brand_name | string | null | Matches JSON data[].brand_name. |
variation_sub_sku | string | null | Matches JSON data[].variation_sub_sku. |
sell_price_ex_tax | JSON string | Serialized version of the JSON price range object. |
sell_price_inc_tax | JSON string | Serialized version of the JSON price range object. |
| Status | When it happens | Response shape |
|---|---|---|
200 |
Successful JSON list response or CSV stream. | JSON: data + meta. CSV: downloaded file. |
403 |
The token user lacks both product.view and product.create. |
{ "message": "Unauthorized" } |
422 |
Invalid query parameters, such as per_page=0, an unsupported format, or q shorter than 2 characters. |
Validation JSON with message; framework validation errors may also include an errors object. |
curl -s -G "https://billdiary.com/api/v1/integration/products" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json" \ --data-urlencode "page=1" \ --data-urlencode "per_page=20" \ --data-urlencode "q=milk"
curl -G "https://billdiary.com/api/v1/integration/products" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ --data-urlencode "format=csv" \ --data-urlencode "q=milk" \ -o products.csv
{
"data": [
{
"id": 1,
"name": "Example",
"sku": "SKU001",
"type": "single",
"enable_stock": true,
"is_inactive": false,
"not_for_selling": false,
"unit_name": "Pc(s)",
"category_name": null,
"brand_name": null,
"variation_sub_sku": "SKU001",
"sell_price_ex_tax": { "min": 10.0, "max": 10.0 },
"sell_price_inc_tax": { "min": 10.0, "max": 10.0 }
}
],
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 20,
"total": 1,
"q": null
}
}
{
"message": "Unauthorized"
}
{
"message": "Query parameter q must be at least 2 characters when provided."
}
Checks whether a candidate SKU is available inside the authenticated user’s business. The lookup compares both product-level sku values and variation-level sub_sku values, matching the same duplicate-check rules used by the web product form.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/products/check-sku |
| Authentication | Bearer token or API key + secret required. |
| Permission | product.create or product.update |
| Business scope | Checks only product and variation SKUs belonging to the token user’s business. |
| Comparison scope | Checks product sku first, then variation sub_sku if no product-level match is found. |
| Edit support | Use product_id and/or variation_id to exclude the current record while editing. |
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_ACCESS_TOKEN |
Accept | No | Recommended: application/json. |
| Parameter | Type | Required | Description |
|---|---|---|---|
sku |
string | No | Candidate SKU to check. Leading and trailing spaces are trimmed. If omitted or blank, the endpoint checks whether a blank SKU / sub-SKU would collide, matching the web behaviour. |
product_id |
integer | No | Exclude this product record from the comparison. Useful when editing an existing product. |
variation_id |
integer | No | Exclude this specific variation row from the sub_sku comparison. Useful when editing a variable product variation. |
| Field | Type | Description |
|---|---|---|
data | object | Availability result. |
| Field | Type | Description |
|---|---|---|
available |
boolean | true when the SKU is unused in the business after applying any exclusions; otherwise false. |
sku |
string | null | The normalized candidate SKU that was checked. Returns null when the incoming value is blank. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Availability check completed successfully. | { "data": { "available": boolean, "sku": string | null } } |
403 | The token user lacks both product.create and product.update. | { "message": "Unauthorized" } |
422 | Invalid parameter types, such as non-integer product_id or variation_id. | Laravel validation JSON with message and typically an errors object. |
curl -s -G "https://billdiary.com/api/v1/integration/products/check-sku" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json" \ --data-urlencode "sku=SKU001" \ --data-urlencode "product_id=12" \ --data-urlencode "variation_id=45"
{
"data": {
"available": false,
"sku": "SKU001"
}
}
Checks whether an exact product name is already in use inside the authenticated user’s business. This endpoint is intended for create/edit validation and follows the same duplicate-name logic as the web product form.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/products/check-name |
| Authentication | Bearer token or API key + secret required. |
| Permission | product.create or product.update |
| Business scope | Checks only product names in the token user’s business. |
| Comparison rule | Exact match against products.name after trimming the provided value. |
| Edit support | Use product_id to exclude the current product while editing. |
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_ACCESS_TOKEN |
Accept | No | Recommended: application/json. |
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | No | Candidate product name to check. Leading and trailing spaces are trimmed. When blank, the response checks whether a blank name would collide. |
product_id |
integer | No | Exclude this product id from the comparison when editing an existing product. |
| Field | Type | Description |
|---|---|---|
available | boolean | true when no other product in the business uses the same name after exclusions. |
name | string | null | The normalized candidate name that was checked. Returns null when the incoming value is blank. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Name check completed successfully. | { "data": { "available": boolean, "name": string | null } } |
403 | The token user lacks both product.create and product.update. | { "message": "Unauthorized" } |
422 | Invalid parameter types, such as a non-integer product_id. | Laravel validation JSON with message and typically an errors object. |
curl -s -G "https://billdiary.com/api/v1/integration/products/check-name" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json" \ --data-urlencode "name=Example Product" \ --data-urlencode "product_id=12"
{
"data": {
"available": true,
"name": "Example Product"
}
}
Returns the full product detail document for a single product in the authenticated user’s business. This is the canonical detail endpoint for product records and includes linked catalog entities, assigned locations, all variations, and stock-by-location snapshots.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/products/{id} |
| Authentication | Bearer token or API key + secret required. |
| Permission | product.view |
| Business scope | Returns only products belonging to the token user’s business_id. |
| Exclusions | Products with type = modifier are never returned. |
| JSON response | Top-level data object with nested linked entities and variations. |
| CSV response | Single-row file with one column, data_json, containing the serialized JSON data object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Primary key of the product to fetch. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format |
string | No | Allowed values: json (default) or csv. CSV downloads a single-row UTF-8 BOM file named product-{id}-detail-{business_id}-{timestamp}.csv with one column: data_json. |
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_ACCESS_TOKEN |
Accept | No | Recommended: application/json. |
| Field | Type | Description |
|---|---|---|
data | ProductDetail | Complete product document. |
| Field | Type | Description |
|---|---|---|
id | integer | Product primary key. |
name | string | Product display name. |
sku | string | Product-level SKU. |
type | string | Product type such as single, variable, or combo. |
image | string | null | Stored filename of the primary image when present. |
image_url | string | null | Resolved image URL when a custom primary image exists. |
barcode_type | string | null | Barcode type stored on the product. |
tax_type | string | null | inclusive or exclusive when set. |
enable_stock | boolean | Whether stock tracking is enabled. |
not_for_selling | boolean | Whether the product is blocked from selling flows. |
is_inactive | boolean | Whether the product is marked inactive. |
unit | UnitSummary | null | Linked unit record, if any. |
category | NamedEntity | null | Primary category, if any. |
sub_category | NamedEntity | null | Sub-category, if any. |
brand | NamedEntity | null | Brand, if any. |
tax_rate | TaxRateSummary | null | Linked tax rate, if any. |
locations | array<LocationSummary> | Business locations where the product is assigned. |
variations | array<ProductVariation> | All loaded variation rows for the product. |
| Object | Fields | Description |
|---|---|---|
UnitSummary |
id, name, short_name |
Basic unit identity for the linked unit. |
NamedEntity |
id, name |
Used by category, sub_category, and brand. |
TaxRateSummary |
id, name, amount |
Linked product tax rate when present. |
LocationSummary |
id, name |
Assigned business location. |
ProductVariation |
id, name, sub_sku, default_purchase_price, dpp_inc_tax, profit_percent, default_sell_price, sell_price_inc_tax, stock_by_location |
One variation row with pricing and stock snapshots. |
VariationStockByLocation |
location_id, location_name, qty_available |
Per-location quantity snapshot for the variation. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The product was found and returned as JSON or CSV. | JSON: { "data": { ... } }. CSV: one-row file with data_json. |
403 | The token user lacks product.view. | { "message": "Unauthorized" } |
404 | The product id does not exist in the business or refers to a modifier product. | { "message": "Not found" } |
422 | Unsupported query parameters such as an invalid format. | Laravel validation JSON with message and typically an errors object. |
curl -s "https://billdiary.com/api/v1/integration/products/1" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json"
curl -s -G "https://billdiary.com/api/v1/integration/products/1" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ --data-urlencode "format=csv" \ -o product-detail.csv
{
"data": {
"id": 1,
"name": "Example",
"sku": "SKU001",
"type": "single",
"image": null,
"image_url": null,
"barcode_type": "C128",
"tax_type": "exclusive",
"enable_stock": true,
"not_for_selling": false,
"is_inactive": false,
"unit": { "id": 1, "name": "Pc(s)", "short_name": "Pc" },
"category": null,
"sub_category": null,
"brand": null,
"tax_rate": null,
"locations": [{ "id": 1, "name": "Main" }],
"variations": [
{
"id": 1,
"name": "DUMMY",
"sub_sku": "SKU001",
"default_purchase_price": 5,
"dpp_inc_tax": 5,
"profit_percent": 0,
"default_sell_price": 10,
"sell_price_inc_tax": 10,
"stock_by_location": [
{ "location_id": 1, "location_name": "Main", "qty_available": 12 }
]
}
]
}
}
{
"message": "Not found"
}
Creates a new product in the authenticated user’s business. The endpoint supports the same three product modes as the web product form: single, variable, and combo.
application/json for normal requests. Use multipart/form-data only when uploading the optional primary image file image. The same field names are supported in both encodings.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/products |
| Authentication | Bearer token or API key + secret required. |
| Permission | product.create |
| Subscription | Requires an active package subscription when superadmin package checks are enabled. |
| Quota | Requires available product quota; otherwise the request is rejected before validation. |
| Demo mode | Returns 403 in demo environments. |
| Success response | 201 with a compact created-product summary. |
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_ACCESS_TOKEN |
Content-Type | Yes | application/json or multipart/form-data when sending image. |
Accept | No | Recommended: application/json. |
| Field | Type | Required | Description |
|---|---|---|---|
type | string | No | single (default), variable, or combo. |
name | string | Yes | Product name. |
unit_id | integer | Yes | Unit id that belongs to the current business. |
sku | string | null | No | Optional custom product SKU. Leave blank to use the system flow. |
category_id | integer | null | No | Product category id for this business. |
sub_category_id | integer | null | No | Product sub-category id for this business. |
brand_id | integer | null | No | Brand id for this business. |
tax | integer | null | No | Tax rate id for this business. |
tax_type | string | null | No | exclusive or inclusive. |
barcode_type | string | null | No | Barcode type such as C128. |
enable_stock | boolean | No | Whether stock tracking is enabled. |
not_for_selling | boolean | No | Whether the product is blocked from selling flows. |
product_locations | array<integer> | No | Business location ids where this product should be assigned. |
image | file | No | Primary image upload. Allowed only in multipart requests. Must pass image validation and the app’s configured upload size limit. |
type=single| Field | Type | Required | Description |
|---|---|---|---|
purchase_price_ex_tax | number | Yes | Default variation purchase price excluding tax. |
purchase_price_inc_tax | number | Yes | Default variation purchase price including tax. |
selling_price_ex_tax | number | Yes | Default variation sell price excluding tax. |
selling_price_inc_tax | number | Yes | Default variation sell price including tax. |
profit_percent | number | null | No | Optional profit percentage. If omitted, business defaults are used. |
type=variable| Field | Type | Required | Description |
|---|---|---|---|
sku_type | string | Yes | with_out_variation or with_variation, matching the web form behaviour. |
product_variation | array | Yes | Variation group definitions to create. |
product_variation[].variation_template_id | integer | null | Conditional | Existing variation template id for this business. Either this field or name is required for each group. |
product_variation[].name | string | null | Conditional | Template name to use when you are not referencing an existing template id. |
product_variation[].variations | array | Yes | Variation rows within the group. |
product_variation[].variations[].value | string | Yes | Variation value label. |
product_variation[].variations[].sub_sku | string | null | No | Optional variation SKU. |
product_variation[].variations[].variation_value_id | integer | null | No | Existing variation value id when appending to a template-driven structure. |
product_variation[].variations[].default_purchase_price | number | Yes | Variation purchase price excluding tax. |
product_variation[].variations[].dpp_inc_tax | number | Yes | Variation purchase price including tax. |
product_variation[].variations[].profit_percent | number | null | No | Optional variation profit percentage. |
product_variation[].variations[].default_sell_price | number | Yes | Variation sell price excluding tax. |
product_variation[].variations[].sell_price_inc_tax | number | Yes | Variation sell price including tax. |
type=combo| Field | Type | Required | Description |
|---|---|---|---|
combo_lines | array | Yes | Lines that make up the combo product. |
combo_lines[].variation_id | integer | Yes | Variation id from an existing product in this business. |
combo_lines[].quantity | number | Yes | Quantity used for the combo line. |
combo_lines[].unit_id | integer | Yes | Unit id for the combo line. |
item_level_purchase_price_total | number | Yes | Total purchase price of the combo based on its component items. |
purchase_price_inc_tax | number | Yes | Combo purchase price including tax. |
selling_price | number | Yes | Combo selling price excluding tax. |
selling_price_inc_tax | number | Yes | Combo selling price including tax. |
profit_percent | number | null | No | Optional profit percentage. |
| Field | Type | Description |
|---|---|---|
message | string | Localized success message. |
data.id | integer | Created product id. |
data.name | string | Created product name. |
data.sku | string | Stored product SKU. |
data.type | string | Created product type. |
data.image | string | null | Stored primary image filename when an image was saved. |
data.image_url | string | null | Resolvable primary image URL when an image was saved. |
| Status | When it happens | Response shape |
|---|---|---|
201 | Product created successfully. | { "message": string, "data": { ... } } |
402 | Subscription expired or maximum product quota reached. | { "message": string } |
403 | Demo mode or missing product.create permission. | { "message": string } |
422 | Validation failed, the image upload is invalid, or complex variable/combo payload rules are broken. | Laravel validation JSON or an explicit message such as Invalid image upload. |
500 | The create transaction failed unexpectedly. | { "message": "something_went_wrong" } |
curl -s -X POST "https://billdiary.com/api/v1/integration/products" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "API Product",
"unit_id": 1,
"type": "single",
"purchase_price_ex_tax": 5,
"purchase_price_inc_tax": 5,
"selling_price_ex_tax": 10,
"selling_price_inc_tax": 10
}'
{
"message": "Product added successfully",
"data": {
"id": 15,
"name": "API Product",
"sku": "SKU0015",
"type": "single",
"image": null,
"image_url": null
}
}
Updates an existing product in the authenticated user’s business. The endpoint supports partial updates for the common product fields, but the exact rules depend on the existing product type (single, variable, or combo).
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/products/{id} |
| Authentication | Bearer token or API key + secret required. |
| Permission | product.update |
| Demo mode | Returns 403 in demo environments. |
| Request encoding | application/json or multipart/form-data when replacing the primary image. |
| Success response | 200 with the same ProductDetail object documented under Get product. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Updated product name. |
unit_id | integer | No | Replacement unit id for this business. |
sku | string | null | No | Replacement product SKU. Blank values are normalized by the controller. |
category_id | integer | null | No | Updated category id. |
sub_category_id | integer | null | No | Updated sub-category id. |
brand_id | integer | null | No | Updated brand id. |
tax | integer | null | No | Updated tax rate id. |
barcode_type | string | null | No | Updated barcode type. |
tax_type | string | null | No | inclusive or exclusive. |
enable_stock | boolean | No | Updated stock-tracking flag. |
not_for_selling | boolean | No | Updated selling availability flag. |
is_inactive | boolean | No | Updated inactive flag. |
product_locations | array<integer> | No | Replaces the product’s assigned locations. Omit to leave unchanged; send [] to clear all assignments. |
image | file | No | Multipart file upload to replace the current primary image. |
| Product type | Payload shape | Notes |
|---|---|---|
single |
purchase_price_ex_tax, purchase_price_inc_tax, selling_price_ex_tax, selling_price_inc_tax, optional profit_percent |
To update pricing, send the full set of price fields together. |
variable |
sku_type, optional product_variation_edit, optional product_variation |
product_variation_edit is keyed by existing product variation group id and must provide full variation snapshots in variations_edit. The controller rejects incomplete snapshots and invalid variation mappings. |
combo |
Optional combo_lines plus combo price fields item_level_purchase_price_total, purchase_price_inc_tax, selling_price, selling_price_inc_tax, optional profit_percent |
When combo pricing is updated, the controller recalculates and replaces the combo variation pricing snapshot. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Product updated successfully. | { "message": string, "data": ProductDetail } |
403 | Demo mode or missing product.update permission. | { "message": string } |
404 | The product id is missing, outside the business, or refers to an unsupported row. | { "message": "Not found" } |
422 | Validation failed, variable update snapshots are incomplete, a business rule blocked the update, or the controller surfaced a known translated business message such as purchase_already_exist. | Laravel validation JSON or an explicit message. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
curl -s -X PATCH "https://billdiary.com/api/v1/integration/products/1" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"name":"Renamed product","is_inactive":false}'
{
"message": "Product updated successfully",
"data": {
"id": 1,
"name": "Renamed product"
}
}
Deletes a product when the same business rules as the web UI allow it. The controller checks product usage before deleting related location rows and detaching assignments.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/products/{id} |
| Permission | product.delete |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the deleted product id. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The product was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | Demo mode or missing product.delete permission. | { "message": string } |
404 | The product id was not found in the current business. | { "message": "Not found" } |
422 | A business rule blocked deletion, for example purchase history, sold opening stock, stock adjustments, non-stock sales usage, or manufacturing ingredient references. | { "message": string } |
500 | The delete transaction failed unexpectedly. | { "message": "something_went_wrong" } |
curl -s -X DELETE "https://billdiary.com/api/v1/integration/products/1" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json"
{
"message": "Product deleted successfully",
"data": {
"id": 1
}
}
Returns movement history for one variation of a product at one location. This is the API equivalent of the stock history screen and combines a summary object with the movement ledger.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/products/{id}/stock-history |
| Permission | product.view |
| Scope | One product, one variation, one business location. |
| JSON response | Returns data with product/variation context, summary, and movements. |
| CSV response | Returns only movement rows; summary is JSON-only. |
| Parameter | Type | Required | Description |
|---|---|---|---|
location_id | integer | Yes | Business location id that belongs to the current business. |
variation_id | integer | Conditional | Required when the product has more than one variation. Omit only when the product has exactly one variation. |
q | string | No | Optional movement search filter. Minimum 2 characters when provided. Matches movement labels, references, contact fields, notes, and transaction id text. |
format | string | No | json (default) or csv. |
| Field | Type | Description |
|---|---|---|
data.product_id | integer | Product id. |
data.product_name | string | Product name. |
data.variation_id | integer | Resolved variation id. |
data.variation_name | string | Variation name. |
data.sub_sku | string | null | Variation SKU. |
data.location_id | integer | Location id. |
data.location_name | string | null | Location name. |
data.summary | object | Stock summary from the same business logic as the web stock detail screen. Includes at least current stock information. |
data.movements | array<MovementRow> | Movement ledger in newest-first order. |
data.q | string | null | Echo of the applied search term. |
| Field | Type | Description |
|---|---|---|
date | string | ISO 8601 movement timestamp. |
transaction_id | integer | null | Related transaction id when available. |
type | string | null | Internal movement type key. |
type_label | string | null | User-facing movement label. |
badge_type | string | null | Badge style/type metadata for the UI-originated stock history data. |
ref_no | string | null | Reference number when present. |
quantity_change | number | null | Quantity delta for the movement. |
stock | number | null | Running stock after the movement. |
contact_name | string | null | Related contact name when present. |
supplier_business_name | string | null | Related supplier business name when present. |
additional_notes | string | null | Optional movement notes. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Stock history returned successfully. | JSON { "data": { ... } } or CSV file. |
403 | Missing product.view permission. | { "message": "Unauthorized" } |
404 | Product or variation was not found in the current business. | { "message": "Not found" } or { "message": "Variation not found." } |
422 | Invalid location, missing variation for multi-variation products, variation/product mismatch, or q shorter than 2 characters. | { "message": string } or Laravel validation JSON. |
Returns the selling price group price matrix for one product. The payload matches the structure used by the web “Add selling prices” screen: active price groups, product variations, and a nested group_prices matrix.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/products/{id}/selling-price-groups |
| Permission | product.view |
| Formats | json (default) or csv |
| Success response | 200 with the product selling price group matrix. |
| Field | Type | Description |
|---|---|---|
data.product_id | integer | Product id. |
data.selling_price_groups | array | Active selling price groups for the business. Each row includes id, name, and description. |
data.variations | array | Variation rows for the product. Each row includes id, name, sub_sku, and product_variation_id. |
data.group_prices | object | Nested matrix keyed as group_prices[selling_price_group_id][variation_id] with price and price_type. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. CSV flattens each populated matrix cell into one row. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The price-group matrix was returned successfully. | JSON { "data": { ... } } or CSV download. |
403 | The token user lacks product.view. | { "message": "Unauthorized" } |
404 | The product id does not exist in the current business. | { "message": "Not found" } |
422 | The query string failed validation, such as an unsupported format. | Laravel validation JSON. |
Partially updates the selling price matrix for a product. Only the supplied matrix cells are upserted; omitted groups and variations are left unchanged.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/products/{id}/selling-price-groups |
| Permission | product.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated product selling price group matrix. |
| Field | Type | Required | Description |
|---|---|---|---|
group_prices | object | Yes | Nested matrix keyed by selling price group id, then variation id. |
group_prices.{group_id}.{variation_id}.price | number | Yes | Price including tax for that matrix cell. |
group_prices.{group_id}.{variation_id}.price_type | string | Yes | fixed or percentage. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Matrix updated successfully. | { "message": string, "data": { ...same as GET... } } |
403 | Demo mode or missing product.create permission. | { "message": string } |
404 | The product id was not found in the current business. | { "message": "Not found" } |
422 | Invalid group ids, variation mismatch, malformed matrix entries, non-numeric price values, or unsupported price_type. | { "message": string } or Laravel validation JSON. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Bulk-updates default selling prices and/or selling price group cells by variation sub_sku. This mirrors the rules used by the web “Update product price” import flow.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/product-prices/bulk-update |
| Permission | product.update |
| Demo mode | Returns 403 in demo environments. |
| Request limit | 1 to 500 update rows. |
| Success response | 200 with processed row count. |
| Field | Type | Required | Description |
|---|---|---|---|
rows | array | Yes | 1 to 500 update rows. |
rows[].sub_sku | string | Yes | Variation SKU to locate. |
rows[].sell_price_inc_tax | number | null | No | Updated default selling price including tax. |
rows[].group_prices | object | null | No | Selling price group matrix for that variation. At least one of this field or sell_price_inc_tax is required. |
rows[].group_prices.{group_id}.price | number | Yes | Updated selling price group price. |
rows[].group_prices.{group_id}.price_type | string | No | fixed or percentage. Defaults to fixed when omitted. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Rows were processed successfully. | { "message": string, "data": { "rows_processed": integer } } |
403 | Demo mode or missing product.update permission. | { "message": string } |
422 | Missing sub_sku, rows without any price data, invalid or inactive selling price group ids, malformed group price cells, or unknown variation SKUs. | { "message": string } or Laravel validation JSON. |
500 | The bulk update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Bulk-edits multiple products and multiple variations in one request, using the same persistence path as the web bulk edit screen.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/products/bulk-update |
| Permission | product.update |
| Demo mode | Returns 403 in demo environments. |
| Request limit | 1 to 100 product payloads. |
| Success response | 200 with updated product count. |
| Field | Type | Required | Description |
|---|---|---|---|
products | array | Yes | 1 to 100 product update payloads. |
products[].id | integer | Yes | Product id to update. |
products[].category_id | integer | null | No | Replacement category id. |
products[].sub_category_id | integer | null | No | Replacement sub-category id. |
products[].brand_id | integer | null | No | Replacement brand id. |
products[].tax | integer | null | No | Replacement tax rate id. |
products[].product_locations | array<integer> | null | No | Replacement set of location assignments. |
products[].variations | array | Yes | At least one variation update row. |
products[].variations[].id | integer | Yes | Variation id belonging to the current product. |
products[].variations[].default_purchase_price | number | Yes | Updated purchase price excluding tax. |
products[].variations[].dpp_inc_tax | number | Yes | Updated purchase price including tax. |
products[].variations[].profit_percent | number | Yes | Updated profit percentage. |
products[].variations[].default_sell_price | number | Yes | Updated sell price excluding tax. |
products[].variations[].sell_price_inc_tax | number | Yes | Updated sell price including tax. |
products[].variations[].group_prices | object | null | No | Optional map of selling price group id to numeric price. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Products were updated successfully. | { "message": string, "data": { "updated_count": integer } } |
403 | Demo mode or missing product.update permission. | { "message": string } |
422 | Validation failed or a selling price group id in the payload is invalid or inactive. | { "message": string } or Laravel validation JSON. |
500 | An unexpected model lookup or transaction error occurred during the bulk update. | { "message": "something_went_wrong" } |
Imports products from a spreadsheet using the same importer and template rules as the web product import flow.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/import/products |
| Permission | product.create |
| Encoding | multipart/form-data |
| Execution context | Runs through the same importer used by the web product import flow. |
| Demo mode | Returns 403 in demo environments. |
| Field | Type | Required | Description |
|---|---|---|---|
products_csv | file | Yes | Multipart file upload containing the product import sheet. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The import completed successfully. | { "success": 1, "message": string } |
403 | Demo mode or missing product.create permission. | { "message": string } |
422 | The file was missing, malformed, or the importer rejected the uploaded sheet. | { "success": 0, "message": string } or Laravel validation JSON. |
Imports opening stock rows using the same spreadsheet rules as the web opening stock importer.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/import/opening-stock |
| Permission | product.opening_stock |
| Encoding | multipart/form-data |
| Execution context | Runs through the same importer used by the web opening stock import flow. |
| Demo mode | Returns 403 in demo environments. |
| Field | Type | Required | Description |
|---|---|---|---|
products_csv | file | Yes | Multipart file upload for the opening stock template. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The import completed successfully. | { "success": 1, "message": string } |
403 | Demo mode or missing product.opening_stock permission. | { "message": string } |
422 | The file was missing, malformed, or the importer rejected the uploaded sheet. | { "success": 0, "message": string } or Laravel validation JSON. |
Imports sales from a spreadsheet. The API path uses the same import pipeline as the web sales importer, including spreadsheet parsing, grouping, and mapped field validation.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/import/sales |
| Permission | sell.create |
| Encoding | multipart/form-data |
| Execution context | Runs through the same importer and session bridge used by the web sales import flow. |
| Demo mode | Returns 403 in demo environments. |
| Field | Type | Required | Description |
|---|---|---|---|
sales | file | Yes | Spreadsheet file in the same layout as the web sales import template. |
location_id | integer | Yes | Business location id for the imported invoices. |
group_by | integer | Yes | Zero-based column index used to group rows into invoices. |
import_fields | array | JSON string | Yes | Map of spreadsheet column index to importer field key. The controller also accepts this field as a JSON string. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The import completed successfully. | { "success": 1, "message": string, "import_batch": integer } |
403 | Demo mode or missing sell.create permission. | { "message": string } |
422 | The spreadsheet mapping was invalid, required mapped fields were missing, the file upload failed validation, or the importer rejected the sheet contents. | { "success": 0, "message": string } or Laravel validation JSON. |
Lists variation templates available in the current business. Each template includes its values and the number of product variation groups currently referencing it.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/variation-templates |
| Permissions | product.view or product.create |
| Pagination | Supports per_page and page. |
| Search | Supports q and legacy search. q must be at least 2 characters when provided. |
| Sorting | sort=name|created_at and direction=asc|desc |
| Formats | json (default) or csv |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Page size from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
q | string | No | Search term for template names and value names. Minimum 2 characters when provided. |
search | string | No | Legacy alias for search. |
sort | string | No | name or created_at. |
direction | string | No | asc or desc. |
format | string | No | json (default) or csv. CSV returns all filtered rows and ignores pagination. |
| Field | Type | Description |
|---|---|---|
data | array<VariationTemplate> | Filtered variation templates. |
meta | object | Pagination metadata with current_page, last_page, per_page, and total. |
| Field | Type | Description |
|---|---|---|
id | integer | Template id. |
name | string | Template name. |
values | array | Variation values; each entry has id and name. |
product_variations_count | integer | How many product variation groups currently reference this template. |
created_at | string | null | Creation timestamp. |
updated_at | string | null | Last update timestamp. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Variation templates were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user lacks both product.view and product.create. | { "message": "Unauthorized" } |
422 | q is shorter than 2 characters or another query parameter failed validation. | Laravel validation JSON. |
500 | The controller failed while listing templates. | { "message": "Could not list variation templates" } |
Returns one variation template record for the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/variation-templates/{id} |
| Permissions | product.view or product.create |
| Formats | json (default) or csv |
| Success response | 200 with one VariationTemplate object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. CSV returns a one-row file with the template payload in data_json. |
| Field | Type | Description |
|---|---|---|
data | VariationTemplate | The requested template object. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The template was found and returned. | JSON { "data": { ... } } or CSV download. |
403 | The token user lacks both product.view and product.create. | { "message": "Unauthorized" } |
404 | The template id does not exist in the current business. | { "message": "Not found" } |
Creates a new variation template with optional initial value rows.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/variation-templates |
| Permission | product.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 201 with the created VariationTemplate. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Template name. |
variation_values | array<string> | No | Optional values to create under the template, such as ["S","M","L"]. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The template was created successfully. | { "message": string, "data": VariationTemplate } |
403 | Demo mode or missing product.create permission. | { "message": string } |
422 | The request body failed validation. | Laravel validation JSON. |
500 | The create transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Updates a variation template by renaming the template, renaming existing values, and/or appending new values.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/variation-templates/{id} |
| Permission | product.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated VariationTemplate. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Renames the template and synced product variation group labels. |
edit_variation_values | object | No | Map of existing value id to new value name. |
variation_values | array<string> | No | Additional values to append to the template. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The template was updated successfully. | { "message": string, "data": VariationTemplate } |
403 | Demo mode or missing product.create permission. | { "message": string } |
404 | The template id does not exist in the current business. | { "message": "Not found" } |
422 | The request body failed validation or a referenced variation value id does not belong to the template. | { "message": string } or Laravel validation JSON. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Deletes a variation template when it is no longer referenced by product variation groups.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/variation-templates/{id} |
| Permission | product.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with deleted id. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The template was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | Demo mode or missing product.create permission. | { "message": string } |
404 | The template id does not exist in the current business. | { "message": "Not found" } |
422 | The template is still in use by one or more product variation groups. | { "message": string } |
500 | The delete operation failed unexpectedly. | { "message": "something_went_wrong" } |
Lists brands in the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/brands |
| Permissions | brand.view or brand.create |
| Pagination | Supports per_page and page. |
| Search | Supports q and legacy search. |
| Sorting | sort=name|created_at and direction=asc|desc |
| Formats | json (default) or csv |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Page size from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
q | string | No | Search term for brand name or description. Minimum 2 characters when provided. |
search | string | No | Legacy alias for search. |
sort | string | No | name or created_at. |
direction | string | No | asc or desc. |
format | string | No | json (default) or csv. CSV returns all filtered rows and ignores pagination. |
| Field | Type | Description |
|---|---|---|
data | array<Brand> | Filtered brand records. |
meta | object | Pagination metadata with current_page, last_page, per_page, and total. |
| Field | Type | Description |
|---|---|---|
id | integer | Brand id. |
name | string | Brand name. |
description | string | null | Optional description. |
use_for_repair | boolean | Present when the Repair module is installed. |
created_at | string | null | Creation timestamp. |
updated_at | string | null | Last update timestamp. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Brands were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user lacks both brand.view and brand.create. | { "message": "Unauthorized" } |
422 | q is shorter than 2 characters or another query parameter failed validation. | Laravel validation JSON. |
500 | The controller failed while listing brands. | { "message": "Could not list brands" } |
Returns one brand record for the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/brands/{id} |
| Permissions | brand.view or brand.create |
| Formats | json (default) or csv |
| Success response | 200 with one Brand object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. CSV returns a one-row file with the brand payload in data_json. |
| Field | Type | Description |
|---|---|---|
data | Brand | The requested brand object. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The brand was found and returned. | JSON { "data": { ... } } or CSV download. |
403 | The token user lacks both brand.view and brand.create. | { "message": "Unauthorized" } |
404 | The brand id does not exist in the current business. | { "message": "Not found" } |
Creates a new brand for the current business.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/brands |
| Permission | brand.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 201 with the created Brand. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Brand name. |
description | string | null | No | Optional brand description. |
use_for_repair | boolean | No | Optional Repair-module flag. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The brand was created successfully. | { "data": Brand } |
403 | Demo mode or missing brand.create permission. | { "message": string } |
422 | The request body failed validation. | Laravel validation JSON. |
500 | The create transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Updates a brand record in the current business.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/brands/{id} |
| Permission | brand.update |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated Brand. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Brand name. |
description | string | null | No | Optional brand description. |
use_for_repair | boolean | No | Optional Repair-module flag when that module is installed. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The brand was updated successfully. | { "data": Brand } |
403 | Demo mode or missing brand.update permission. | { "message": string } |
404 | The brand id does not exist in the current business. | { "message": "Not found" } |
422 | The request body failed validation. | Laravel validation JSON. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Soft-deletes a brand record in the current business.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/brands/{id} |
| Permission | brand.delete |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with deleted id. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The brand was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | Demo mode or missing brand.delete permission. | { "message": string } |
404 | The brand id does not exist in the current business. | { "message": "Not found" } |
500 | The delete transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Lists units in the current business, including base-unit relationships and the display label used by the web UI.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/units |
| Permissions | unit.view or unit.create |
| Pagination | Supports per_page and page. |
| Search | Supports q and legacy search. |
| Sorting | sort=actual_name|short_name|created_at and direction=asc|desc |
| Formats | json (default) or csv |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Page size from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
q | string | No | Search term for unit names. Minimum 2 characters when provided. |
search | string | No | Legacy alias for search. |
sort | string | No | actual_name, short_name, or created_at. |
direction | string | No | asc or desc. |
format | string | No | json (default) or csv. CSV returns all filtered rows and ignores pagination. |
| Field | Type | Description |
|---|---|---|
data | array<Unit> | Filtered unit records. |
meta | object | Pagination metadata with current_page, last_page, per_page, and total. |
| Field | Type | Description |
|---|---|---|
id | integer | Unit id. |
actual_name | string | Full unit name. |
short_name | string | Short display name. |
allow_decimal | boolean | Whether decimal quantities are allowed. |
base_unit_id | integer | null | Parent/base unit id when this is a sub-unit. |
base_unit_multiplier | number | null | Multiplier against the base unit. |
display_name | string | UI-friendly label. |
base_unit | object | null | Base unit summary with id, actual_name, and short_name. |
created_at | string | null | Creation timestamp. |
updated_at | string | null | Last update timestamp. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Units were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user lacks both unit.view and unit.create. | { "message": "Unauthorized" } |
422 | q is shorter than 2 characters or another query parameter failed validation. | Laravel validation JSON. |
500 | The controller failed while listing units. | { "message": "Could not list units" } or a generic error message. |
Returns one unit record for the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/units/{id} |
| Permissions | unit.view or unit.create |
| Formats | json (default) or csv |
| Success response | 200 with one Unit object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. CSV returns a one-row file with the unit payload in data_json. |
| Field | Type | Description |
|---|---|---|
data | Unit | The requested unit object. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The unit was found and returned. | JSON { "data": { ... } } or CSV download. |
403 | The token user lacks both unit.view and unit.create. | { "message": "Unauthorized" } |
404 | The unit id does not exist in the current business. | { "message": "Not found" } |
Creates a new base unit or sub-unit for the current business.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/units |
| Permission | unit.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 201 with the created Unit. |
| Field | Type | Required | Description |
|---|---|---|---|
actual_name | string | Yes | Full unit name. |
short_name | string | Yes | Short unit label. |
allow_decimal | boolean | Yes | Whether decimal quantities are allowed. |
base_unit_id | integer | null | No | Base unit id when creating a sub-unit. |
base_unit_multiplier | number | null | No | Required together with base_unit_id for sub-units. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The unit was created successfully. | { "data": Unit } |
403 | Demo mode or missing unit.create permission. | { "message": string } |
422 | The request body failed validation, the base unit was invalid, or the multiplier normalized to zero. | Laravel validation JSON or { "message": string }. |
500 | The create transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Updates a unit record, including whether it should behave as a base unit or a sub-unit.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/units/{id} |
| Permission | unit.update |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated Unit. |
| Field | Type | Required | Description |
|---|---|---|---|
actual_name | string | Yes | Full unit name. |
short_name | string | Yes | Short unit label. |
allow_decimal | boolean | Yes | Whether decimal quantities are allowed. |
define_base_unit | boolean | Yes | When true, the unit behaves as a sub-unit and must also receive base_unit_id and base_unit_multiplier. When false, those relationships are cleared. |
base_unit_id | integer | null | Conditional | Required when define_base_unit=true. |
base_unit_multiplier | number | null | Conditional | Required when define_base_unit=true and must normalize to a non-zero value. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The unit was updated successfully. | { "data": Unit } |
403 | Demo mode or missing unit.update permission. | { "message": string } |
404 | The unit id does not exist in the current business. | { "message": "Not found" } |
422 | The request body failed validation, the base unit was invalid, the unit tried to reference itself, or the multiplier normalized to zero. | Laravel validation JSON or { "message": string }. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Soft-deletes a unit when no products still reference it.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/units/{id} |
| Permission | unit.delete |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with deleted id. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The unit was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | Demo mode or missing unit.delete permission. | { "message": string } |
404 | The unit id does not exist in the current business. | { "message": "Not found" } |
422 | The unit is still referenced by one or more products. | { "message": string } |
500 | The delete operation failed unexpectedly. | { "message": "something_went_wrong" } |
Lists product categories in the current business, including parent/sub-category relationships.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/product-categories |
| Permissions | category.view or category.create |
| Pagination | Supports per_page and page. |
| Search | Supports q and legacy search. |
| Sorting | sort=name|short_code|created_at and direction=asc|desc |
| Formats | json (default) or csv |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Page size from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
q | string | No | Search term for category name, short code, or description. Minimum 2 characters when provided. |
search | string | No | Legacy alias for search. |
sort | string | No | name, short_code, or created_at. |
direction | string | No | asc or desc. |
format | string | No | json (default) or csv. CSV returns all filtered rows and ignores pagination. |
| Field | Type | Description |
|---|---|---|
data | array<ProductCategory> | Filtered product category records. |
meta | object | Pagination metadata with current_page, last_page, per_page, and total. |
| Field | Type | Description |
|---|---|---|
id | integer | Category id. |
name | string | Category name. |
short_code | string | null | Optional short code. |
description | string | null | Optional description. |
category_type | string | Always product in this endpoint family. |
parent_id | integer | null | Parent category id when this row is a child. |
is_sub_category | boolean | Whether the row is a sub-category. |
display_name | string | UI-friendly label. |
parent | object | null | Parent summary with id and name. |
sub_categories | array | Immediate child categories. |
created_at | string | null | Creation timestamp. |
updated_at | string | null | Last update timestamp. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Product categories were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user lacks both category.view and category.create. | { "message": "Unauthorized" } |
422 | q is shorter than 2 characters or another query parameter failed validation. | Laravel validation JSON. |
500 | The controller failed while listing categories. | { "message": "Could not list categories" } or a generic error message. |
Returns one product category record for the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/product-categories/{id} |
| Permissions | category.view or category.create |
| Formats | json (default) or csv |
| Success response | 200 with one ProductCategory object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. CSV returns a one-row file with the category payload in data_json. |
| Field | Type | Description |
|---|---|---|
data | ProductCategory | The requested product category object. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The category was found and returned. | JSON { "data": { ... } } or CSV download. |
403 | The token user lacks both category.view and category.create. | { "message": "Unauthorized" } |
404 | The category id does not exist in the current business. | { "message": "Not found" } |
Creates a new product category for the current business.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/product-categories |
| Permission | category.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 201 with the created ProductCategory. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Category name. |
short_code | string | null | No | Optional short code. |
description | string | null | No | Optional description. |
parent_id | integer | null | No | Use 0 or omit for a top-level category; otherwise provide a valid top-level product category id. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The category was created successfully. | { "data": ProductCategory } |
403 | Demo mode or missing category.create permission. | { "message": string } |
422 | The request body failed validation or parent_id does not refer to a valid top-level product category. | Laravel validation JSON or { "message": "Invalid parent_id" }. |
500 | The create transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Updates an existing product category, including whether it remains top-level or becomes a child category.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/product-categories/{id} |
| Permission | category.update |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated ProductCategory. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Category name. |
short_code | string | null | No | Optional short code. |
description | string | null | No | Optional description. |
parent_id | integer | Yes | Use 0 to keep the category top-level, or provide a valid top-level product category id to make it a child. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The category was updated successfully. | { "data": ProductCategory } |
403 | Demo mode or missing category.update permission. | { "message": string } |
404 | The category id does not exist in the current business. | { "message": "Not found" } |
422 | The request body failed validation, the category tried to become its own parent, or parent_id is invalid. | Laravel validation JSON or { "message": string }. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Soft-deletes a product category record in the current business.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/product-categories/{id} |
| Permission | category.delete |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with deleted id. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The category was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | Demo mode or missing category.delete permission. | { "message": string } |
404 | The category id does not exist in the current business. | { "message": "Not found" } |
500 | The delete operation failed unexpectedly. | { "message": "something_went_wrong" } |
Lists standalone tax rates in the current business. Tax groups are excluded from this endpoint family.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/tax-rates |
| Permissions | tax_rate.view or tax_rate.create |
| Pagination | Supports per_page and page. |
| Search | Supports q and legacy search. q must be at least 2 characters when provided. |
| Sorting | sort=name|amount|created_at and direction=asc|desc |
| Formats | json (default) or csv |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Page size from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
q | string | No | Search term for tax rate names or ids. Minimum 2 characters when provided. |
search | string | No | Legacy alias for search. |
sort | string | No | name, amount, or created_at. |
direction | string | No | asc or desc. |
format | string | No | json (default) or csv. CSV returns all filtered rows and ignores pagination. |
| Field | Type | Description |
|---|---|---|
data | array<TaxRate> | Filtered standalone tax rates. |
meta | object | Pagination metadata with current_page, last_page, per_page, and total. |
| Field | Type | Description |
|---|---|---|
id | integer | Tax rate id. |
name | string | Tax rate name. |
amount | number | Tax percentage amount. |
for_tax_group | boolean | Whether the rate is intended to be used as a component inside a tax group. |
is_tax_group | boolean | Always false in this endpoint family. |
created_at | string | null | Creation timestamp. |
updated_at | string | null | Last update timestamp. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Tax rates were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user lacks both tax_rate.view and tax_rate.create. | { "message": "Unauthorized" } |
422 | q is shorter than 2 characters or another query parameter failed validation. | Laravel validation JSON. |
500 | The controller failed while listing tax rates. | { "message": "Could not list tax rates" } |
Returns one standalone tax rate record for the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/tax-rates/{id} |
| Permissions | tax_rate.view or tax_rate.create |
| Formats | json (default) or csv |
| Success response | 200 with one TaxRate object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. CSV returns a one-row file with the tax rate payload in data_json. |
| Field | Type | Description |
|---|---|---|
data | TaxRate | The requested standalone tax rate. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The tax rate was found and returned. | JSON { "data": { ... } } or CSV download. |
403 | The token user lacks both tax_rate.view and tax_rate.create. | { "message": "Unauthorized" } |
404 | The tax rate id does not exist in the current business or is not a standalone rate. | { "message": "Not found" } |
Creates a new standalone tax rate for the current business.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/tax-rates |
| Permission | tax_rate.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 201 with the created TaxRate. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Tax rate name. |
amount | number | Yes | Tax percentage amount. |
for_tax_group | boolean | No | Whether the rate is intended to be used only as a component in combined tax groups. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The tax rate was created successfully. | { "data": TaxRate } |
403 | Demo mode or missing tax_rate.create permission. | { "message": string } |
422 | The request body failed validation. | Laravel validation JSON. |
500 | The create transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Updates an existing standalone tax rate in the current business.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/tax-rates/{id} |
| Permission | tax_rate.update |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated TaxRate. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Tax rate name. |
amount | number | Yes | Tax percentage amount. |
for_tax_group | boolean | No | Whether the rate is intended to be used as a component inside combined tax groups. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The tax rate was updated successfully. | { "data": TaxRate } |
403 | Demo mode or missing tax_rate.update permission. | { "message": string } |
404 | The tax rate id does not exist in the current business or is not a standalone rate. | { "message": "Not found" } |
422 | The request body failed validation. | Laravel validation JSON. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Deletes a standalone tax rate when it is no longer used inside a combined tax group.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/tax-rates/{id} |
| Permission | tax_rate.delete |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with deleted id. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The tax rate was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | Demo mode or missing tax_rate.delete permission. | { "message": string } |
404 | The tax rate id does not exist in the current business or is not a standalone rate. | { "message": "Not found" } |
422 | The tax rate is still referenced inside a combined tax group. | { "message": string } |
500 | The delete transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Lists combined tax groups in the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/tax-groups |
| Permissions | tax_rate.view or tax_rate.create |
| Pagination | Supports per_page and page. |
| Search | Supports q and legacy search. q must be at least 2 characters when provided. |
| Sorting | sort=name|amount|created_at and direction=asc|desc |
| Formats | json (default) or csv |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Page size from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
q | string | No | Search term for tax group names or ids. Minimum 2 characters when provided. |
search | string | No | Legacy alias for search. |
sort | string | No | name, amount, or created_at. |
direction | string | No | asc or desc. |
format | string | No | json (default) or csv. CSV returns all filtered rows and ignores pagination. |
| Field | Type | Description |
|---|---|---|
data | array<TaxGroup> | Filtered combined tax groups. |
meta | object | Pagination metadata with current_page, last_page, per_page, and total. |
| Field | Type | Description |
|---|---|---|
id | integer | Tax group id. |
name | string | Tax group name. |
amount | number | Computed total amount from all component rates. |
is_tax_group | boolean | Always true in this endpoint family. |
sub_taxes | array | Component tax rates; each entry has id, name, and amount. |
created_at | string | null | Creation timestamp. |
updated_at | string | null | Last update timestamp. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Tax groups were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user lacks both tax_rate.view and tax_rate.create. | { "message": "Unauthorized" } |
422 | q is shorter than 2 characters or another query parameter failed validation. | Laravel validation JSON. |
500 | The controller failed while listing tax groups. | { "message": "Could not list tax groups" } |
Returns one combined tax group record for the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/tax-groups/{id} |
| Permissions | tax_rate.view or tax_rate.create |
| Formats | json (default) or csv |
| Success response | 200 with one TaxGroup object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. CSV returns a one-row file with the tax group payload in data_json. |
| Field | Type | Description |
|---|---|---|
data | TaxGroup | The requested combined tax group. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The tax group was found and returned. | JSON { "data": { ... } } or CSV download. |
403 | The token user lacks both tax_rate.view and tax_rate.create. | { "message": "Unauthorized" } |
404 | The tax group id does not exist in the current business or is not a combined tax group. | { "message": "Not found" } |
Creates a new combined tax group from standalone tax rates in the current business.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/tax-groups |
| Permission | tax_rate.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 201 with the created TaxGroup. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Tax group name. |
sub_tax_ids | array<integer> | Yes | Standalone tax rate ids for this business. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The tax group was created successfully. | { "data": TaxGroup } |
403 | Demo mode or missing tax_rate.create permission. | { "message": string } |
422 | The request body failed validation or one or more sub_tax_ids do not belong to valid standalone tax rates in the current business. | Laravel validation JSON or { "message": "Invalid sub_tax_ids for this business." }. |
500 | The create transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Updates an existing combined tax group by changing its name, component rates, or both.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/tax-groups/{id} |
| Permission | tax_rate.update |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated TaxGroup. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Updated tax group name. |
sub_tax_ids | array<integer> | No | Replacement set of standalone tax rate ids for the group. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The tax group was updated successfully. | { "data": TaxGroup } |
403 | Demo mode or missing tax_rate.update permission. | { "message": string } |
404 | The tax group id does not exist in the current business or is not a combined tax group. | { "message": "Not found" } |
422 | The request body failed validation, no fields were supplied, or one or more sub_tax_ids are invalid for the current business. | Laravel validation JSON or { "message": string }. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Deletes a combined tax group in the current business.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/tax-groups/{id} |
| Permission | tax_rate.delete |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with deleted id. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The tax group was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | Demo mode or missing tax_rate.delete permission. | { "message": string } |
404 | The tax group id does not exist in the current business or is not a combined tax group. | { "message": "Not found" } |
500 | The delete transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Lists selling price groups in the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/selling-price-groups |
| Permission | product.create |
| Pagination | Supports per_page and page. |
| Search | Supports q and legacy search. q must be at least 2 characters when provided. |
| Sorting | sort=name|is_active|created_at and direction=asc|desc |
| Formats | json (default) or csv |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Page size from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
q | string | No | Search term for selling price group names, descriptions, or ids. Minimum 2 characters when provided. |
search | string | No | Legacy alias for search. |
sort | string | No | name, is_active, or created_at. |
direction | string | No | asc or desc. |
format | string | No | json (default) or csv. CSV returns all filtered rows and ignores pagination. |
| Field | Type | Description |
|---|---|---|
data | array<SellingPriceGroup> | Filtered selling price groups. |
meta | object | Pagination metadata with current_page, last_page, per_page, and total. |
| Field | Type | Description |
|---|---|---|
id | integer | Selling price group id. |
name | string | Group name. |
description | string | null | Optional description. |
is_active | boolean | Whether the group is active. |
permission_name | string | Spatie permission created for the group, e.g. selling_price_group.5. |
created_at | string | null | Creation timestamp. |
updated_at | string | null | Last update timestamp. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Selling price groups were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user lacks product.create. | { "message": "Unauthorized" } |
422 | q is shorter than 2 characters or another query parameter failed validation. | Laravel validation JSON. |
500 | The controller failed while listing selling price groups. | { "message": "Could not list selling price groups" } |
Returns one selling price group record for the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/selling-price-groups/{id} |
| Permission | product.create |
| Formats | json (default) or csv |
| Success response | 200 with one SellingPriceGroup object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. CSV returns a one-row file with the selling price group payload in data_json. |
| Field | Type | Description |
|---|---|---|
data | SellingPriceGroup | The requested selling price group. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The selling price group was found and returned. | JSON { "data": { ... } } or CSV download. |
403 | The token user lacks product.create. | { "message": "Unauthorized" } |
404 | The selling price group id does not exist in the current business. | { "message": "Not found" } |
Creates a new selling price group and its matching permission row in the current business.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/selling-price-groups |
| Permission | product.create |
| Side effect | Creates the matching permission name selling_price_group.{id} in the same transaction. |
| Demo mode | Returns 403 in demo environments. |
| Success response | 201 with the created SellingPriceGroup. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Group name. |
description | string | null | No | Optional description. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The selling price group was created successfully. | { "data": SellingPriceGroup } |
403 | Demo mode or missing product.create permission. | { "message": string } |
422 | The request body failed validation. | Laravel validation JSON. |
500 | The create transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Updates an existing selling price group in the current business.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/selling-price-groups/{id} |
| Permission | product.update |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated SellingPriceGroup. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Selling price group name. |
description | string | null | No | Optional description. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The selling price group was updated successfully. | { "data": SellingPriceGroup } |
403 | Demo mode or missing product.update permission. | { "message": string } |
404 | The selling price group id does not exist in the current business. | { "message": "Not found" } |
422 | The request body failed validation. | Laravel validation JSON. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Toggles the active state of a selling price group and returns the updated record.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/selling-price-groups/{id}/toggle-active |
| Permission | product.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated SellingPriceGroup. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The group active state was flipped successfully. | { "data": SellingPriceGroup } |
403 | Demo mode or missing product.create permission. | { "message": string } |
404 | The selling price group id does not exist in the current business. | { "message": "Not found" } |
500 | The toggle operation failed unexpectedly. | { "message": "something_went_wrong" } |
Soft-deletes a selling price group in the current business.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/selling-price-groups/{id} |
| Permission | product.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with deleted id. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The selling price group was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | Demo mode or missing product.create permission. | { "message": string } |
404 | The selling price group id does not exist in the current business. | { "message": "Not found" } |
500 | The delete transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Lists warranties available in the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/warranties |
| Permissions | product.view or product.create |
| Pagination | Supports per_page and page. |
| Search | Supports q and legacy search. q must be at least 2 characters when provided. |
| Sorting | sort=name|duration|duration_type|created_at and direction=asc|desc |
| Formats | json (default) or csv |
| Parameter | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Page size from 1 to 100. Defaults to 20. |
page | integer | No | Pagination page number. |
q | string | No | Search term for warranty names, descriptions, or ids. Minimum 2 characters when provided. |
search | string | No | Legacy alias for search. |
sort | string | No | name, duration, duration_type, or created_at. |
direction | string | No | asc or desc. |
format | string | No | json (default) or csv. CSV returns all filtered rows and ignores pagination. |
| Field | Type | Description |
|---|---|---|
data | array<Warranty> | Filtered warranty records. |
meta | object | Pagination metadata with current_page, last_page, per_page, and total. |
| Field | Type | Description |
|---|---|---|
id | integer | Warranty id. |
name | string | Warranty name. |
description | string | null | Optional description. |
duration | integer | Warranty length. |
duration_type | string | days, months, or years. |
display_name | string | UI-friendly label built from name + duration. |
created_at | string | null | Creation timestamp. |
updated_at | string | null | Last update timestamp. |
| Status | When it happens | Response shape |
|---|---|---|
200 | Warranties were returned successfully. | JSON { "data": [...], "meta": { ... } } or CSV download. |
403 | The token user lacks both product.view and product.create. | { "message": "Unauthorized" } |
422 | q is shorter than 2 characters or another query parameter failed validation. | Laravel validation JSON. |
500 | The controller failed while listing warranties. | { "message": "Could not list warranties" } |
Returns one warranty record for the current business.
| Property | Value |
|---|---|
| Method | GET |
| Path | /api/v1/integration/warranties/{id} |
| Permissions | product.view or product.create |
| Formats | json (default) or csv |
| Success response | 200 with one Warranty object. |
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | json (default) or csv. CSV returns a one-row file with the warranty payload in data_json. |
| Field | Type | Description |
|---|---|---|
data | Warranty | The requested warranty record. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The warranty was found and returned. | JSON { "data": { ... } } or CSV download. |
403 | The token user lacks both product.view and product.create. | { "message": "Unauthorized" } |
404 | The warranty id does not exist in the current business. | { "message": "Not found" } |
Creates a new warranty record for the current business.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/integration/warranties |
| Permission | product.create |
| Demo mode | Returns 403 in demo environments. |
| Success response | 201 with the created Warranty. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Warranty name. |
duration | integer | Yes | Positive warranty length. |
duration_type | string | Yes | days, months, or years. |
description | string | null | No | Optional description. |
| Status | When it happens | Response shape |
|---|---|---|
201 | The warranty was created successfully. | { "data": Warranty } |
403 | Demo mode or missing product.create permission. | { "message": string } |
422 | The request body failed validation. | Laravel validation JSON. |
500 | The create transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Updates an existing warranty record in the current business.
| Property | Value |
|---|---|
| Methods | PATCH or PUT |
| Path | /api/v1/integration/warranties/{id} |
| Permission | product.update |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with the updated Warranty. |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Warranty name. |
duration | integer | Yes | Positive warranty length. |
duration_type | string | Yes | days, months, or years. |
description | string | null | No | Optional description. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The warranty was updated successfully. | { "data": Warranty } |
403 | Demo mode or missing product.update permission. | { "message": string } |
404 | The warranty id does not exist in the current business. | { "message": "Not found" } |
422 | The request body failed validation. | Laravel validation JSON. |
500 | The update transaction failed unexpectedly. | { "message": "something_went_wrong" } |
Deletes a warranty record when it is no longer referenced by products or sale-line warranty data.
| Property | Value |
|---|---|
| Method | DELETE |
| Path | /api/v1/integration/warranties/{id} |
| Permission | product.delete |
| Demo mode | Returns 403 in demo environments. |
| Success response | 200 with deleted id. |
| Status | When it happens | Response shape |
|---|---|---|
200 | The warranty was deleted successfully. | { "message": string, "data": { "id": integer } } |
403 | Demo mode or missing product.delete permission. | { "message": string } |
404 | The warranty id does not exist in the current business. | { "message": "Not found" } |
422 | The warranty is still assigned to a product or referenced by sale-line warranty data. | { "message": string } |
500 | The delete transaction failed unexpectedly. | { "message": "something_went_wrong" } |