iotoms Docs

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:

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, totalAmount and currencyCode on every line. Only itemId is checked for. If you leave out another field, the line is not rejected: iotoms uses a quantity of 1, amounts of 0 and the currency USD.
  • quantity must be greater than 0, and unitPrice, discountAmount and taxAmount must not be negative. Otherwise the call returns 400.
  • discountAmount and taxAmount are for the whole line, not per unit.
  • You set the prices. The cart uses the unitPrice you send and does not look up the item's selling price.
  • uomId is 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, taxAmountCents and totalAmountCents. 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, taxTotal and grandTotal: the totals iotoms worked out. Each is rounded to 2 decimals.
TotalHow iotoms works it out
subtotalsum of unitPrice × quantity
discountTotalsum of discountAmount
taxTotalsum of taxAmount
grandTotalsubtotal − 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, taxTotal and grandTotal must match the cart's totals from Step 1, within 0.01. If any differs, the call returns 400 with TRANSACTION_TOTALS_MISMATCH. If you send the …Cents versions, they must match exactly.
  • clientTransactionId is your own unique ID for the sale. If a request times out, send it again with the same clientTransactionId. 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 with TRANSACTION_ALREADY_EXISTS.
  • channel is one of POS, LINE_DISTRIBUTION, ECOM or OTHER. businessDate is the trading day the sale belongs to. Reports group sales by this date.
  • registerId decides which stockroom the stock comes out of.
  • soldBy is 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, quantityReturned and quantityReturnable.
  • payments[]: the mode and amount of each payment.
  • returnSummary: returnCount, totalReturnedAmount and a status of NONE, OPEN, PARTIAL or SETTLED.

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>'
  • data is a plain list, newest first, with no paging totals. Increase page until a page comes back with fewer than size rows. size can be up to 100.
  • Send both businessDateFrom and businessDateTo for a range, or businessDate for one day. Dates use YYYY-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 by registerId in your own code.
  • Rows in the list carry header fields only. Call Step 3 for the lines of a sale.

Endpoint: List transactions

What's next