Skip to main content

Complete examples

Each of these scripts is complete and runs as it stands. Both read every page, retry the errors that are worth retrying and stop with a message on any other.

Total a day's sales (Node.js)​

Adds up one business date from the raw orders and refunds, then compares the result with the daily summary. Needs the read:reports permission.

day.mjs
// Totals one business date from the raw orders and refunds, and checks the
// result against the daily summary. Node.js 18 or later, no dependencies.
//
// SEAMLESS_API_KEY=k_live_... node day.mjs 2026-11-01

const ENDPOINT = "https://api.seamlesspos.com/graphql";
const RETRY = new Set(["RATE_LIMITED", "UNAVAILABLE", "INTERNAL_SERVER_ERROR"]);

// Sends one query. Retries the errors worth retrying, waiting longer each time.
async function seamless(query, variables = {}) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SEAMLESS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query, variables }),
});
const body = await res.json();
if (!body.errors) return body.data;

const { message, extensions } = body.errors[0];
if (!RETRY.has(extensions?.code) || attempt === 4) {
throw new Error(`${extensions?.code}: ${message}`);
}
await new Promise(resolve => setTimeout(resolve, 1000 * 2 ** attempt));
}
}

// Reads every page of `orders` or `refunds`.
async function allPages(field, selection, from, to) {
const query = `query Page($from: DateTime!, $to: DateTime!, $after: String) {
site { ${field}(from: $from, to: $to, first: 200, after: $after) { nodes { ${selection} } nextCursor } }
}`;
const records = [];
let after = null;
do {
const data = await seamless(query, { from, to, after });
records.push(...data.site[field].nodes);
after = data.site[field].nextCursor;
} while (after);
return records;
}

const date = process.argv[2];
if (!/^\d{4}-\d{2}-\d{2}$/.test(date ?? "")) throw new Error("Pass a date, like 2026-11-01");

// A business date does not line up with midnight UTC, so read a day either
// side and keep the records whose businessDate is the one wanted.
const day = 24 * 60 * 60 * 1000;
const from = new Date(Date.parse(`${date}T00:00:00Z`) - day).toISOString();
const to = new Date(Date.parse(`${date}T00:00:00Z`) + 2 * day).toISOString();

const orders = (await allPages("orders", "businessDate total voided", from, to))
.filter(o => o.businessDate === date && !o.voided);
const refunds = (await allPages("refunds", "businessDate amount", from, to))
.filter(r => r.businessDate === date);

const sales = orders.reduce((sum, o) => sum + (o.total ?? 0), 0);
const refunded = refunds.reduce((sum, r) => sum + (r.amount ?? 0), 0);

const { site } = await seamless(
`query Summary($date: Date!) { site { currency dailySummary(from: $date, to: $date) { gross transactions } } }`,
{ date },
);
const summary = site.dailySummary[0];

const money = pence => `${(pence / 100).toFixed(2)} ${site.currency}`;
console.log(`${date}: ${orders.length} orders, ${refunds.length} refunds`);
console.log(`Sales ${money(sales)} less refunds ${money(refunded)} = ${money(sales - refunded)}`);
console.log(`Daily summary gross: ${money(summary?.gross ?? 0)}`);

Output

2026-11-01: 143 orders, 2 refunds
Sales 1867.40 GBP less refunds 24.90 GBP = 1842.50 GBP
Daily summary gross: 1842.50 GBP

Export the product library (Python)​

Writes every product and price to a CSV file. Needs the read:products permission.

products.py
"""Writes the whole product library to products.csv. Python 3.8 or later.

pip install requests
SEAMLESS_API_KEY=k_live_... python products.py
"""
import csv
import os
import time

import requests

ENDPOINT = "https://api.seamlesspos.com/graphql"
RETRY = {"RATE_LIMITED", "UNAVAILABLE", "INTERNAL_SERVER_ERROR"}


def seamless(query, variables=None):
"""Sends one query. Retries the errors worth retrying, waiting longer each time."""
for attempt in range(5):
res = requests.post(
ENDPOINT,
headers={"Authorization": f"Bearer {os.environ['SEAMLESS_API_KEY']}"},
json={"query": query, "variables": variables or {}},
timeout=30,
)
body = res.json()
if not body.get("errors"):
return body["data"]
error = body["errors"][0]
code = error.get("extensions", {}).get("code")
if code not in RETRY or attempt == 4:
raise RuntimeError(f"{code}: {error['message']}")
time.sleep(2 ** attempt)


QUERY = """
query Products($after: String) {
site {
products(first: 200, after: $after) {
nodes {
id
name
category { name }
prices { amount orderMode variant taxRatePercent }
}
nextCursor
}
}
}
"""

products, after = [], None
while True:
page = seamless(QUERY, {"after": after})["site"]["products"]
products.extend(page["nodes"])
after = page["nextCursor"]
if not after:
break

with open("products.csv", "w", newline="") as f:
out = csv.writer(f)
out.writerow(["id", "name", "category", "order mode", "variant", "price", "tax rate %"])
for product in products:
for price in product["prices"]:
out.writerow([
product["id"],
product["name"],
(product["category"] or {}).get("name"),
price["orderMode"],
price["variant"],
None if price["amount"] is None else f"{price['amount'] / 100:.2f}",
price["taxRatePercent"],
])

print(f"Wrote {len(products)} products to products.csv")

Output

Wrote 408 products to products.csv
products.csv
id,name,category,order mode,variant,price,tax rate %
66168c952dacadbfc5e41576,Lemonade,Soft Drinks,Sit In,,3.50,20
66168c952dacadbfc5e41576,Lemonade,Soft Drinks,Take Out,,3.20,20
66336eae98f4389e8af4dcc2,Steak,Hot Food,Sit In,,25.00,20