Skip to content

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.

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.

GET /v1/shop, /v1/company-payment-type reference data, once
│
GET /v1/client?phone_number=… find the customer
POST /v1/client create if not found
│
POST /v2/order {shop_id, cashbox_id} create the sale ──> order_id
POST /v2/order-product/:order_id add a product (one per request)
PUT /v2/order-customer-new/:order_id attach the customer
POST /v2/order-manual-discount/:order_id discount
POST /v1/recalculate-order-bill/:order_id verify the total
POST /v2/order-payment/:order_id complete the sale
│
GET /v2/order/:id details of the completed sale

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

Окно терминала
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.

Окно терминала
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.

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.

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.

Окно терминала
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.

Окно терминала
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.

Окно терминала
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.

Окно терминала
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.

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.

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'

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.

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

The sale UUID (id from the response envelope at step 2). order_number is intended for correspondence with support.

  1. 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.
  2. Before creating a sale, check whether a record for this order already exists.
  3. The external system’s order number is passed in comment at payment: a duplicate is visible in the BILLZ interface and in reports.

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.

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