Products API
28 PracBill API endpoints for products. Base URL https://billing.pracbill.com.au/api.
Get a single product category.
GET /{api_key}/category/get/{catid}
Responses
200— Success. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Get all product categories for the department, keyed by catid.
GET /{api_key}/category/getAll
Responses
200— Success. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Get a single product in the API key's department (a product in another department, or a deleted one, returns `no product found`). Returns the catalogue and stock-control fields writable through `/products/add` and `/products/update/{pid}` as well as the original product fields. (The URI is singular `product`; there is no `/products/get/{pid}` route.)
GET /{api_key}/product/get/{pid}
Responses
200— Product retrieved successfully. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Create a product in the API key's department (PRACBILL-1967). Only the fields in `ProductWriteRequest` are accepted; any other field is rejected with `unsupported field(s): ...`. `title` is required. `taxrate_id`, `catid` and `vid` must reference a live record in the key's department (0 leaves it unset). Accepts a raw JSON body or the legacy `json={...}` form.
POST /{api_key}/products/add
Request body
| Field | Type | Description |
|---|---|---|
partnumber | string | Part number (maximum 32 characters) |
vpartnumber | string | Vendor part number |
title | string | Product title (required on create; cannot be emptied) |
description | string | Long description |
cost | object | Cost price (numeric) |
ex_tax_amount | object | Price ex-tax (numeric) |
inc_tax_amount | object | Price inc-tax (numeric). Not derived from ex_tax_amount - send both if both should change |
taxrate_id | integer | Tax code id (`taxrates.id`) in the key's department; 0 clears it. Also sets the product's `taxrate` percentage from that code |
catid | integer | Category id in the key's department; 0 clears it |
vid | integer | Vendor id in the key's department; 0 clears it |
manufacturer | string | Manufacturer |
model | string | Manufacturer part number / model |
weight | object | Weight in kg |
length | integer | Length in mm |
width | integer | Width in mm |
height | integer | Height in mm |
hazmatcode | string | HAZMAT code |
tracked | object | Non-inventory item flag |
stock_control | string (none | serial | non_serial) | Stock control mode |
minstock | integer | Minimum quantity to keep in stock |
reorder_amount | object | Quantity to reorder |
stocktake_daily | object | Include in the daily stocktake report |
webstore_item | object | Show on the webstore |
description_short | string | Brief (webstore) description, HTML |
special_notes | string | Special notes |
Responses
200— Product created; returns its `pid`. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Products for a category (excludes deleted products, ordered by title). Page 1. Rows are 20 per page; the server remembers the autotable token per api key (temp file `api-{token}-products.txt`).
GET /{api_key}/products/category/{catid}
Responses
200— Request received successfully. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Products for a category (excludes deleted products, ordered by title). Page 1. Rows are 20 per page; the server remembers the autotable token per api key (temp file `api-{token}-products.txt`).
GET /{api_key}/products/category/{catid}/page
Responses
200— Request received successfully. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Products for a category (excludes deleted products, ordered by title). Rows are 20 per page; the server remembers the autotable token per api key (temp file `api-{token}-products.txt`).
GET /{api_key}/products/category/{catid}/page/{page_number}
Responses
200— Request received successfully. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
List products (page 1). Handler `ProductsGetMyPage`. Excludes deleted products, ordered by title. Rows are 20 per page; the server remembers the autotable token per api key (temp file `api-{token}-products.txt`).
GET /{api_key}/products/get
Responses
200— Request received successfully. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
List products (page 1). Alias of `/products/get`. Rows are 20 per page; the server remembers the autotable token per api key (temp file `api-{token}-products.txt`).
GET /{api_key}/products/get/page
Responses
200— Request received successfully. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
List products for the given page. Note: a second class `productsGetPage` in the same file declares the same URIs but is shadowed by `ProductsGetMyPage` (declared first, equal priority) and is never dispatched. Rows are 20 per page; the server remembers the autotable token per api key (temp file `api-{token}-products.txt`).
GET /{api_key}/products/get/page/{page_number}
Responses
200— Request received successfully. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Get all products (no pagination), keyed by pid. Includes deleted products. When the department has no products returns `success: false`, `description: no products found`.
GET /{api_key}/products/getAll
Responses
200— All products retrieved successfully. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Update a product in the API key's department (PRACBILL-1967). Only the supplied fields change, and only the fields in `ProductWriteRequest` are accepted; any other field is rejected and nothing is written. The same validation as create applies; `title` may not be emptied. A product in another department, or a deleted one, returns `no product found`. Accepts a raw JSON body or the legacy `json={...}` form.
POST /{api_key}/products/update/{pid}
Request body
| Field | Type | Description |
|---|---|---|
partnumber | string | Part number (maximum 32 characters) |
vpartnumber | string | Vendor part number |
title | string | Product title (required on create; cannot be emptied) |
description | string | Long description |
cost | object | Cost price (numeric) |
ex_tax_amount | object | Price ex-tax (numeric) |
inc_tax_amount | object | Price inc-tax (numeric). Not derived from ex_tax_amount - send both if both should change |
taxrate_id | integer | Tax code id (`taxrates.id`) in the key's department; 0 clears it. Also sets the product's `taxrate` percentage from that code |
catid | integer | Category id in the key's department; 0 clears it |
vid | integer | Vendor id in the key's department; 0 clears it |
manufacturer | string | Manufacturer |
model | string | Manufacturer part number / model |
weight | object | Weight in kg |
length | integer | Length in mm |
width | integer | Width in mm |
height | integer | Height in mm |
hazmatcode | string | HAZMAT code |
tracked | object | Non-inventory item flag |
stock_control | string (none | serial | non_serial) | Stock control mode |
minstock | integer | Minimum quantity to keep in stock |
reorder_amount | object | Quantity to reorder |
stocktake_daily | object | Include in the daily stocktake report |
webstore_item | object | Show on the webstore |
description_short | string | Brief (webstore) description, HTML |
special_notes | string | Special notes |
Responses
200— Product updated; returns the updated product detail. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Soft-delete (retire) a product in the API key's department (PRACBILL-1967). Refused, with the product left unchanged, while the product still has stock on hand: a non-zero quantity of live `inventory_items` in a stock location, or unallocated serialised units. Move or write off the stock first (e.g. `DELETE /{api_key}/stock/{pid}`). A product in another department, or one already deleted, returns `no product found`.
DELETE /{api_key}/products/{pid}
Responses
200— Product soft-deleted. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
List a product's serial numbers
GET /{api_key}/products/{pid}/serials
Every active, non-deleted serial for the product, allocated or not, ordered by serial number. Each record carries the allocation status the inventory product view shows.
Responses
200— The product's serials. Failures (invalid key, unknown/other-department product, serial not on this product, validation) are also returned with HTTP 200 and `success: false` + `description`.
Register a serial number
POST /{api_key}/products/{pid}/serials
Adds an unallocated serial to the product. `serialnumber` is required and must be unique across every product. Only `serialnumber`, `model` and `poid` are accepted; any other field is rejected.
Request body
| Field | Type | Description |
|---|---|---|
serialnumber | string | Required on create; unique across all products |
model | string | |
poid | integer | Purchase order id; blank or 0 for none |
Responses
200— The created serial. Failures (invalid key, unknown/other-department product, serial not on this product, validation) are also returned with HTTP 200 and `success: false` + `description`.
Delete a serial number
DELETE /{api_key}/products/{pid}/serials/{snid}
Soft-deletes the serial. An allocated serial is rejected until it is unallocated.
Responses
200— The serial was deleted. Failures (invalid key, unknown/other-department product, serial not on this product, validation) are also returned with HTTP 200 and `success: false` + `description`.
Update a serial number
POST /{api_key}/products/{pid}/serials/{snid}
Changes the supplied fields; omitted fields keep their value. Only `serialnumber`, `model` and `poid` are accepted. Allocation is not editable here; use the allocate/unallocate endpoints.
Request body
| Field | Type | Description |
|---|---|---|
serialnumber | string | Required on create; unique across all products |
model | string | |
poid | integer | Purchase order id; blank or 0 for none |
Responses
200— The updated serial. Failures (invalid key, unknown/other-department product, serial not on this product, validation) are also returned with HTTP 200 and `success: false` + `description`.
Allocate a serial number
POST /{api_key}/products/{pid}/serials/{snid}/allocate
Allocates the serial to an invoice (`iid` > 0) or an order (`iid` = minus the order id). The invoice or order must exist in the key's department; otherwise the validation reason is returned (e.g. `Invoice #123 does not exist.`). An already-allocated serial is re-allocated.
Request body
| Field | Type | Description |
|---|---|---|
iid required | integer | Invoice id, or minus the order id. 0 is rejected; use unallocate. |
Responses
200— The allocated serial. Failures (invalid key, unknown/other-department product, serial not on this product, validation) are also returned with HTTP 200 and `success: false` + `description`.
Unallocate a serial number
POST /{api_key}/products/{pid}/serials/{snid}/unallocate
Returns the serial to stock (`iid` = 0). No request body.
Responses
200— The unallocated serial. Failures (invalid key, unknown/other-department product, serial not on this product, validation) are also returned with HTTP 200 and `success: false` + `description`.
Set the physical quantity of a non-serial product at a location (the new total, not a delta) through `moveInventoryItem()`. Serialised products are rejected - receive, transfer or remove units individually - as is a location holding negative (oversold) stock for the product.
POST /{api_key}/stock/adjust
Request body
| Field | Type | Description |
|---|---|---|
pid required | integer | Non-serial product ID |
location_id required | integer | Stock location ID (not 0 / -1000) |
quantity required | integer | New physical quantity at the location (0 to 1000000) |
Responses
200— Stock adjusted (`stock adjusted`, or `stock unchanged` when the quantity already matches). Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
List the live stock locations in the API key's department, ordered by name.
GET /{api_key}/stock/locations
Responses
200— Locations retrieved. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Create a stock location. Setting `primary_sales_location` clears the flag on the department's other locations.
POST /{api_key}/stock/locations/add
Request body
| Field | Type | Description |
|---|---|---|
name | string | Required on create; cannot be blank |
description | string | |
parent_location | integer | Parent location ID in the same department, or 0 for none |
primary_sales_location | boolean | Boolean-like (`true`/`false`, `1`/`0`) |
Responses
200— Location created (`stock location created`). Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Partially update a stock location: only the fields sent change. The reserved locations 0 (Out) and -1000 (Overshipped) cannot be modified; the primary sales flag cannot be cleared directly (promote another location instead); a location cannot become its own ancestor.
POST /{api_key}/stock/locations/update/{id}
Request body
| Field | Type | Description |
|---|---|---|
name | string | Required on create; cannot be blank |
description | string | |
parent_location | integer | Parent location ID in the same department, or 0 for none |
primary_sales_location | boolean | Boolean-like (`true`/`false`, `1`/`0`) |
Responses
200— Location updated (`stock location updated`). Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Soft-delete a stock location. Refused while the location holds stock, has live child locations or is the primary sales location; the reserved locations 0 and -1000 cannot be deleted.
DELETE /{api_key}/stock/locations/{id}
Responses
200— Location deleted (`stock location deleted`). Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Get stock quantity on hand for a product, grouped by stock location.
GET /{api_key}/stock/quantity/{pid}
Responses
200— Stock quantities retrieved. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Receive stock into a location through `moveInventoryItem()` (records movement history and updates the on-hand total). Non-serial stock merges into the existing item at the location. Serialised products are received one unit per request with a unique `serial`; the duplicate check and the receipt are atomic (a concurrent receipt of the same serial waits, or is refused with `success: false` if it cannot get the serial's lock in time).
POST /{api_key}/stock/receive
Request body
| Field | Type | Description |
|---|---|---|
pid required | integer | Product ID |
location_id required | integer | Stock location ID (not 0 / -1000) |
quantity required | integer | Positive whole number (max 1000000); must be 1 for serialised products |
serial | string | Required for serialised products, rejected otherwise. Must not exist on a live stock item; letters, digits, spaces and `. _ : / # + -` only. |
Responses
200— Stock received (`stock received`). Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Move stock between two different locations in the department through `moveInventoryItem()`. Total on-hand stock is unchanged. Rejected without any write when the source holds too little stock; reserved locations 0/-1000 cannot be used.
POST /{api_key}/stock/transfer
Request body
| Field | Type | Description |
|---|---|---|
pid required | integer | Product ID |
from_location_id required | integer | Source stock location ID (not 0 / -1000) |
to_location_id required | integer | Destination stock location ID (not 0 / -1000, not the source) |
quantity required | integer | Positive whole number |
serials | array of string | Serialised products only: exactly `quantity` serials at the source to move. Omit to move the oldest units. |
Responses
200— Stock transferred (`stock transferred`). Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.
Remove stock (move out) for a product from a location. Uses HTTP DELETE with an optional JSON body (raw JSON, or the legacy `data=` form). The product and location must belong to the API key's department.
DELETE /{api_key}/stock/{pid}
Request body
| Field | Type | Description |
|---|---|---|
location | integer | Stock location ID to remove stock from. Defaults to the primary sales location. |
quantity | integer | Quantity to remove. When omitted/0 a single unit is removed. Serialised products remove that many serials. |
Responses
200— Stock removed. Failures (invalid key, validation, not found) are also returned with HTTP 200 and `success: false` + `description`.