Documentation

Complete guide to using SmartPOS AI point of sale system and API.

Getting Started

Welcome to SmartPOS AI! This guide will help you get your point of sale system up and running quickly.

Setup

SmartPOS AI is a cloud-based solution, so there's no software to install. Simply:

  • Create your account at smartposai.com/register
  • Verify your email address
  • Set up your business profile
  • Add your first products

Configuration

After registration, configure your settings in the Backoffice:

  • Business Details: Name, address, tax information
  • Tax Rates: Configure your regional tax rates
  • Payment Methods: Enable cash, card, and other payment options
  • Receipt Settings: Customize your receipt layout

API Authentication

The SmartPOS API provides read-only access to your products, receipts, and shifts. API access requires the Professional plan or above.

Getting Your API Key

  1. Log in to your Backoffice
  2. Go to Settings → API
  3. Click Generate API Key
  4. Copy the key immediately — it won't be shown in full again

Include your API key in the X-API-Key header with every request:

# Example request curl -X GET "https://yoursubdomain.smartposai.com/api/v1/products" \ -H "X-API-Key: spos_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"

🔐 Keep Your API Key Secure

Never expose your API key in client-side code or public repositories. Use environment variables to store sensitive credentials. You can regenerate your key at any time in Settings → API.

Base URL

All API endpoints use your subdomain as the base URL:

https://{your-subdomain}.smartposai.com/api/v1/

Products API

Retrieve your product catalog and stock levels.

GET /api/v1/products

List all products with optional filtering

Query Parameters

  • store_id — Filter by store (integer)
  • category_id — Filter by category (integer)
  • status — Filter by status: active or inactive (default: all)
  • limit — Results per page (default: 50, max: 200)
  • offset — Pagination offset (default: 0)

Example Response

{ "success": true, "data": [ { "id": 1, "sku": "ESP-001", "name": "Espresso", "price": "2.50", "cost": "0.80", "tax_rate": "20.00", "stock": 150, "category_id": 3, "status": "active", "image_url": null, "created_at": "2025-01-15 10:30:00" } ], "total": 84, "limit": 50, "offset": 0 }
GET /api/v1/products/{id}

Get a single product with per-store stock levels

Example Response

{ "success": true, "data": { "id": 1, "sku": "ESP-001", "name": "Espresso", "price": "2.50", "cost": "0.80", "tax_rate": "20.00", "stock": 150, "category_id": 3, "status": "active", "stores": [ { "store_id": 1, "store_name": "Main Store", "stock": 100 }, { "store_id": 2, "store_name": "Downtown", "stock": 50 } ] } }

Receipts API

Access completed sales receipts and their line items.

GET /api/v1/receipts

List receipts with date range and store filtering

Query Parameters

  • start_date — Start date in YYYY-MM-DD format
  • end_date — End date in YYYY-MM-DD format
  • store_id — Filter by store (integer)
  • limit — Results per page (default: 50, max: 200)
  • offset — Pagination offset (default: 0)

Example Response

{ "success": true, "data": [ { "id": 1042, "order_number": "ORD-20250130-0042", "total": "45.80", "subtotal": "38.17", "tax_total": "7.63", "discount_total": "0.00", "payment_method": "cash", "status": "completed", "customer_name": "John Doe", "created_at": "2025-01-30 14:22:05" } ], "total": 1042, "limit": 50, "offset": 0 }
GET /api/v1/receipts/{id}

Get a single receipt with all line items

Example Response

{ "success": true, "data": { "id": 1042, "order_number": "ORD-20250130-0042", "total": "45.80", "subtotal": "38.17", "tax_total": "7.63", "discount_total": "0.00", "payment_method": "cash", "status": "completed", "customer_name": "John Doe", "created_at": "2025-01-30 14:22:05", "items": [ { "product_name": "Espresso", "quantity": 2, "price": "2.50", "discount": "0.00", "tax": "1.00", "total": "6.00" } ] } }

Shifts API

Access employee shift data including cash tracking and sales totals.

GET /api/v1/shifts

List shifts with filtering by date, store, and status

Query Parameters

  • start_date — Start date in YYYY-MM-DD format
  • end_date — End date in YYYY-MM-DD format
  • store_id — Filter by store (integer)
  • status — Filter by status: open or closed
  • limit — Results per page (default: 50, max: 200)
  • offset — Pagination offset (default: 0)

Example Response

{ "success": true, "data": [ { "id": 87, "user_name": "Ana M.", "store_name": "Main Store", "status": "closed", "opening_cash": "200.00", "closing_cash": "485.30", "total_sales": "312.50", "total_orders": 28, "opened_at": "2025-01-30 08:00:00", "closed_at": "2025-01-30 16:05:00" } ], "total": 87, "limit": 50, "offset": 0 }
GET /api/v1/shifts/{id}

Get shift details including sales breakdown

Example Response

{ "success": true, "data": { "id": 87, "user_name": "Ana M.", "store_name": "Main Store", "status": "closed", "opening_cash": "200.00", "closing_cash": "485.30", "expected_cash": "487.50", "difference": "-2.20", "total_sales": "312.50", "total_orders": 28, "opened_at": "2025-01-30 08:00:00", "closed_at": "2025-01-30 16:05:00", "notes": null } }

Errors & Rate Limits

The API uses standard HTTP status codes. All error responses include a JSON body with a descriptive message.

Status Codes

  • 200 — Success
  • 401 — Invalid or missing API key
  • 403 — Feature not available on your plan
  • 404 — Resource not found
  • 500 — Internal server error

Error Response Format

{ "success": false, "error": "Invalid API key or inactive account." }

Rate Limiting

API requests are limited to 100 requests per minute per API key. If you exceed this limit, you'll receive a 429 status code. Wait a moment before retrying.

Webhooks

Webhooks allow you to receive real-time HTTP notifications when events occur in your SmartPOS AI account. When an event is triggered, we send a POST request with a JSON payload to your registered URL.

Plan Requirement

Webhooks require the Professional plan or above. Free and Starter plans do not have webhook access.

Supported Events

  • order.created — A new receipt/sale has been completed
  • order.deleted — A receipt was voided/deleted
  • product.created — A new product was added
  • product.updated — A product was modified (name, price, stock, etc.)
  • product.deleted — A product was deleted
  • inventory.low_stock — An item in a store reached or fell below its alert level (sent once, with store, stock and level; checked hourly)
  • customer.created — A new customer was added
  • customer.updated — A customer record was modified
  • shift.opened — A new shift was started
  • shift.closed — A shift was ended
  • refund.created — A refund was issued
GET /api/v1/webhooks

List all registered webhooks

Example Response

{ "success": true, "data": [ { "id": 1, "url": "https://example.com/webhook", "events": ["order.created", "product.updated"], "secret": "whsec_65be08d69bc39ccd908708f0309bc20ae86eedf945ed7881", "store_id": null, "max_retries": 3, "is_active": 1, "last_triggered_at": "2026-02-07 14:30:00", "failure_count": 0, "created_at": "2026-01-15 10:00:00" } ] }
POST /api/v1/webhooks

Register a new webhook endpoint

Request Body

{ "url": "https://example.com/webhook", "events": ["order.created", "shift.closed"], "max_retries": 3 }
DELETE /api/v1/webhooks/{id}

Delete a webhook by ID

Webhook Payload Format

When an event occurs, we send a POST request to your URL with this JSON structure (example: order.created; the fields inside data depend on the event):

{ "event": "order.created", "data": { "id": 1042, "receipt_number": "S01-2026-00042", "store_id": 1, "customer_id": null, "subtotal": 48.3, "discount": 2.5, "tax": 6.65, "total": 45.8, "payment_method": "cash", "items_count": 3, "points_earned": 0, "points_used": 0, "tip_amount": 0, "created_at": "2026-02-07T14:30:00+01:00" }, "timestamp": "2026-02-07T14:30:00+01:00", "webhook_id": 1 }

Receipts paid from a table (restaurant mode) send fewer fields in data:

  • closing a table: id, receipt_number, store_id, table_name, order_type (dine_in), subtotal, discount, total, payment_method, items_count, created_at
  • paying one part of a split bill: id, receipt_number, store_id, order_type (dine_in), total, payment_method, is_split, split_number, created_at

Request Headers

Every webhook delivery includes these HTTP headers:

  • Content-Type: application/json
  • User-Agent: SmartPOS-Webhook/1.0
  • X-SmartPOS-Event — The event name (e.g. order.created)
  • X-SmartPOS-Signature — sha256= followed by the HMAC-SHA256 signature of the request body (see below)
  • X-SmartPOS-Delivery — Unique delivery ID (the same for every attempt of one delivery)
  • X-SmartPOS-Version — Payload version (currently 1.0; not sent with the test ping)

Verifying the Signature (Recommended)

Each webhook is signed with HMAC-SHA256 using your webhook secret (it starts with whsec_; you can view it or generate a new one in Backoffice → Settings → API → Webhooks). The X-SmartPOS-Signature header has the form sha256=<hex>. Compute the signature over the raw request body exactly as received (not over re-encoded JSON) to confirm the request really came from SmartPOS AI.

// Node.js example const crypto = require('crypto'); function verifySignature(rawBody, signatureHeader, secret) { const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); const received = Buffer.from(signatureHeader || ''); const wanted = Buffer.from(expected); return received.length === wanted.length && crypto.timingSafeEqual(received, wanted); }

🔐 Always use timing-safe comparison

Use crypto.timingSafeEqual() (Node) or hash_equals() (PHP) when comparing signatures. Plain string comparison is vulnerable to timing attacks.

Retry Policy

If your endpoint does not return a 2xx status code within 10 seconds, the attempt fails. A failed attempt is retried right away after a short pause (0.5 s, then 1 s, 1.5 s…), up to the webhook's max_retries attempts in total (1–5, default 3). A new attempt only starts while less than 9 seconds have passed, so an endpoint that does not answer in time (10 s) gets a single attempt. There are no later, delayed retries.

Every delivery is logged. You can view the log and re-send a failed delivery in Backoffice → Settings → API → Webhooks. All attempts of one delivery carry the same X-SmartPOS-Delivery ID; a manual re-send gets a new one.

POST /api/v1/webhooks/{id}/test

Send a test payload (event test.ping) to your webhook endpoint (useful for verifying your handler before going live)

Webhook Reliability

If your endpoint returns a non-2xx status code, the delivery is marked as failed. After 10 consecutive failures, the webhook is automatically deactivated. You can re-activate it from Settings → API → Webhooks.

Offline Mode

SmartPOS AI works even without an internet connection. Here's how it works:

  • Transactions are stored locally using IndexedDB
  • Data automatically syncs when connection is restored
  • Products and prices are cached for offline access
  • Receipts can be printed locally