Till King Gateway API

Multi-tenant retail POS gateway — Phase 1 endpoints. All API routes (except device registration) require a JWT Bearer token in the Authorization header.

Onboarding a New Mobile Device

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

Step 1 Register your device and get a JWT

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.

Step 2 Log in a cashier (proxy to ERPNext)

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"}] }

Step 3 Pick a branch (if multiple returned)

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"}, ...] }

Step 4 Load products

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 }

Step 5 Submit a sale

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": [...] }

Full flow (copy-paste) Run this script to test the full onboarding + sale
#!/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.

Device Authentication

POST /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 }

POS Operations

POST /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 }

Offline Sync

POST /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 }

GET /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 Proxy

ANY /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"]

Error Codes

CodeMeaning
401Missing or invalid JWT token
403Device blocked / Tenant suspended
404Tenant not found / Batch not found
422Validation failed — check the errors array in the response
429Rate limit exceeded — see Retry-After header
502ERPNext unreachable — the tenant’s ERPNext instance is down or the URL is wrong

Till King Gateway · ERP Champions Ltd · Kampala, Uganda