Omni Way developer docs v1.0.0
Products Β· Sales, Stock & Till

Sales, Stock & Till

The full point-of-sale layer on top of payments: a product catalog, barcode-driven stock control, sales settled by cash or InnBucks, and daily reports β€” plus a bundled Till app at /till that needs zero code. Everything the till does is also plain REST, so any custom frontend can do the same.

The model β€” three objects

01

Product

Name, price, barcode, optional stock tracking. A tracked good (bread), an untracked service (taxi fare), or price-at-sale (price_cents: null).

β†’
02

Stock move

Append-only journal: receive, sale, adjust, count. On-hand is always explainable, like the money ledger.

β†’
03

Sale

A basket of line items + a tender. Cash completes instantly; InnBucks completes when its payment settles. Stock decrements atomically with completion.

β—†
One catalog, two worlds

The catalog and on-hand stock are shared between test and live (the shelf is the shelf), but only live sales change on-hand quantities β€” test-mode sales journal their movements with livemode: false and never bend real inventory. Train staff in TEST without consequences.

Catalog

curl https://omniway.online/v1/products \
  -H "Authorization: Bearer sk_live_..." -H "Content-Type: application/json" \
  -d '{
    "name": "Bread 700g",
    "barcode": "6001234567890",
    "price_cents": 150,
    "track_stock": true,
    "stock_qty": 20,
    "low_stock_at": 5
  }'
  • barcode is unique per merchant β€” the scanner contract. 409 barcode_taken on duplicates.
  • price_cents: null = price decided at sale time (hardware off-cuts, taxi fares, consultations).
  • track_stock: false for services β€” sellable, never counted.
  • Soft-delete with PATCH {"active": false}; history stays intact.

Search/list: GET /v1/products?q=bread, exact scanner lookup: GET /v1/products?barcode=6001234567890.

Barcode scanners (and other capture)

USB and Bluetooth barcode scanners are keyboards β€” they type the code and press Enter. No drivers, no SDK:

scanBox.addEventListener("keydown", async (e) => {
  if (e.key !== "Enter") return;
  const code = scanBox.value.trim(); scanBox.value = "";
  const { data } = await till.get(`/products?barcode=${encodeURIComponent(code)}`);
  data.length ? cart.add(data[0]) : showNoMatch(code);
});

The bundled Till app works exactly this way β€” keep its scan box focused and scan away: scan-to-sell on the Sell screen, scan-to-receive on the Stock screen. The same pattern accepts any keyboard-wedge input device (membrane keypads, QR wands, magstripe readers in keyboard mode).

Stock control

# goods in from a supplier
curl …/v1/stock/receive -d '{"product_id":"…","qty":24,"note":"Delivery β€” Lobels"}'

# damage/shrinkage correction (signed delta)
curl …/v1/stock/adjust -d '{"product_id":"…","qty":-2,"note":"damaged"}'

# stocktake: set the counted absolute quantity
curl …/v1/stock/count -d '{"product_id":"…","qty":18,"note":"monthly count"}'

Every change is a journal row (GET /v1/stock/moves) β€” who/when/why for any discrepancy. GET /v1/stock/report returns on-hand units, valuation at current prices, and the low-stock list (products at or below their low_stock_at).

Sales

curl https://omniway.online/v1/sales \
  -H "Authorization: Bearer sk_live_..." -H "Content-Type: application/json" \
  -H "Idempotency-Key: till3-0611-0142" \
  -d '{
    "tender": "cash",
    "items": [
      {"product_id": "…bread…", "qty": 2},
      {"name": "Delivery", "qty": 1, "unit_cents": 100}
    ],
    "cashier": "Rudo"
  }'
# β†’ status:"completed" immediately; tracked stock decremented

Line items are {product_id, qty} (price from the catalog, overridable with unit_cents) or fully ad-hoc {name, qty, unit_cents}. Items snapshot name and price at sale time β€” later catalog edits never rewrite history.

curl … -d '{"tender": "innbucks", "items": [{"product_id": "…coke…", "qty": 2}]}'
# β†’ status:"pending" + embedded payment { code, qr_png_data_url, deep_link, checkout_url }
# show the code/QR; the sale completes itself when the customer pays
# (poll GET /v1/sales/:id, or act on the sale.completed webhook)
✦
Cash is reporting-only money

Cash sales count in takings and decrement stock, but never touch your payable balance β€” cash in the drawer is already yours. Only InnBucks sales feed the balance you can pay out. The daily report separates the two tenders for drawer reconciliation.

If an InnBucks payment expires unpaid, the sale auto-cancels (status: "cancelled") β€” no stock moves, no money. Ring it up again for another attempt.

Reports

curl ".../v1/sales/report?from=2026-06-11T00:00:00Z" -H "Authorization: Bearer sk_live_..."
# β†’ by_tender: cash vs innbucks count + totals per currency
#   top_products: best sellers by revenue
#   totals: overall count + per-currency takings

Pair with GET /v1/stock/report for the full picture: what sold, what it took, what's left and what it's worth. Money-side reporting (fees, payouts, statement CSV) stays on the ledger.

The bundled Till app

https://omniway.online/till β€” sign in with your dashboard credentials. Three screens, phone-and-tablet friendly, no installation:

ScreenWhat it does
πŸ›’ SellAlways-focused scan box (scanner or type-to-search), quick-tap product tiles, the cart with Β± quantities, then Cash (records instantly) or InnBucks (shows code + QR in a dialog and flips to βœ“ PAID on settlement).
πŸ“¦ StockScan-to-receive, stocktake counts, new-product capture (scan an unknown barcode β†’ β€œ+ New” pre-fills it), low-stock alerts, on-hand valuation.
πŸ“ˆ TodayToday's takings by tender, top products, recent sales β€” the drawer-reconciliation view.

The TEST/LIVE pill in the header switches worlds β€” train new staff in TEST, where sales are simulated and stock is untouched. The till speaks to the same API documented above; if it ever feels limiting, build your own frontend against /v1 and keep the data.

Industry presets β€” same engine, different habits

BusinessHow to set up the catalog
Retail / tuck shopTracked products with barcodes and low-stock alerts; scan-to-sell; cash + InnBucks tenders.
Hardware storeBarcoded lines tracked; cut-to-size and bulk items as price-at-sale products (price_cents: null).
Taxi / transportUntracked β€œFare” products per route (fixed price) or one price-at-sale β€œFare”; InnBucks tender shows the QR to the passenger.
HospitalityMenu items untracked (or track bottled stock only); ad-hoc lines for specials; cashier name per sale for shift accountability.
Services / clinicUntracked service products (consultation, procedure); price overrides per sale where needed.

Omni Way is the financial core; your storefront or till is the surface it powers. Integrate once and the same ledger, payouts and reporting work across every product you adopt.

omniway.online Β· InnBucksPowered by InnBucks (MicroBank Limited) Β· USD & ZWG.