Guides

Guide: sync sales from a shop or POS

Send each order to Quilo once, linked to the right client, with a nightly or real-time job.

Approach#

For each order in your shop or point-of-sale system:

  1. Find the client by email (GET /clients?email=) or your customer number (GET /clients?external_code=); create it if missing.
  2. Create the sale with receipt_no = your order number, and Idempotency-Key = order-<number>.
  3. A 409 conflict means the sale is already in Quilo: skip it.

Shopify stores can use the built-in Shopify integration instead (Settings → Integrations & Shopify); use the API for other systems.

Python script (run nightly with cron)#

Python
import os, requests

API = f"{os.environ['QUILO_URL']}/api/v1"
S = requests.Session()
S.headers.update({"Authorization": f"Bearer {os.environ['QUILO_API_KEY']}", "Content-Type": "application/json"})
BRAND = os.environ["QUILO_BRAND_ID"]

def client_for(order):
    r = S.get(f"{API}/clients", params={"email": order["email"], "limit": 1}, timeout=30)
    r.raise_for_status()
    if r.json()["data"]:
        return r.json()["data"][0]["id"]
    r = S.post(f"{API}/clients", json={
        "brand_id": BRAND, "first_name": order["first_name"], "last_name": order["last_name"],
        "email": order["email"], "external_code": order["customer_no"], "tags": ["shop"],
    }, headers={"Idempotency-Key": f"customer-{order['customer_no']}"}, timeout=30)
    r.raise_for_status()
    return r.json()["id"]

def push(order):
    r = S.post(f"{API}/sales", json={
        "brand_id": BRAND, "client_id": client_for(order), "receipt_no": order["number"],
        "purchased_on": order["date"],           # YYYY-MM-DD
        "items": ", ".join(i["title"] for i in order["items"])[:2000],
        "total": round(order["total"], 2), "channel": "website",
    }, headers={"Idempotency-Key": f"order-{order['number']}"}, timeout=30)
    if r.status_code == 409:
        return "already in Quilo"
    r.raise_for_status()
    return r.json()["id"]

for order in load_orders_since_yesterday():   # your shop's export
    print(order["number"], push(order))

Tips#

  • Use one key per system (Shop sync, POS Zurich), so the API keys page shows which system did what.
  • Totals are in the brand currency; convert before sending if your shop sells in several currencies.
  • To correct a sale, PATCH /sales/{id}; to remove it, DELETE /sales/{id} (owners, admins, managers).
  • Use the rate-limit helper for large back-fills.