Record a sale
Build a cart with the items sold, turn it into a transaction on a register, then read the sale back and list a day's sales.
A sale in iotoms takes two calls. First you build a cart with the lines sold, and iotoms works out its totals. Then you create a transaction from that cart. The transaction is the completed sale: it counts in reports, and it takes the goods out of the register's stockroom.
At the end you will have a recorded sale with a transaction ID, its lines and payment, and a way to list every sale for a date range.
Before you start you need:
- a token and the merchant's tenant code. See Authentication.
- a store ID and register ID. See Set up a location.
- items in the catalog. See Add products to the catalog.
- optionally, a customer number. See Sync customers.
Step 1 — Build the cart
The cart holds the lines sold. iotoms adds up its totals from those lines, and the transaction must match them in Step 2.
curl -X POST 'https://beta.iotoms.com/api/v1/transaction/cart' \
-H 'X-TenantID: <tenant-code>' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"registerId": 11,
"customerNumber": "CUS-000-2026-00042",
"source": "partner-pos",
"items": [
{
"itemId": "SKU-10042",
"description": "Sparkling Water 500 ml",
"quantity": 6,
"unitPrice": 1.20,
"discountAmount": 0.20,
"taxAmount": 0.35,
"totalAmount": 7.35,
"currencyCode": "AED"
}
]
}'- Send
itemId,quantity,unitPrice,discountAmount,taxAmount,totalAmountandcurrencyCodeon every line. OnlyitemIdis checked for. If you leave out another field, the line is not rejected: iotoms uses a quantity of 1, amounts of 0 and the currencyUSD. quantitymust be greater than 0, andunitPrice,discountAmountandtaxAmountmust not be negative. Otherwise the call returns 400.discountAmountandtaxAmountare for the whole line, not per unit.- You set the prices. The cart uses the
unitPriceyou send and does not look up the item's selling price. uomIdis optional. It names the unit the line is sold in. Leave it out to sell in the item's default selling unit.- If you work in minor units, you can also send
unitPriceCents,discountAmountCents,taxAmountCentsandtotalAmountCents. When both forms are sent, the minor-unit value wins. See money formats.
The response (201) is the cart itself, with no envelope. Read:
cartId: the cart's ID. iotoms makes one when you do not send your own.subtotal,discountTotal,taxTotalandgrandTotal: the totals iotoms worked out. Each is rounded to 2 decimals.
| Total | How iotoms works it out |
|---|---|
subtotal | sum of unitPrice × quantity |
discountTotal | sum of discountAmount |
taxTotal | sum of taxAmount |
grandTotal | subtotal − discountTotal + taxTotal |
In the example these are 7.20, 0.20, 0.35 and 7.35. Any totals you send on the cart itself are replaced by these.
Endpoint: Create a cart
Step 2 — Create the transaction
The transaction turns the cart into a completed sale.
curl -X POST 'https://beta.iotoms.com/api/v1/transaction/transactions' \
-H 'X-TenantID: <tenant-code>' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"cartId": "b41d7e2a-93c5-4f18-a0d6-5e2c8b1f7a39",
"clientTransactionId": "pos-till1-2026-10-18-000187",
"channel": "POS",
"contextType": "POS_ORDER",
"businessDate": "2026-10-18",
"storeId": 7,
"storeName": "Main counter",
"registerId": 11,
"customerNumber": "CUS-000-2026-00042",
"invoiceNumber": "INV-000187",
"subtotal": 7.20,
"discountTotal": 0.20,
"taxTotal": 0.35,
"grandTotal": 7.35,
"paymentStatus": "Paid",
"paymentMode": "CASH",
"cashCollected": 7.35
}'subtotal,discountTotal,taxTotalandgrandTotalmust match the cart's totals from Step 1, within 0.01. If any differs, the call returns 400 withTRANSACTION_TOTALS_MISMATCH. If you send the…Centsversions, they must match exactly.clientTransactionIdis your own unique ID for the sale. If a request times out, send it again with the sameclientTransactionId. You get back the transaction already recorded, not a second sale.- A cart can become only one transaction. Another transaction for the same cart, under a
different
clientTransactionId, returns 400 withTRANSACTION_ALREADY_EXISTS. channelis one ofPOS,LINE_DISTRIBUTION,ECOMorOTHER.businessDateis the trading day the sale belongs to. Reports group sales by this date.registerIddecides which stockroom the stock comes out of.soldByis optional. When it is left out, the sale is credited to the user behind the token.
The response (201) gives you data.transactionId (a UUID) and the sale's header fields.
This response does not include the lines.
Stock problems don't block the sale
The sale is always recorded. Stock problems are reported to the merchant for follow-up instead. These include a register with no stockroom, or not enough stock. Stock comes out in the item's base unit, converted from the unit on each cart line. Set each register's stockroom so sales reduce the right stock.
Endpoint: Create a transaction from a cart
Step 3 — Read the sale back
Fetch the full transaction to get its lines and payments.
curl 'https://beta.iotoms.com/api/v1/transaction/transactions/2e9f4a1c-5b7d-4c36-8a02-d1f3e6b9c4a7' \
-H 'X-TenantID: <tenant-code>' \
-H 'Authorization: Bearer <token>'From data:
lines[]:itemId,quantitySold,unitPrice,discountAmount,taxAmount,lineTotal,quantityReturnedandquantityReturnable.payments[]: themodeandamountof each payment.returnSummary:returnCount,totalReturnedAmountand astatusofNONE,OPEN,PARTIALorSETTLED.
Endpoint: Get a transaction
Step 4 — List a day's sales
Use this to reconcile with your own records or to export sales.
curl 'https://beta.iotoms.com/api/v1/transaction/transactions?businessDateFrom=2026-10-01&businessDateTo=2026-10-18&page=0&size=100' \
-H 'X-TenantID: <tenant-code>' \
-H 'Authorization: Bearer <token>'datais a plain list, newest first, with no paging totals. Increasepageuntil a page comes back with fewer thansizerows.sizecan be up to 100.- Send both
businessDateFromandbusinessDateTofor a range, orbusinessDatefor one day. Dates useYYYY-MM-DD. - Filters do not combine. If you send
registerId, the date filters are ignored and you get every sale on that register. List by date, then filter byregisterIdin your own code. - Rows in the list carry header fields only. Call Step 3 for the lines of a sale.
Endpoint: List transactions