Multi-tenant retail POS gateway — Phase 1 endpoints. All API routes (except device registration) require a JWT Bearer token in the Authorization header.
Follow these steps to connect a new POS device to the gateway. Use curl or any HTTP client. The gateway is at https://tillking.store. In production, use your tenant subdomain (e.g. shop1.tillking.com).
No auth required. The tenant_subdomain must match a registered tenant on the gateway.
curl -X POST https://tillking.store/gateway/device/register \
-H "Content-Type: application/json" \
-d '{
"device_id": "mobile-pos-001",
"device_name": "Till 1 Front",
"tenant_subdomain": "testshop"
}'201 → { "token": "eyJ...", "expires_at": 1780826754, "tenant_name": "Test Supermarket", "device_id": "mobile-pos-001" }
Store the token. All subsequent requests need it in the Authorization: Bearer <token> header.
Use the JWT from Step 1. Credentials are the user's ERPNext username and password.
curl -X POST https://tillking.store/v1/login \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token-from-step-1>" \
-d '{
"username": "cashier@testshop.com",
"password": "your-password"
}'200 → { "user_id": "cashier@testshop.com", "full_name": "John Okello", "email": "...", "role": "Cashier", "branches": [{"id":"Main Warehouse","name":"Main Store"}] }
The branch determines which warehouse stock is used for sales.
curl https://tillking.store/v1/branches \ -H "Authorization: Bearer <token>"
200 → { "branches": [{"id":"Main Warehouse","name":"Main Store"}, ...] }
Fetch the product catalogue. Cached by the gateway for 5 minutes. Supports ?search=, ?page=, and ?since= for incremental sync.
curl https://tillking.store/v1/products \ -H "Authorization: Bearer <token>" # Or search: curl "https://tillking.store/v1/products?search=posho" \ -H "Authorization: Bearer <token>" # Or single item by barcode: curl https://tillking.store/v1/products/60000001 \ -H "Authorization: Bearer <token>"
200 → { "products": [{ "item_code":"POSHO-5KG", "item_name":"Posho 5kg", "barcode":"60000001", "price":24000, "uom":"Nos" }, ...], "cached": true }
Send the cart to the gateway. It normalises to ERPNext format and forwards to the tenant's ERPNext.
curl -X POST https://tillking.store/v1/sale \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"items": [
{"item_code": "POSHO-5KG", "item_name": "Posho 5kg", "qty": 2, "rate": 24000},
{"item_code": "SUGAR-2KG", "item_name": "Sugar 2kg", "qty": 1, "rate": 10800}
],
"payments": [
{"method": "cash", "amount": 58800}
],
"customer": "Walk-in Customer",
"branch_id": 1,
"till_id": "TILL-01"
}'201 → { "invoice_id": "ACC-SINV-2026-00042", "total": 58800, "items": [...], "payments": [...] }
#!/bin/bash
# Till King — Device Onboarding & First Sale
GATEWAY="https://tillking.store"
SUB="testshop"
# Step 1: Register device
TOKEN=$(curl -s -X POST "$GATEWAY/gateway/device/register" \
-H "Content-Type: application/json" \
-d "{\"device_id\":\"test-$(date +%s)\",\"device_name\":\"Test Till\",\"tenant_subdomain\":\"$SUB\"}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
echo "✓ Device registered"
# Step 2: Login
USER=$(curl -s -X POST "$GATEWAY/v1/login" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"username":"cashier","password":"test"}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('full_name',d.get('message','error')))")
echo "✓ Logged in as: $USER"
# Step 3: Load products
ITEMS=$(curl -s "$GATEWAY/v1/products" \
-H "Authorization: Bearer $TOKEN" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('total',0))")
echo "✓ Products loaded: $ITEMS items"
# Step 4: Submit sale
SALE=$(curl -s -X POST "$GATEWAY/v1/sale" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"items":[{"item_code":"POSHO-5KG","item_name":"Posho 5kg","qty":2,"rate":24000}],"payments":[{"method":"cash","amount":48000}]}')
INVOICE=$(echo "$SALE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('invoice_id',d.get('error','FAILED')))")
echo "✓ Sale complete — Invoice: $INVOICE"Save as onboard.sh, run chmod +x onboard.sh && ./onboard.sh. You'll see each step complete in sequence.
/gateway/device/register
Register a POS device and receive a JWT
Registers (or re-registers) a device with the gateway. No prior auth required. Re-registering the same device_id updates the existing record and issues a fresh token.
{
"device_id": "unique-device-uuid",
"device_name": "Till 1 Front",
"tenant_subdomain": "shop1",
"branch_id": null
}201 → { token, expires_at, tenant_name, device_id }
/v1/sale
Submit a POS sale — JWT required
Normalises the client POS payload to ERPNext Sales Invoice format and forwards to the tenant’s ERPNext instance. Items, payments, and customer are mapped using the tenant’s config rules.
{
"items": [
{ "item_code": "POSHO-5KG", "item_name": "Posho 5kg", "qty": 2, "rate": 24000 }
],
"payments": [
{ "method": "cash", "amount": 30000 },
{ "method": "momo", "amount": 18000, "reference": "MTN-077..." }
],
"customer": "Walk-in Customer",
"branch_id": 1,
"till_id": "TILL-01"
}201 → { invoice_id, total, net_total, items, payments }
/sync/batch
Upload queued offline transactions — JWT required
Accepts up to 500 transactions per batch. Jobs are enqueued to Redis and processed by background workers with exponential backoff on failure.
{
"transactions": [
{ "type": "sale", "payload": { ... } },
{ "type": "return", "payload": { ... } }
]
}202 → { batch_uuid, job_count, enqueued, redis_ok }
/sync/status/{batch_uuid}
Check batch sync status — JWT required
Returns the batch status and per-job statuses. Jobs cycle through pending → retrying → synced (or failed after 5 attempts).
200 → { batch_uuid, status, summary, jobs[] }
/erpnext/{path}
Forward to tenant ERPNext — tenant subdomain required
Proxies any request to the tenant’s ERPNext API. The tenant is resolved from the Host header (subdomain). The gateway attaches the tenant’s API credentials automatically — the client never sees them.
Example: GET testshop.tillking.com/erpnext/Sales%20Invoice?fields=["name"]
| Code | Meaning |
|---|---|
| 401 | Missing or invalid JWT token |
| 403 | Device blocked / Tenant suspended |
| 404 | Tenant not found / Batch not found |
| 422 | Validation failed — check the errors array in the response |
| 429 | Rate limit exceeded — see Retry-After header |
| 502 | ERPNext unreachable — the tenant’s ERPNext instance is down or the URL is wrong |
Till King Gateway · ERP Champions Ltd · Kampala, Uganda