Online order from an external system
Task: an online store or a marketplace places an order on its side and must post it in BILLZ as a sale of a specific shop — with the buyer, a discount and a payment.
Terminology fork
Section titled “Terminology fork”The word “order” is already taken in BILLZ.
| Task wording | BILLZ term | Methods |
|---|---|---|
| A buyer ordered a product on the website | Sale (Order, sales receipt) |
/v2/order, /v2/order-product, /v2/order-payment |
| A product is ordered from a supplier | Purchase order (SupplierOrder) |
/v2/supplier-order, /v2/supplier-order/add-item |
An online order needs the first row.
Scenario diagram
Section titled “Scenario diagram”GET /v1/shop, /v1/company-payment-type reference data, once │GET /v1/client?phone_number=… find the customerPOST /v1/client create if not found │POST /v2/order {shop_id, cashbox_id} create the sale ──> order_idPOST /v2/order-product/:order_id add a product (one per request)PUT /v2/order-customer-new/:order_id attach the customerPOST /v2/order-manual-discount/:order_id discountPOST /v1/recalculate-order-bill/:order_id verify the totalPOST /v2/order-payment/:order_id complete the sale │GET /v2/order/:id details of the completed saleCreating a sale and the methods that change the receipt contents are asynchronous: the
Billz-Response-Channel: HTTP header is required, otherwise an event identifier is returned instead of the result.
Recalculation and payment are synchronous and do not need the header.
1. Customer
Section titled “1. Customer”Search
Section titled “Search”curl -X GET 'https://api-admin.billz.ai/v1/client?phone_number=%2B998900000000&limit=10&page=1' \ -H 'Authorization: Bearer TOKEN'Parameters: phone_number, search, chat_id, limit, page. The response is {count, clients[]}.
The detailed card for a found ID is GET /v1/customer/:id (it has a different, wider
set of fields than an element of clients[]).
search matches a substring in the first name, last name, middle name, phone numbers, external_id, chat_id,
date of birth, loyalty card code and name. Several words separated by spaces are combined with AND.
Special characters are stripped: +998901234567 and 998901234567 are equivalent.
phone_number matches a substring, chat_id — an exact value.
Creation
Section titled “Creation”curl -X POST 'https://api-admin.billz.ai/v1/client' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "first_name": "John", "last_name": "Smith", "phone_number": "+998900000000", "gender": 1, "date_of_birth": "1990-05-14" }'The response is { "id": "..." }: the customer UUID, which is stored in the external system.
gender: 1 — male, 2 — female, other values are saved as empty.
2. Create the sale in the right shop
Section titled “2. Create the sale in the right shop”The sale is created by the asynchronous method POST /v2/order. The response channel is set by the
Billz-Response-Channel: HTTP header or by the query parameter of the same name, as in the example below.
curl -X POST 'https://api-admin.billz.ai/v2/order?Billz-Response-Channel=HTTP' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "shop_id": "c1056fcc-539b-47ea-8bc4-d9e337c433f0", "cashbox_id": "9b4a7f8c-a8ef-4870-8a1b-89869fdd4141" }'{ "session_id": "fd8d352a-6299-4489-8a6e-a5c037f3f65b", "status_code": 200, "id": "38c3920a-ffdd-4233-a823-8af3266f5f4f", "error": { "code": "", "message": "" }, "data": { "id": "38c3920a-ffdd-4233-a823-8af3266f5f4f", "order_number": "5632631379", "order_type": "SALE" }, "correlation_id": "3674f28e-7b96-4683-87c3-80892698f421"}The sale UUID is the id field (the same value as in data.id); it is passed to all subsequent
steps. order_number is the short sale number that the cashier sees in the interface.
The shop is selected by the shop_id field, the cash register by the cashbox_id field. The list of shops is
GET /v1/shop.
3. Add products
Section titled “3. Add products”One product item per request.
curl -X POST 'https://api-admin.billz.ai/v2/order-product/SALE_ID' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -H 'Billz-Response-Channel: HTTP' \ -d '{ "product_id": "2ce40c63-4527-4898-a975-7c30c255fd50", "sold_measurement_value": 2, "used_wholesale_price": false }'This is an upsert with an absolute quantity: a repeated call with the same product_id
replaces the quantity. sold_measurement_value: 0 removes the line item from the receipt.
4. Attach the customer
Section titled “4. Attach the customer”curl -X PUT 'https://api-admin.billz.ai/v2/order-customer-new/SALE_ID' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -H 'Billz-Response-Channel: HTTP' \ -d '{ "customer_id": "CUSTOMER_ID", "check_auth_code": false }'The response comes in an envelope: the object is in the data field.
5. Discount
Section titled “5. Discount”curl -X POST 'https://api-admin.billz.ai/v2/order-manual-discount/SALE_ID' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -H 'Billz-Response-Channel: HTTP' \ -d '{ "discount_unit": "PERCENTAGE", "discount_value": 10 }'discount_unit is PERCENTAGE or CURRENCY. The optional product_id applies the discount
to a single line item; without it, the discount applies to the whole cart.
A manual discount restarts the cart recalculation: call the payment after the asynchronous response to the discount.
6. Verify the total
Section titled “6. Verify the total”curl -X POST 'https://api-admin.billz.ai/v1/recalculate-order-bill/SALE_ID' \ -H 'Authorization: Bearer TOKEN'The method is synchronous and returns the current receipt with line items, discounts and the total.
For a held sale the method returns an error.
7. Complete the sale
Section titled “7. Complete the sale”curl -X POST 'https://api-admin.billz.ai/v2/order-payment/SALE_ID' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "payments": [ { "company_payment_type_id": "c6e84d9b-...", "paid_amount": 900000, "returned_amount": 0 } ], "comment": "Order #12345 from the website", "without_cashback": false }'| Field | Meaning |
|---|---|
payments[] |
list of payments; several elements — payment with different methods |
payments[].company_payment_type_id |
payment type from GET /v1/company-payment-type |
payments[].paid_amount |
amount paid |
payments[].returned_amount |
change |
payments[].gift_card_id |
integer; required for payment with a gift card |
with_cashback |
amount (not a flag) redeemed from the customer’s bonus balance |
without_cashback |
true — do not accrue cashback for this sale |
sold_at |
date and time of the sale in the YYYY-MM-DD HH:MM:SS format — for backdated posting |
comment |
order number of the external system; an empty string does not overwrite the previous one |
skip_ofd |
skips the fiscal data operator (OFD) checks before payment, see below |
The method is synchronous. The response is {"order_type": "SALE", "should_print_cheque": true}
with status 200.
Payment error codes
Section titled “Payment error codes”An error comes with HTTP 400, and the reason code is inside data:
{ "status_code": 400, "error": { "code": "", "message": "status: 400 - errors: Bad request - causes: ..." }, "data": { "error_code": 20084, "message": "payment is possible only for calculated order" }}The reason is determined by data.error_code: the error.code field in this response is empty.
| Code | Message | Meaning |
|---|---|---|
20009 |
cashbox shop id and order shop id are different | the user’s current cash register belongs to another shop |
20010 |
user’s current cashbox id is empty | the sale’s shop has no open cash register |
20011 |
cashbox has not enough money | the cash register does not have enough money for the return |
20015 |
order does not have items | the receipt has no line items |
20016 |
not enough product | not enough stock; the available quantity is in data.data |
20017 |
order is deleted | the sale has been deleted |
20020 |
wrong payment amount | the payment amounts do not add up; data contains total_price and claimed_amount |
20047 |
not enough funds in customer balance | not enough bonus points for with_cashback |
20063 |
order not sent to epos | the receipt has not been sent to the OFD (bypassed with the skip_ofd flag) |
20082 |
cannot make payment with closed cashbox | the cash register is closed |
20084 |
payment is possible only for calculated order | the cart has not been recalculated, see step 3 |
claimed_amount is the sum of paid_amount minus returned_amount over all elements,
plus with_cashback, plus the debt; it is compared with the receipt total.
8. Sale ID and details
Section titled “8. Sale ID and details”The ID is returned at step 2. Full details of the completed sale — line items, payments, discounts, customer:
curl -X GET 'https://api-admin.billz.ai/v2/order/SALE_ID' \ -H 'Authorization: Bearer TOKEN'Answers to frequent questions
Section titled “Answers to frequent questions”Which field selects the shop
Section titled “Which field selects the shop”The shop_id field in the body of POST /v2/order; the cash register is set there too — cashbox_id.
The list of shops is GET /v1/shop.
Can a sale be made when stock is insufficient
Section titled “Can a sale be made when stock is insufficient”This is controlled by the product setting “allow negative stock”. If it is disabled, adding a product beyond the stock fails; if enabled, the stock goes negative.
The error is HTTP 400, data.error_code = 20016, data.message = not enough product;
the available quantity is returned in data.data.
A preliminary stock check is GET /v2/products →
shop_measurement_values[].active_measurement_value for the required shop_id. Between the check
and the payment the product may be sold offline: a refusal at payment is handled as well.
In what form is the invoice returned
Section titled “In what form is the invoice returned”The public API does not return a ready-made invoice or a printable form for a sale.
| What is needed | What covers it |
|---|---|
| Own printable form | GET /v2/order/:id — line items, prices, discounts, payments, shop, customer; layout on the external system’s side |
| Export for accounting | reports /v1/general-report-table, /v1/transaction-report-table |
Which identifier to store
Section titled “Which identifier to store”The sale UUID (id from the response envelope at step 2). order_number is intended
for correspondence with support.
- The mapping “external system order number → BILLZ sale UUID” is stored on the external system’s side; the UUID is saved right after step 2, before adding products.
- Before creating a sale, check whether a record for this order already exists.
- The external system’s order number is passed in
commentat payment: a duplicate is visible in the BILLZ interface and in reports.
Permissions
Section titled “Permissions”A breakdown of 403 is in troubleshooting, the full list
of what is available to the key is in the reference.
Issuing a key opens the basic set of methods; sensitive methods are opened separately, by configuring the key’s permissions.
Method summary
Section titled “Method summary”| Step | Method | Type |
|---|---|---|
| List of shops | GET /v1/shop |
sync |
| Payment types | GET /v1/company-payment-type |
sync |
| Find customer | GET /v1/client |
sync |
| Customer card | GET /v1/customer/:id |
sync |
| Create customer | POST /v1/client |
sync |
| Update customer | PUT /v1/client/:id |
sync |
| Create sale | POST /v2/order |
async |
| Add product | POST /v2/order-product/:order_id |
async |
| Attach customer | PUT /v2/order-customer-new/:order_id |
async |
| Discount | POST /v2/order-manual-discount/:order_id |
async |
| Verify total | POST /v1/recalculate-order-bill/:order_id |
sync |
| Complete sale | POST /v2/order-payment/:order_id |
sync |
| Sale details | GET /v2/order/:id |
sync |