Home Integration API reference

Integration API reference

REST API for integrations and external apps

Products

List 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.

Use List products to build product pickers, sync lists, search screens, and admin overviews. Use Get product when you need the full record including tax, assigned locations, all variations, and per-location stock.

Endpoint summary

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.

Required headers

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.

Query parameters

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.

Top-level JSON response

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.

ProductListItem object

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.

PriceRange object

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.

meta object

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.

CSV output

When format=csv is sent, the response is a streamed file and the JSON wrapper is not returned.

Column Type in CSV cell Description
idintegerMatches JSON data[].id.
namestringMatches JSON data[].name.
skustringMatches JSON data[].sku.
typestringMatches JSON data[].type.
enable_stockbooleanMatches JSON data[].enable_stock.
is_inactivebooleanMatches JSON data[].is_inactive.
not_for_sellingbooleanMatches JSON data[].not_for_selling.
unit_namestring | nullMatches JSON data[].unit_name.
category_namestring | nullMatches JSON data[].category_name.
brand_namestring | nullMatches JSON data[].brand_name.
variation_sub_skustring | nullMatches JSON data[].variation_sub_sku.
sell_price_ex_taxJSON stringSerialized version of the JSON price range object.
sell_price_inc_taxJSON stringSerialized version of the JSON price range object.

Status codes

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.
Example request (JSON)
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"
Example request (CSV)
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
Example JSON response (200)
{
  "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
  }
}
Example error response (403)
{
  "message": "Unauthorized"
}
Example error response (422: q too short)
{
  "message": "Query parameter q must be at least 2 characters when provided."
}

Check product SKU availability

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.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/products/check-sku
AuthenticationBearer token or API key + secret required.
Permissionproduct.create or product.update
Business scopeChecks only product and variation SKUs belonging to the token user’s business.
Comparison scopeChecks product sku first, then variation sub_sku if no product-level match is found.
Edit supportUse product_id and/or variation_id to exclude the current record while editing.

Required headers

HeaderRequiredDescription
AuthorizationYesBearer YOUR_ACCESS_TOKEN
AcceptNoRecommended: application/json.

Query parameters

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.

Top-level JSON response

Field Type Description
dataobjectAvailability result.

Availability result object

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 codes

StatusWhen it happensResponse shape
200Availability check completed successfully.{ "data": { "available": boolean, "sku": string | null } }
403The token user lacks both product.create and product.update.{ "message": "Unauthorized" }
422Invalid parameter types, such as non-integer product_id or variation_id.Laravel validation JSON with message and typically an errors object.
Example request
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"
Example JSON response (200)
{
  "data": {
    "available": false,
    "sku": "SKU001"
  }
}

Check product name availability

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.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/products/check-name
AuthenticationBearer token or API key + secret required.
Permissionproduct.create or product.update
Business scopeChecks only product names in the token user’s business.
Comparison ruleExact match against products.name after trimming the provided value.
Edit supportUse product_id to exclude the current product while editing.

Required headers

HeaderRequiredDescription
AuthorizationYesBearer YOUR_ACCESS_TOKEN
AcceptNoRecommended: application/json.

Query parameters

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.

Availability result object

Field Type Description
availablebooleantrue when no other product in the business uses the same name after exclusions.
namestring | nullThe normalized candidate name that was checked. Returns null when the incoming value is blank.

Status codes

StatusWhen it happensResponse shape
200Name check completed successfully.{ "data": { "available": boolean, "name": string | null } }
403The token user lacks both product.create and product.update.{ "message": "Unauthorized" }
422Invalid parameter types, such as a non-integer product_id.Laravel validation JSON with message and typically an errors object.
Example request
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"
Example JSON response (200)
{
  "data": {
    "available": true,
    "name": "Example Product"
  }
}

Get 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.

Use Get product when you need the complete product document. For list/search UIs or catalog sync indexes, prefer List products because it is intentionally smaller and paginated.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/products/{id}
AuthenticationBearer token or API key + secret required.
Permissionproduct.view
Business scopeReturns only products belonging to the token user’s business_id.
ExclusionsProducts with type = modifier are never returned.
JSON responseTop-level data object with nested linked entities and variations.
CSV responseSingle-row file with one column, data_json, containing the serialized JSON data object.

Path parameters

Parameter Type Required Description
id integer Yes Primary key of the product to fetch.

Query parameters

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.

Required headers

HeaderRequiredDescription
AuthorizationYesBearer YOUR_ACCESS_TOKEN
AcceptNoRecommended: application/json.

Top-level JSON response

Field Type Description
dataProductDetailComplete product document.

ProductDetail object

Field Type Description
idintegerProduct primary key.
namestringProduct display name.
skustringProduct-level SKU.
typestringProduct type such as single, variable, or combo.
imagestring | nullStored filename of the primary image when present.
image_urlstring | nullResolved image URL when a custom primary image exists.
barcode_typestring | nullBarcode type stored on the product.
tax_typestring | nullinclusive or exclusive when set.
enable_stockbooleanWhether stock tracking is enabled.
not_for_sellingbooleanWhether the product is blocked from selling flows.
is_inactivebooleanWhether the product is marked inactive.
unitUnitSummary | nullLinked unit record, if any.
categoryNamedEntity | nullPrimary category, if any.
sub_categoryNamedEntity | nullSub-category, if any.
brandNamedEntity | nullBrand, if any.
tax_rateTaxRateSummary | nullLinked tax rate, if any.
locationsarray<LocationSummary>Business locations where the product is assigned.
variationsarray<ProductVariation>All loaded variation rows for the product.

Nested object schemas

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 codes

StatusWhen it happensResponse shape
200The product was found and returned as JSON or CSV.JSON: { "data": { ... } }. CSV: one-row file with data_json.
403The token user lacks product.view.{ "message": "Unauthorized" }
404The product id does not exist in the business or refers to a modifier product.{ "message": "Not found" }
422Unsupported query parameters such as an invalid format.Laravel validation JSON with message and typically an errors object.
Example request (JSON)
curl -s "https://billdiary.com/api/v1/integration/products/1" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"
Example request (CSV)
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
Example JSON response (200)
{
  "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 }
        ]
      }
    ]
  }
}
Example error response (404)
{
  "message": "Not found"
}

Create product

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.

Use 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.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/products
AuthenticationBearer token or API key + secret required.
Permissionproduct.create
SubscriptionRequires an active package subscription when superadmin package checks are enabled.
QuotaRequires available product quota; otherwise the request is rejected before validation.
Demo modeReturns 403 in demo environments.
Success response201 with a compact created-product summary.

Required headers

HeaderRequiredDescription
AuthorizationYesBearer YOUR_ACCESS_TOKEN
Content-TypeYesapplication/json or multipart/form-data when sending image.
AcceptNoRecommended: application/json.

Common request fields

FieldTypeRequiredDescription
typestringNosingle (default), variable, or combo.
namestringYesProduct name.
unit_idintegerYesUnit id that belongs to the current business.
skustring | nullNoOptional custom product SKU. Leave blank to use the system flow.
category_idinteger | nullNoProduct category id for this business.
sub_category_idinteger | nullNoProduct sub-category id for this business.
brand_idinteger | nullNoBrand id for this business.
taxinteger | nullNoTax rate id for this business.
tax_typestring | nullNoexclusive or inclusive.
barcode_typestring | nullNoBarcode type such as C128.
enable_stockbooleanNoWhether stock tracking is enabled.
not_for_sellingbooleanNoWhether the product is blocked from selling flows.
product_locationsarray<integer>NoBusiness location ids where this product should be assigned.
imagefileNoPrimary image upload. Allowed only in multipart requests. Must pass image validation and the app’s configured upload size limit.

Fields for type=single

FieldTypeRequiredDescription
purchase_price_ex_taxnumberYesDefault variation purchase price excluding tax.
purchase_price_inc_taxnumberYesDefault variation purchase price including tax.
selling_price_ex_taxnumberYesDefault variation sell price excluding tax.
selling_price_inc_taxnumberYesDefault variation sell price including tax.
profit_percentnumber | nullNoOptional profit percentage. If omitted, business defaults are used.

Fields for type=variable

FieldTypeRequiredDescription
sku_typestringYeswith_out_variation or with_variation, matching the web form behaviour.
product_variationarrayYesVariation group definitions to create.
product_variation[].variation_template_idinteger | nullConditionalExisting variation template id for this business. Either this field or name is required for each group.
product_variation[].namestring | nullConditionalTemplate name to use when you are not referencing an existing template id.
product_variation[].variationsarrayYesVariation rows within the group.
product_variation[].variations[].valuestringYesVariation value label.
product_variation[].variations[].sub_skustring | nullNoOptional variation SKU.
product_variation[].variations[].variation_value_idinteger | nullNoExisting variation value id when appending to a template-driven structure.
product_variation[].variations[].default_purchase_pricenumberYesVariation purchase price excluding tax.
product_variation[].variations[].dpp_inc_taxnumberYesVariation purchase price including tax.
product_variation[].variations[].profit_percentnumber | nullNoOptional variation profit percentage.
product_variation[].variations[].default_sell_pricenumberYesVariation sell price excluding tax.
product_variation[].variations[].sell_price_inc_taxnumberYesVariation sell price including tax.

Fields for type=combo

FieldTypeRequiredDescription
combo_linesarrayYesLines that make up the combo product.
combo_lines[].variation_idintegerYesVariation id from an existing product in this business.
combo_lines[].quantitynumberYesQuantity used for the combo line.
combo_lines[].unit_idintegerYesUnit id for the combo line.
item_level_purchase_price_totalnumberYesTotal purchase price of the combo based on its component items.
purchase_price_inc_taxnumberYesCombo purchase price including tax.
selling_pricenumberYesCombo selling price excluding tax.
selling_price_inc_taxnumberYesCombo selling price including tax.
profit_percentnumber | nullNoOptional profit percentage.

Success response

FieldTypeDescription
messagestringLocalized success message.
data.idintegerCreated product id.
data.namestringCreated product name.
data.skustringStored product SKU.
data.typestringCreated product type.
data.imagestring | nullStored primary image filename when an image was saved.
data.image_urlstring | nullResolvable primary image URL when an image was saved.

Status codes

StatusWhen it happensResponse shape
201Product created successfully.{ "message": string, "data": { ... } }
402Subscription expired or maximum product quota reached.{ "message": string }
403Demo mode or missing product.create permission.{ "message": string }
422Validation 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.
500The create transaction failed unexpectedly.{ "message": "something_went_wrong" }
Example request (single product, JSON)
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
  }'
Example JSON response (201)
{
  "message": "Product added successfully",
  "data": {
    "id": 15,
    "name": "API Product",
    "sku": "SKU0015",
    "type": "single",
    "image": null,
    "image_url": null
  }
}

Update product

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).

Endpoint summary

PropertyValue
MethodsPATCH or PUT
Path/api/v1/integration/products/{id}
AuthenticationBearer token or API key + secret required.
Permissionproduct.update
Demo modeReturns 403 in demo environments.
Request encodingapplication/json or multipart/form-data when replacing the primary image.
Success response200 with the same ProductDetail object documented under Get product.

Common updatable fields

FieldTypeRequiredDescription
namestringNoUpdated product name.
unit_idintegerNoReplacement unit id for this business.
skustring | nullNoReplacement product SKU. Blank values are normalized by the controller.
category_idinteger | nullNoUpdated category id.
sub_category_idinteger | nullNoUpdated sub-category id.
brand_idinteger | nullNoUpdated brand id.
taxinteger | nullNoUpdated tax rate id.
barcode_typestring | nullNoUpdated barcode type.
tax_typestring | nullNoinclusive or exclusive.
enable_stockbooleanNoUpdated stock-tracking flag.
not_for_sellingbooleanNoUpdated selling availability flag.
is_inactivebooleanNoUpdated inactive flag.
product_locationsarray<integer>NoReplaces the product’s assigned locations. Omit to leave unchanged; send [] to clear all assignments.
imagefileNoMultipart file upload to replace the current primary image.

Type-specific update rules

Product typePayload shapeNotes
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 codes

StatusWhen it happensResponse shape
200Product updated successfully.{ "message": string, "data": ProductDetail }
403Demo mode or missing product.update permission.{ "message": string }
404The product id is missing, outside the business, or refers to an unsupported row.{ "message": "Not found" }
422Validation 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.
500The update transaction failed unexpectedly.{ "message": "something_went_wrong" }
Example request
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}'
Example JSON response (200)
{
  "message": "Product updated successfully",
  "data": {
    "id": 1,
    "name": "Renamed product"
  }
}

Delete 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.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/products/{id}
Permissionproduct.delete
Demo modeReturns 403 in demo environments.
Success response200 with the deleted product id.

Status codes

StatusWhen it happensResponse shape
200The product was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode or missing product.delete permission.{ "message": string }
404The product id was not found in the current business.{ "message": "Not found" }
422A business rule blocked deletion, for example purchase history, sold opening stock, stock adjustments, non-stock sales usage, or manufacturing ingredient references.{ "message": string }
500The delete transaction failed unexpectedly.{ "message": "something_went_wrong" }
Example request
curl -s -X DELETE "https://billdiary.com/api/v1/integration/products/1" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"
Example JSON response (200)
{
  "message": "Product deleted successfully",
  "data": {
    "id": 1
  }
}

Product stock history

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.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/products/{id}/stock-history
Permissionproduct.view
ScopeOne product, one variation, one business location.
JSON responseReturns data with product/variation context, summary, and movements.
CSV responseReturns only movement rows; summary is JSON-only.

Query parameters

ParameterTypeRequiredDescription
location_idintegerYesBusiness location id that belongs to the current business.
variation_idintegerConditionalRequired when the product has more than one variation. Omit only when the product has exactly one variation.
qstringNoOptional movement search filter. Minimum 2 characters when provided. Matches movement labels, references, contact fields, notes, and transaction id text.
formatstringNojson (default) or csv.

Top-level JSON response

FieldTypeDescription
data.product_idintegerProduct id.
data.product_namestringProduct name.
data.variation_idintegerResolved variation id.
data.variation_namestringVariation name.
data.sub_skustring | nullVariation SKU.
data.location_idintegerLocation id.
data.location_namestring | nullLocation name.
data.summaryobjectStock summary from the same business logic as the web stock detail screen. Includes at least current stock information.
data.movementsarray<MovementRow>Movement ledger in newest-first order.
data.qstring | nullEcho of the applied search term.

MovementRow object

FieldTypeDescription
datestringISO 8601 movement timestamp.
transaction_idinteger | nullRelated transaction id when available.
typestring | nullInternal movement type key.
type_labelstring | nullUser-facing movement label.
badge_typestring | nullBadge style/type metadata for the UI-originated stock history data.
ref_nostring | nullReference number when present.
quantity_changenumber | nullQuantity delta for the movement.
stocknumber | nullRunning stock after the movement.
contact_namestring | nullRelated contact name when present.
supplier_business_namestring | nullRelated supplier business name when present.
additional_notesstring | nullOptional movement notes.

Status codes

StatusWhen it happensResponse shape
200Stock history returned successfully.JSON { "data": { ... } } or CSV file.
403Missing product.view permission.{ "message": "Unauthorized" }
404Product or variation was not found in the current business.{ "message": "Not found" } or { "message": "Variation not found." }
422Invalid location, missing variation for multi-variation products, variation/product mismatch, or q shorter than 2 characters.{ "message": string } or Laravel validation JSON.

Get product selling price group prices

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.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/products/{id}/selling-price-groups
Permissionproduct.view
Formatsjson (default) or csv
Success response200 with the product selling price group matrix.

Response schema

FieldTypeDescription
data.product_idintegerProduct id.
data.selling_price_groupsarrayActive selling price groups for the business. Each row includes id, name, and description.
data.variationsarrayVariation rows for the product. Each row includes id, name, sub_sku, and product_variation_id.
data.group_pricesobjectNested matrix keyed as group_prices[selling_price_group_id][variation_id] with price and price_type.

Query parameters

ParameterTypeRequiredDescription
formatstringNojson (default) or csv. CSV flattens each populated matrix cell into one row.

Status codes

StatusWhen it happensResponse shape
200The price-group matrix was returned successfully.JSON { "data": { ... } } or CSV download.
403The token user lacks product.view.{ "message": "Unauthorized" }
404The product id does not exist in the current business.{ "message": "Not found" }
422The query string failed validation, such as an unsupported format.Laravel validation JSON.

Update product selling price group prices (PATCH)

Partially updates the selling price matrix for a product. Only the supplied matrix cells are upserted; omitted groups and variations are left unchanged.

Endpoint summary

PropertyValue
MethodsPATCH or PUT
Path/api/v1/integration/products/{id}/selling-price-groups
Permissionproduct.create
Demo modeReturns 403 in demo environments.
Success response200 with the updated product selling price group matrix.

Request body

FieldTypeRequiredDescription
group_pricesobjectYesNested matrix keyed by selling price group id, then variation id.
group_prices.{group_id}.{variation_id}.pricenumberYesPrice including tax for that matrix cell.
group_prices.{group_id}.{variation_id}.price_typestringYesfixed or percentage.

Status codes

StatusWhen it happensResponse shape
200Matrix updated successfully.{ "message": string, "data": { ...same as GET... } }
403Demo mode or missing product.create permission.{ "message": string }
404The product id was not found in the current business.{ "message": "Not found" }
422Invalid group ids, variation mismatch, malformed matrix entries, non-numeric price values, or unsupported price_type.{ "message": string } or Laravel validation JSON.
500The update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Bulk update prices by SKU (POST)

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.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/product-prices/bulk-update
Permissionproduct.update
Demo modeReturns 403 in demo environments.
Request limit1 to 500 update rows.
Success response200 with processed row count.

Request body

FieldTypeRequiredDescription
rowsarrayYes1 to 500 update rows.
rows[].sub_skustringYesVariation SKU to locate.
rows[].sell_price_inc_taxnumber | nullNoUpdated default selling price including tax.
rows[].group_pricesobject | nullNoSelling price group matrix for that variation. At least one of this field or sell_price_inc_tax is required.
rows[].group_prices.{group_id}.pricenumberYesUpdated selling price group price.
rows[].group_prices.{group_id}.price_typestringNofixed or percentage. Defaults to fixed when omitted.

Status codes

StatusWhen it happensResponse shape
200Rows were processed successfully.{ "message": string, "data": { "rows_processed": integer } }
403Demo mode or missing product.update permission.{ "message": string }
422Missing 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.
500The bulk update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Bulk edit products (web parity, POST)

Bulk-edits multiple products and multiple variations in one request, using the same persistence path as the web bulk edit screen.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/products/bulk-update
Permissionproduct.update
Demo modeReturns 403 in demo environments.
Request limit1 to 100 product payloads.
Success response200 with updated product count.

Request body

FieldTypeRequiredDescription
productsarrayYes1 to 100 product update payloads.
products[].idintegerYesProduct id to update.
products[].category_idinteger | nullNoReplacement category id.
products[].sub_category_idinteger | nullNoReplacement sub-category id.
products[].brand_idinteger | nullNoReplacement brand id.
products[].taxinteger | nullNoReplacement tax rate id.
products[].product_locationsarray<integer> | nullNoReplacement set of location assignments.
products[].variationsarrayYesAt least one variation update row.
products[].variations[].idintegerYesVariation id belonging to the current product.
products[].variations[].default_purchase_pricenumberYesUpdated purchase price excluding tax.
products[].variations[].dpp_inc_taxnumberYesUpdated purchase price including tax.
products[].variations[].profit_percentnumberYesUpdated profit percentage.
products[].variations[].default_sell_pricenumberYesUpdated sell price excluding tax.
products[].variations[].sell_price_inc_taxnumberYesUpdated sell price including tax.
products[].variations[].group_pricesobject | nullNoOptional map of selling price group id to numeric price.

Status codes

StatusWhen it happensResponse shape
200Products were updated successfully.{ "message": string, "data": { "updated_count": integer } }
403Demo mode or missing product.update permission.{ "message": string }
422Validation failed or a selling price group id in the payload is invalid or inactive.{ "message": string } or Laravel validation JSON.
500An unexpected model lookup or transaction error occurred during the bulk update.{ "message": "something_went_wrong" }

Import products CSV (POST multipart)

Imports products from a spreadsheet using the same importer and template rules as the web product import flow.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/import/products
Permissionproduct.create
Encodingmultipart/form-data
Execution contextRuns through the same importer used by the web product import flow.
Demo modeReturns 403 in demo environments.
FieldTypeRequiredDescription
products_csvfileYesMultipart file upload containing the product import sheet.

Status codes

StatusWhen it happensResponse shape
200The import completed successfully.{ "success": 1, "message": string }
403Demo mode or missing product.create permission.{ "message": string }
422The file was missing, malformed, or the importer rejected the uploaded sheet.{ "success": 0, "message": string } or Laravel validation JSON.

Import opening stock CSV (POST multipart)

Imports opening stock rows using the same spreadsheet rules as the web opening stock importer.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/import/opening-stock
Permissionproduct.opening_stock
Encodingmultipart/form-data
Execution contextRuns through the same importer used by the web opening stock import flow.
Demo modeReturns 403 in demo environments.
FieldTypeRequiredDescription
products_csvfileYesMultipart file upload for the opening stock template.

Status codes

StatusWhen it happensResponse shape
200The import completed successfully.{ "success": 1, "message": string }
403Demo mode or missing product.opening_stock permission.{ "message": string }
422The file was missing, malformed, or the importer rejected the uploaded sheet.{ "success": 0, "message": string } or Laravel validation JSON.

Import sales (spreadsheet)

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.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/import/sales
Permissionsell.create
Encodingmultipart/form-data
Execution contextRuns through the same importer and session bridge used by the web sales import flow.
Demo modeReturns 403 in demo environments.
FieldTypeRequiredDescription
salesfileYesSpreadsheet file in the same layout as the web sales import template.
location_idintegerYesBusiness location id for the imported invoices.
group_byintegerYesZero-based column index used to group rows into invoices.
import_fieldsarray | JSON stringYesMap of spreadsheet column index to importer field key. The controller also accepts this field as a JSON string.
The final field map must include customer phone or email, product name or SKU, quantity, and unit price, and it must not contain duplicate field keys.

Status codes

StatusWhen it happensResponse shape
200The import completed successfully.{ "success": 1, "message": string, "import_batch": integer }
403Demo mode or missing sell.create permission.{ "message": string }
422The 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.

List variation templates

Lists variation templates available in the current business. Each template includes its values and the number of product variation groups currently referencing it.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/variation-templates
Permissionsproduct.view or product.create
PaginationSupports per_page and page.
SearchSupports q and legacy search. q must be at least 2 characters when provided.
Sortingsort=name|created_at and direction=asc|desc
Formatsjson (default) or csv

Query parameters

ParameterTypeRequiredDescription
per_pageintegerNoPage size from 1 to 100. Defaults to 20.
pageintegerNoPagination page number.
qstringNoSearch term for template names and value names. Minimum 2 characters when provided.
searchstringNoLegacy alias for search.
sortstringNoname or created_at.
directionstringNoasc or desc.
formatstringNojson (default) or csv. CSV returns all filtered rows and ignores pagination.

Top-level JSON response

FieldTypeDescription
dataarray<VariationTemplate>Filtered variation templates.
metaobjectPagination metadata with current_page, last_page, per_page, and total.

VariationTemplate object

FieldTypeDescription
idintegerTemplate id.
namestringTemplate name.
valuesarrayVariation values; each entry has id and name.
product_variations_countintegerHow many product variation groups currently reference this template.
created_atstring | nullCreation timestamp.
updated_atstring | nullLast update timestamp.

Status codes

StatusWhen it happensResponse shape
200Variation templates were returned successfully.JSON { "data": [...], "meta": { ... } } or CSV download.
403The token user lacks both product.view and product.create.{ "message": "Unauthorized" }
422q is shorter than 2 characters or another query parameter failed validation.Laravel validation JSON.
500The controller failed while listing templates.{ "message": "Could not list variation templates" }

Get variation template

Returns one variation template record for the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/variation-templates/{id}
Permissionsproduct.view or product.create
Formatsjson (default) or csv
Success response200 with one VariationTemplate object.

Query parameters

ParameterTypeRequiredDescription
formatstringNojson (default) or csv. CSV returns a one-row file with the template payload in data_json.

Top-level JSON response

FieldTypeDescription
dataVariationTemplateThe requested template object.

Status codes

StatusWhen it happensResponse shape
200The template was found and returned.JSON { "data": { ... } } or CSV download.
403The token user lacks both product.view and product.create.{ "message": "Unauthorized" }
404The template id does not exist in the current business.{ "message": "Not found" }

Create variation template

Creates a new variation template with optional initial value rows.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/variation-templates
Permissionproduct.create
Demo modeReturns 403 in demo environments.
Success response201 with the created VariationTemplate.

Request body

FieldTypeRequiredDescription
namestringYesTemplate name.
variation_valuesarray<string>NoOptional values to create under the template, such as ["S","M","L"].

Status codes

StatusWhen it happensResponse shape
201The template was created successfully.{ "message": string, "data": VariationTemplate }
403Demo mode or missing product.create permission.{ "message": string }
422The request body failed validation.Laravel validation JSON.
500The create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Update variation template

Updates a variation template by renaming the template, renaming existing values, and/or appending new values.

Endpoint summary

PropertyValue
MethodsPATCH or PUT
Path/api/v1/integration/variation-templates/{id}
Permissionproduct.create
Demo modeReturns 403 in demo environments.
Success response200 with the updated VariationTemplate.

Request body

FieldTypeRequiredDescription
namestringNoRenames the template and synced product variation group labels.
edit_variation_valuesobjectNoMap of existing value id to new value name.
variation_valuesarray<string>NoAdditional values to append to the template.

Status codes

StatusWhen it happensResponse shape
200The template was updated successfully.{ "message": string, "data": VariationTemplate }
403Demo mode or missing product.create permission.{ "message": string }
404The template id does not exist in the current business.{ "message": "Not found" }
422The request body failed validation or a referenced variation value id does not belong to the template.{ "message": string } or Laravel validation JSON.
500The update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete variation template

Deletes a variation template when it is no longer referenced by product variation groups.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/variation-templates/{id}
Permissionproduct.create
Demo modeReturns 403 in demo environments.
Success response200 with deleted id.

Status codes

StatusWhen it happensResponse shape
200The template was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode or missing product.create permission.{ "message": string }
404The template id does not exist in the current business.{ "message": "Not found" }
422The template is still in use by one or more product variation groups.{ "message": string }
500The delete operation failed unexpectedly.{ "message": "something_went_wrong" }

List brands

Lists brands in the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/brands
Permissionsbrand.view or brand.create
PaginationSupports per_page and page.
SearchSupports q and legacy search.
Sortingsort=name|created_at and direction=asc|desc
Formatsjson (default) or csv

Query parameters

ParameterTypeRequiredDescription
per_pageintegerNoPage size from 1 to 100. Defaults to 20.
pageintegerNoPagination page number.
qstringNoSearch term for brand name or description. Minimum 2 characters when provided.
searchstringNoLegacy alias for search.
sortstringNoname or created_at.
directionstringNoasc or desc.
formatstringNojson (default) or csv. CSV returns all filtered rows and ignores pagination.

Top-level JSON response

FieldTypeDescription
dataarray<Brand>Filtered brand records.
metaobjectPagination metadata with current_page, last_page, per_page, and total.

Brand object

FieldTypeDescription
idintegerBrand id.
namestringBrand name.
descriptionstring | nullOptional description.
use_for_repairbooleanPresent when the Repair module is installed.
created_atstring | nullCreation timestamp.
updated_atstring | nullLast update timestamp.

Status codes

StatusWhen it happensResponse shape
200Brands were returned successfully.JSON { "data": [...], "meta": { ... } } or CSV download.
403The token user lacks both brand.view and brand.create.{ "message": "Unauthorized" }
422q is shorter than 2 characters or another query parameter failed validation.Laravel validation JSON.
500The controller failed while listing brands.{ "message": "Could not list brands" }

Get brand

Returns one brand record for the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/brands/{id}
Permissionsbrand.view or brand.create
Formatsjson (default) or csv
Success response200 with one Brand object.

Query parameters

ParameterTypeRequiredDescription
formatstringNojson (default) or csv. CSV returns a one-row file with the brand payload in data_json.

Top-level JSON response

FieldTypeDescription
dataBrandThe requested brand object.

Status codes

StatusWhen it happensResponse shape
200The brand was found and returned.JSON { "data": { ... } } or CSV download.
403The token user lacks both brand.view and brand.create.{ "message": "Unauthorized" }
404The brand id does not exist in the current business.{ "message": "Not found" }

Create brand

Creates a new brand for the current business.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/brands
Permissionbrand.create
Demo modeReturns 403 in demo environments.
Success response201 with the created Brand.

Request body

FieldTypeRequiredDescription
namestringYesBrand name.
descriptionstring | nullNoOptional brand description.
use_for_repairbooleanNoOptional Repair-module flag.

Status codes

StatusWhen it happensResponse shape
201The brand was created successfully.{ "data": Brand }
403Demo mode or missing brand.create permission.{ "message": string }
422The request body failed validation.Laravel validation JSON.
500The create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Update brand

Updates a brand record in the current business.

Endpoint summary

PropertyValue
MethodsPATCH or PUT
Path/api/v1/integration/brands/{id}
Permissionbrand.update
Demo modeReturns 403 in demo environments.
Success response200 with the updated Brand.

Request body

FieldTypeRequiredDescription
namestringYesBrand name.
descriptionstring | nullNoOptional brand description.
use_for_repairbooleanNoOptional Repair-module flag when that module is installed.

Status codes

StatusWhen it happensResponse shape
200The brand was updated successfully.{ "data": Brand }
403Demo mode or missing brand.update permission.{ "message": string }
404The brand id does not exist in the current business.{ "message": "Not found" }
422The request body failed validation.Laravel validation JSON.
500The update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete brand

Soft-deletes a brand record in the current business.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/brands/{id}
Permissionbrand.delete
Demo modeReturns 403 in demo environments.
Success response200 with deleted id.

Status codes

StatusWhen it happensResponse shape
200The brand was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode or missing brand.delete permission.{ "message": string }
404The brand id does not exist in the current business.{ "message": "Not found" }
500The delete transaction failed unexpectedly.{ "message": "something_went_wrong" }

List units

Lists units in the current business, including base-unit relationships and the display label used by the web UI.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/units
Permissionsunit.view or unit.create
PaginationSupports per_page and page.
SearchSupports q and legacy search.
Sortingsort=actual_name|short_name|created_at and direction=asc|desc
Formatsjson (default) or csv

Query parameters

ParameterTypeRequiredDescription
per_pageintegerNoPage size from 1 to 100. Defaults to 20.
pageintegerNoPagination page number.
qstringNoSearch term for unit names. Minimum 2 characters when provided.
searchstringNoLegacy alias for search.
sortstringNoactual_name, short_name, or created_at.
directionstringNoasc or desc.
formatstringNojson (default) or csv. CSV returns all filtered rows and ignores pagination.

Top-level JSON response

FieldTypeDescription
dataarray<Unit>Filtered unit records.
metaobjectPagination metadata with current_page, last_page, per_page, and total.

Unit object

FieldTypeDescription
idintegerUnit id.
actual_namestringFull unit name.
short_namestringShort display name.
allow_decimalbooleanWhether decimal quantities are allowed.
base_unit_idinteger | nullParent/base unit id when this is a sub-unit.
base_unit_multipliernumber | nullMultiplier against the base unit.
display_namestringUI-friendly label.
base_unitobject | nullBase unit summary with id, actual_name, and short_name.
created_atstring | nullCreation timestamp.
updated_atstring | nullLast update timestamp.

Status codes

StatusWhen it happensResponse shape
200Units were returned successfully.JSON { "data": [...], "meta": { ... } } or CSV download.
403The token user lacks both unit.view and unit.create.{ "message": "Unauthorized" }
422q is shorter than 2 characters or another query parameter failed validation.Laravel validation JSON.
500The controller failed while listing units.{ "message": "Could not list units" } or a generic error message.

Get unit

Returns one unit record for the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/units/{id}
Permissionsunit.view or unit.create
Formatsjson (default) or csv
Success response200 with one Unit object.

Query parameters

ParameterTypeRequiredDescription
formatstringNojson (default) or csv. CSV returns a one-row file with the unit payload in data_json.

Top-level JSON response

FieldTypeDescription
dataUnitThe requested unit object.

Status codes

StatusWhen it happensResponse shape
200The unit was found and returned.JSON { "data": { ... } } or CSV download.
403The token user lacks both unit.view and unit.create.{ "message": "Unauthorized" }
404The unit id does not exist in the current business.{ "message": "Not found" }

Create unit

Creates a new base unit or sub-unit for the current business.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/units
Permissionunit.create
Demo modeReturns 403 in demo environments.
Success response201 with the created Unit.

Request body

FieldTypeRequiredDescription
actual_namestringYesFull unit name.
short_namestringYesShort unit label.
allow_decimalbooleanYesWhether decimal quantities are allowed.
base_unit_idinteger | nullNoBase unit id when creating a sub-unit.
base_unit_multipliernumber | nullNoRequired together with base_unit_id for sub-units.

Status codes

StatusWhen it happensResponse shape
201The unit was created successfully.{ "data": Unit }
403Demo mode or missing unit.create permission.{ "message": string }
422The request body failed validation, the base unit was invalid, or the multiplier normalized to zero.Laravel validation JSON or { "message": string }.
500The create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Update unit

Updates a unit record, including whether it should behave as a base unit or a sub-unit.

Endpoint summary

PropertyValue
MethodsPATCH or PUT
Path/api/v1/integration/units/{id}
Permissionunit.update
Demo modeReturns 403 in demo environments.
Success response200 with the updated Unit.

Request body

FieldTypeRequiredDescription
actual_namestringYesFull unit name.
short_namestringYesShort unit label.
allow_decimalbooleanYesWhether decimal quantities are allowed.
define_base_unitbooleanYesWhen 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_idinteger | nullConditionalRequired when define_base_unit=true.
base_unit_multipliernumber | nullConditionalRequired when define_base_unit=true and must normalize to a non-zero value.

Status codes

StatusWhen it happensResponse shape
200The unit was updated successfully.{ "data": Unit }
403Demo mode or missing unit.update permission.{ "message": string }
404The unit id does not exist in the current business.{ "message": "Not found" }
422The 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 }.
500The update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete unit

Soft-deletes a unit when no products still reference it.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/units/{id}
Permissionunit.delete
Demo modeReturns 403 in demo environments.
Success response200 with deleted id.

Status codes

StatusWhen it happensResponse shape
200The unit was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode or missing unit.delete permission.{ "message": string }
404The unit id does not exist in the current business.{ "message": "Not found" }
422The unit is still referenced by one or more products.{ "message": string }
500The delete operation failed unexpectedly.{ "message": "something_went_wrong" }

List product categories

Lists product categories in the current business, including parent/sub-category relationships.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/product-categories
Permissionscategory.view or category.create
PaginationSupports per_page and page.
SearchSupports q and legacy search.
Sortingsort=name|short_code|created_at and direction=asc|desc
Formatsjson (default) or csv

Query parameters

ParameterTypeRequiredDescription
per_pageintegerNoPage size from 1 to 100. Defaults to 20.
pageintegerNoPagination page number.
qstringNoSearch term for category name, short code, or description. Minimum 2 characters when provided.
searchstringNoLegacy alias for search.
sortstringNoname, short_code, or created_at.
directionstringNoasc or desc.
formatstringNojson (default) or csv. CSV returns all filtered rows and ignores pagination.

Top-level JSON response

FieldTypeDescription
dataarray<ProductCategory>Filtered product category records.
metaobjectPagination metadata with current_page, last_page, per_page, and total.

ProductCategory object

FieldTypeDescription
idintegerCategory id.
namestringCategory name.
short_codestring | nullOptional short code.
descriptionstring | nullOptional description.
category_typestringAlways product in this endpoint family.
parent_idinteger | nullParent category id when this row is a child.
is_sub_categorybooleanWhether the row is a sub-category.
display_namestringUI-friendly label.
parentobject | nullParent summary with id and name.
sub_categoriesarrayImmediate child categories.
created_atstring | nullCreation timestamp.
updated_atstring | nullLast update timestamp.

Status codes

StatusWhen it happensResponse shape
200Product categories were returned successfully.JSON { "data": [...], "meta": { ... } } or CSV download.
403The token user lacks both category.view and category.create.{ "message": "Unauthorized" }
422q is shorter than 2 characters or another query parameter failed validation.Laravel validation JSON.
500The controller failed while listing categories.{ "message": "Could not list categories" } or a generic error message.

Get product category

Returns one product category record for the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/product-categories/{id}
Permissionscategory.view or category.create
Formatsjson (default) or csv
Success response200 with one ProductCategory object.

Query parameters

ParameterTypeRequiredDescription
formatstringNojson (default) or csv. CSV returns a one-row file with the category payload in data_json.

Top-level JSON response

FieldTypeDescription
dataProductCategoryThe requested product category object.

Status codes

StatusWhen it happensResponse shape
200The category was found and returned.JSON { "data": { ... } } or CSV download.
403The token user lacks both category.view and category.create.{ "message": "Unauthorized" }
404The category id does not exist in the current business.{ "message": "Not found" }

Create product category

Creates a new product category for the current business.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/product-categories
Permissioncategory.create
Demo modeReturns 403 in demo environments.
Success response201 with the created ProductCategory.

Request body

FieldTypeRequiredDescription
namestringYesCategory name.
short_codestring | nullNoOptional short code.
descriptionstring | nullNoOptional description.
parent_idinteger | nullNoUse 0 or omit for a top-level category; otherwise provide a valid top-level product category id.

Status codes

StatusWhen it happensResponse shape
201The category was created successfully.{ "data": ProductCategory }
403Demo mode or missing category.create permission.{ "message": string }
422The 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" }.
500The create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Update product category

Updates an existing product category, including whether it remains top-level or becomes a child category.

Endpoint summary

PropertyValue
MethodsPATCH or PUT
Path/api/v1/integration/product-categories/{id}
Permissioncategory.update
Demo modeReturns 403 in demo environments.
Success response200 with the updated ProductCategory.

Request body

FieldTypeRequiredDescription
namestringYesCategory name.
short_codestring | nullNoOptional short code.
descriptionstring | nullNoOptional description.
parent_idintegerYesUse 0 to keep the category top-level, or provide a valid top-level product category id to make it a child.

Status codes

StatusWhen it happensResponse shape
200The category was updated successfully.{ "data": ProductCategory }
403Demo mode or missing category.update permission.{ "message": string }
404The category id does not exist in the current business.{ "message": "Not found" }
422The request body failed validation, the category tried to become its own parent, or parent_id is invalid.Laravel validation JSON or { "message": string }.
500The update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete product category

Soft-deletes a product category record in the current business.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/product-categories/{id}
Permissioncategory.delete
Demo modeReturns 403 in demo environments.
Success response200 with deleted id.

Status codes

StatusWhen it happensResponse shape
200The category was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode or missing category.delete permission.{ "message": string }
404The category id does not exist in the current business.{ "message": "Not found" }
500The delete operation failed unexpectedly.{ "message": "something_went_wrong" }

List tax rates

Lists standalone tax rates in the current business. Tax groups are excluded from this endpoint family.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/tax-rates
Permissionstax_rate.view or tax_rate.create
PaginationSupports per_page and page.
SearchSupports q and legacy search. q must be at least 2 characters when provided.
Sortingsort=name|amount|created_at and direction=asc|desc
Formatsjson (default) or csv

Query parameters

ParameterTypeRequiredDescription
per_pageintegerNoPage size from 1 to 100. Defaults to 20.
pageintegerNoPagination page number.
qstringNoSearch term for tax rate names or ids. Minimum 2 characters when provided.
searchstringNoLegacy alias for search.
sortstringNoname, amount, or created_at.
directionstringNoasc or desc.
formatstringNojson (default) or csv. CSV returns all filtered rows and ignores pagination.

Top-level JSON response

FieldTypeDescription
dataarray<TaxRate>Filtered standalone tax rates.
metaobjectPagination metadata with current_page, last_page, per_page, and total.

TaxRate object

FieldTypeDescription
idintegerTax rate id.
namestringTax rate name.
amountnumberTax percentage amount.
for_tax_groupbooleanWhether the rate is intended to be used as a component inside a tax group.
is_tax_groupbooleanAlways false in this endpoint family.
created_atstring | nullCreation timestamp.
updated_atstring | nullLast update timestamp.

Status codes

StatusWhen it happensResponse shape
200Tax rates were returned successfully.JSON { "data": [...], "meta": { ... } } or CSV download.
403The token user lacks both tax_rate.view and tax_rate.create.{ "message": "Unauthorized" }
422q is shorter than 2 characters or another query parameter failed validation.Laravel validation JSON.
500The controller failed while listing tax rates.{ "message": "Could not list tax rates" }

Get tax rate

Returns one standalone tax rate record for the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/tax-rates/{id}
Permissionstax_rate.view or tax_rate.create
Formatsjson (default) or csv
Success response200 with one TaxRate object.

Query parameters

ParameterTypeRequiredDescription
formatstringNojson (default) or csv. CSV returns a one-row file with the tax rate payload in data_json.

Top-level JSON response

FieldTypeDescription
dataTaxRateThe requested standalone tax rate.

Status codes

StatusWhen it happensResponse shape
200The tax rate was found and returned.JSON { "data": { ... } } or CSV download.
403The token user lacks both tax_rate.view and tax_rate.create.{ "message": "Unauthorized" }
404The tax rate id does not exist in the current business or is not a standalone rate.{ "message": "Not found" }

Create tax rate

Creates a new standalone tax rate for the current business.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/tax-rates
Permissiontax_rate.create
Demo modeReturns 403 in demo environments.
Success response201 with the created TaxRate.

Request body

FieldTypeRequiredDescription
namestringYesTax rate name.
amountnumberYesTax percentage amount.
for_tax_groupbooleanNoWhether the rate is intended to be used only as a component in combined tax groups.

Status codes

StatusWhen it happensResponse shape
201The tax rate was created successfully.{ "data": TaxRate }
403Demo mode or missing tax_rate.create permission.{ "message": string }
422The request body failed validation.Laravel validation JSON.
500The create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Update tax rate

Updates an existing standalone tax rate in the current business.

Endpoint summary

PropertyValue
MethodsPATCH or PUT
Path/api/v1/integration/tax-rates/{id}
Permissiontax_rate.update
Demo modeReturns 403 in demo environments.
Success response200 with the updated TaxRate.

Request body

FieldTypeRequiredDescription
namestringYesTax rate name.
amountnumberYesTax percentage amount.
for_tax_groupbooleanNoWhether the rate is intended to be used as a component inside combined tax groups.

Status codes

StatusWhen it happensResponse shape
200The tax rate was updated successfully.{ "data": TaxRate }
403Demo mode or missing tax_rate.update permission.{ "message": string }
404The tax rate id does not exist in the current business or is not a standalone rate.{ "message": "Not found" }
422The request body failed validation.Laravel validation JSON.
500The update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete tax rate

Deletes a standalone tax rate when it is no longer used inside a combined tax group.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/tax-rates/{id}
Permissiontax_rate.delete
Demo modeReturns 403 in demo environments.
Success response200 with deleted id.

Status codes

StatusWhen it happensResponse shape
200The tax rate was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode or missing tax_rate.delete permission.{ "message": string }
404The tax rate id does not exist in the current business or is not a standalone rate.{ "message": "Not found" }
422The tax rate is still referenced inside a combined tax group.{ "message": string }
500The delete transaction failed unexpectedly.{ "message": "something_went_wrong" }

List combined tax groups

Lists combined tax groups in the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/tax-groups
Permissionstax_rate.view or tax_rate.create
PaginationSupports per_page and page.
SearchSupports q and legacy search. q must be at least 2 characters when provided.
Sortingsort=name|amount|created_at and direction=asc|desc
Formatsjson (default) or csv

Query parameters

ParameterTypeRequiredDescription
per_pageintegerNoPage size from 1 to 100. Defaults to 20.
pageintegerNoPagination page number.
qstringNoSearch term for tax group names or ids. Minimum 2 characters when provided.
searchstringNoLegacy alias for search.
sortstringNoname, amount, or created_at.
directionstringNoasc or desc.
formatstringNojson (default) or csv. CSV returns all filtered rows and ignores pagination.

Top-level JSON response

FieldTypeDescription
dataarray<TaxGroup>Filtered combined tax groups.
metaobjectPagination metadata with current_page, last_page, per_page, and total.

TaxGroup object

FieldTypeDescription
idintegerTax group id.
namestringTax group name.
amountnumberComputed total amount from all component rates.
is_tax_groupbooleanAlways true in this endpoint family.
sub_taxesarrayComponent tax rates; each entry has id, name, and amount.
created_atstring | nullCreation timestamp.
updated_atstring | nullLast update timestamp.

Status codes

StatusWhen it happensResponse shape
200Tax groups were returned successfully.JSON { "data": [...], "meta": { ... } } or CSV download.
403The token user lacks both tax_rate.view and tax_rate.create.{ "message": "Unauthorized" }
422q is shorter than 2 characters or another query parameter failed validation.Laravel validation JSON.
500The controller failed while listing tax groups.{ "message": "Could not list tax groups" }

Get tax group

Returns one combined tax group record for the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/tax-groups/{id}
Permissionstax_rate.view or tax_rate.create
Formatsjson (default) or csv
Success response200 with one TaxGroup object.

Query parameters

ParameterTypeRequiredDescription
formatstringNojson (default) or csv. CSV returns a one-row file with the tax group payload in data_json.

Top-level JSON response

FieldTypeDescription
dataTaxGroupThe requested combined tax group.

Status codes

StatusWhen it happensResponse shape
200The tax group was found and returned.JSON { "data": { ... } } or CSV download.
403The token user lacks both tax_rate.view and tax_rate.create.{ "message": "Unauthorized" }
404The tax group id does not exist in the current business or is not a combined tax group.{ "message": "Not found" }

Create tax group

Creates a new combined tax group from standalone tax rates in the current business.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/tax-groups
Permissiontax_rate.create
Demo modeReturns 403 in demo environments.
Success response201 with the created TaxGroup.

Request body

FieldTypeRequiredDescription
namestringYesTax group name.
sub_tax_idsarray<integer>YesStandalone tax rate ids for this business.

Status codes

StatusWhen it happensResponse shape
201The tax group was created successfully.{ "data": TaxGroup }
403Demo mode or missing tax_rate.create permission.{ "message": string }
422The 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." }.
500The create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Update tax group (PATCH)

Updates an existing combined tax group by changing its name, component rates, or both.

Endpoint summary

PropertyValue
MethodsPATCH or PUT
Path/api/v1/integration/tax-groups/{id}
Permissiontax_rate.update
Demo modeReturns 403 in demo environments.
Success response200 with the updated TaxGroup.

Request body

FieldTypeRequiredDescription
namestringNoUpdated tax group name.
sub_tax_idsarray<integer>NoReplacement set of standalone tax rate ids for the group.

Status codes

StatusWhen it happensResponse shape
200The tax group was updated successfully.{ "data": TaxGroup }
403Demo mode or missing tax_rate.update permission.{ "message": string }
404The tax group id does not exist in the current business or is not a combined tax group.{ "message": "Not found" }
422The 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 }.
500The update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete tax group

Deletes a combined tax group in the current business.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/tax-groups/{id}
Permissiontax_rate.delete
Demo modeReturns 403 in demo environments.
Success response200 with deleted id.

Status codes

StatusWhen it happensResponse shape
200The tax group was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode or missing tax_rate.delete permission.{ "message": string }
404The tax group id does not exist in the current business or is not a combined tax group.{ "message": "Not found" }
500The delete transaction failed unexpectedly.{ "message": "something_went_wrong" }

List selling price groups

Lists selling price groups in the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/selling-price-groups
Permissionproduct.create
PaginationSupports per_page and page.
SearchSupports q and legacy search. q must be at least 2 characters when provided.
Sortingsort=name|is_active|created_at and direction=asc|desc
Formatsjson (default) or csv

Query parameters

ParameterTypeRequiredDescription
per_pageintegerNoPage size from 1 to 100. Defaults to 20.
pageintegerNoPagination page number.
qstringNoSearch term for selling price group names, descriptions, or ids. Minimum 2 characters when provided.
searchstringNoLegacy alias for search.
sortstringNoname, is_active, or created_at.
directionstringNoasc or desc.
formatstringNojson (default) or csv. CSV returns all filtered rows and ignores pagination.

Top-level JSON response

FieldTypeDescription
dataarray<SellingPriceGroup>Filtered selling price groups.
metaobjectPagination metadata with current_page, last_page, per_page, and total.

SellingPriceGroup object

FieldTypeDescription
idintegerSelling price group id.
namestringGroup name.
descriptionstring | nullOptional description.
is_activebooleanWhether the group is active.
permission_namestringSpatie permission created for the group, e.g. selling_price_group.5.
created_atstring | nullCreation timestamp.
updated_atstring | nullLast update timestamp.

Status codes

StatusWhen it happensResponse shape
200Selling price groups were returned successfully.JSON { "data": [...], "meta": { ... } } or CSV download.
403The token user lacks product.create.{ "message": "Unauthorized" }
422q is shorter than 2 characters or another query parameter failed validation.Laravel validation JSON.
500The controller failed while listing selling price groups.{ "message": "Could not list selling price groups" }

Get selling price group

Returns one selling price group record for the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/selling-price-groups/{id}
Permissionproduct.create
Formatsjson (default) or csv
Success response200 with one SellingPriceGroup object.

Query parameters

ParameterTypeRequiredDescription
formatstringNojson (default) or csv. CSV returns a one-row file with the selling price group payload in data_json.

Top-level JSON response

FieldTypeDescription
dataSellingPriceGroupThe requested selling price group.

Status codes

StatusWhen it happensResponse shape
200The selling price group was found and returned.JSON { "data": { ... } } or CSV download.
403The token user lacks product.create.{ "message": "Unauthorized" }
404The selling price group id does not exist in the current business.{ "message": "Not found" }

Create selling price group

Creates a new selling price group and its matching permission row in the current business.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/selling-price-groups
Permissionproduct.create
Side effectCreates the matching permission name selling_price_group.{id} in the same transaction.
Demo modeReturns 403 in demo environments.
Success response201 with the created SellingPriceGroup.

Request body

FieldTypeRequiredDescription
namestringYesGroup name.
descriptionstring | nullNoOptional description.

Status codes

StatusWhen it happensResponse shape
201The selling price group was created successfully.{ "data": SellingPriceGroup }
403Demo mode or missing product.create permission.{ "message": string }
422The request body failed validation.Laravel validation JSON.
500The create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Update selling price group

Updates an existing selling price group in the current business.

Endpoint summary

PropertyValue
MethodsPATCH or PUT
Path/api/v1/integration/selling-price-groups/{id}
Permissionproduct.update
Demo modeReturns 403 in demo environments.
Success response200 with the updated SellingPriceGroup.

Request body

FieldTypeRequiredDescription
namestringYesSelling price group name.
descriptionstring | nullNoOptional description.

Status codes

StatusWhen it happensResponse shape
200The selling price group was updated successfully.{ "data": SellingPriceGroup }
403Demo mode or missing product.update permission.{ "message": string }
404The selling price group id does not exist in the current business.{ "message": "Not found" }
422The request body failed validation.Laravel validation JSON.
500The update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Toggle selling price group active

Toggles the active state of a selling price group and returns the updated record.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/selling-price-groups/{id}/toggle-active
Permissionproduct.create
Demo modeReturns 403 in demo environments.
Success response200 with the updated SellingPriceGroup.

Status codes

StatusWhen it happensResponse shape
200The group active state was flipped successfully.{ "data": SellingPriceGroup }
403Demo mode or missing product.create permission.{ "message": string }
404The selling price group id does not exist in the current business.{ "message": "Not found" }
500The toggle operation failed unexpectedly.{ "message": "something_went_wrong" }

Delete selling price group

Soft-deletes a selling price group in the current business.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/selling-price-groups/{id}
Permissionproduct.create
Demo modeReturns 403 in demo environments.
Success response200 with deleted id.

Status codes

StatusWhen it happensResponse shape
200The selling price group was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode or missing product.create permission.{ "message": string }
404The selling price group id does not exist in the current business.{ "message": "Not found" }
500The delete transaction failed unexpectedly.{ "message": "something_went_wrong" }

List warranties

Lists warranties available in the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/warranties
Permissionsproduct.view or product.create
PaginationSupports per_page and page.
SearchSupports q and legacy search. q must be at least 2 characters when provided.
Sortingsort=name|duration|duration_type|created_at and direction=asc|desc
Formatsjson (default) or csv

Query parameters

ParameterTypeRequiredDescription
per_pageintegerNoPage size from 1 to 100. Defaults to 20.
pageintegerNoPagination page number.
qstringNoSearch term for warranty names, descriptions, or ids. Minimum 2 characters when provided.
searchstringNoLegacy alias for search.
sortstringNoname, duration, duration_type, or created_at.
directionstringNoasc or desc.
formatstringNojson (default) or csv. CSV returns all filtered rows and ignores pagination.

Top-level JSON response

FieldTypeDescription
dataarray<Warranty>Filtered warranty records.
metaobjectPagination metadata with current_page, last_page, per_page, and total.

Warranty object

FieldTypeDescription
idintegerWarranty id.
namestringWarranty name.
descriptionstring | nullOptional description.
durationintegerWarranty length.
duration_typestringdays, months, or years.
display_namestringUI-friendly label built from name + duration.
created_atstring | nullCreation timestamp.
updated_atstring | nullLast update timestamp.

Status codes

StatusWhen it happensResponse shape
200Warranties were returned successfully.JSON { "data": [...], "meta": { ... } } or CSV download.
403The token user lacks both product.view and product.create.{ "message": "Unauthorized" }
422q is shorter than 2 characters or another query parameter failed validation.Laravel validation JSON.
500The controller failed while listing warranties.{ "message": "Could not list warranties" }

Get warranty

Returns one warranty record for the current business.

Endpoint summary

PropertyValue
MethodGET
Path/api/v1/integration/warranties/{id}
Permissionsproduct.view or product.create
Formatsjson (default) or csv
Success response200 with one Warranty object.

Query parameters

ParameterTypeRequiredDescription
formatstringNojson (default) or csv. CSV returns a one-row file with the warranty payload in data_json.

Top-level JSON response

FieldTypeDescription
dataWarrantyThe requested warranty record.

Status codes

StatusWhen it happensResponse shape
200The warranty was found and returned.JSON { "data": { ... } } or CSV download.
403The token user lacks both product.view and product.create.{ "message": "Unauthorized" }
404The warranty id does not exist in the current business.{ "message": "Not found" }

Create warranty

Creates a new warranty record for the current business.

Endpoint summary

PropertyValue
MethodPOST
Path/api/v1/integration/warranties
Permissionproduct.create
Demo modeReturns 403 in demo environments.
Success response201 with the created Warranty.

Request body

FieldTypeRequiredDescription
namestringYesWarranty name.
durationintegerYesPositive warranty length.
duration_typestringYesdays, months, or years.
descriptionstring | nullNoOptional description.

Status codes

StatusWhen it happensResponse shape
201The warranty was created successfully.{ "data": Warranty }
403Demo mode or missing product.create permission.{ "message": string }
422The request body failed validation.Laravel validation JSON.
500The create transaction failed unexpectedly.{ "message": "something_went_wrong" }

Update warranty

Updates an existing warranty record in the current business.

Endpoint summary

PropertyValue
MethodsPATCH or PUT
Path/api/v1/integration/warranties/{id}
Permissionproduct.update
Demo modeReturns 403 in demo environments.
Success response200 with the updated Warranty.

Request body

FieldTypeRequiredDescription
namestringYesWarranty name.
durationintegerYesPositive warranty length.
duration_typestringYesdays, months, or years.
descriptionstring | nullNoOptional description.

Status codes

StatusWhen it happensResponse shape
200The warranty was updated successfully.{ "data": Warranty }
403Demo mode or missing product.update permission.{ "message": string }
404The warranty id does not exist in the current business.{ "message": "Not found" }
422The request body failed validation.Laravel validation JSON.
500The update transaction failed unexpectedly.{ "message": "something_went_wrong" }

Delete warranty

Deletes a warranty record when it is no longer referenced by products or sale-line warranty data.

Endpoint summary

PropertyValue
MethodDELETE
Path/api/v1/integration/warranties/{id}
Permissionproduct.delete
Demo modeReturns 403 in demo environments.
Success response200 with deleted id.

Status codes

StatusWhen it happensResponse shape
200The warranty was deleted successfully.{ "message": string, "data": { "id": integer } }
403Demo mode or missing product.delete permission.{ "message": string }
404The warranty id does not exist in the current business.{ "message": "Not found" }
422The warranty is still assigned to a product or referenced by sale-line warranty data.{ "message": string }
500The delete transaction failed unexpectedly.{ "message": "something_went_wrong" }