Skip to content

Sample Client

A minimal Python client that follows the integration flow and retry rules. It uses requests.

pip install requests
export POSINGEST_API_KEY="<api_key>"
export POSINGEST_BASE_URL=https://func-posingest-dev.azurewebsites.net/pos/v1
import os
import time
import uuid

import requests

API_KEY = os.environ["POSINGEST_API_KEY"]
BASE_URL = os.environ["POSINGEST_BASE_URL"]

MAX_RETRY_SECONDS = 24 * 60 * 60


def send(method, path, body=None):
    """Sends one request, retrying with the SAME body (and therefore the same batch_id)."""
    started = time.time()
    backoff = 60  # seconds: 1, 2, 4, 8... minutes

    while True:
        try:
            resp = requests.request(
                method,
                f"{BASE_URL}{path}",
                json=body,
                headers={"x-functions-key": API_KEY},
                timeout=60,
            )
        except requests.RequestException:
            resp = None  # network error or timeout: retry

        if resp is not None:
            if resp.status_code in (200, 202):
                return resp.json()
            if resp.status_code == 429:
                time.sleep(int(resp.headers.get("Retry-After", "60")))
                continue
            if resp.status_code < 500:
                # 4xx: do not retry. Fix the data and send it with a NEW batch_id.
                raise RuntimeError(f"{resp.status_code} {resp.text}")

        if time.time() - started > MAX_RETRY_SECONDS:
            raise RuntimeError(f"Giving up on {method} {path} after 24 hours")
        time.sleep(backoff)
        backoff *= 2


def push_business_date(store_id, business_date, trade_records, control_totals):
    # 1. Transactions, in batches of up to 500.
    batches = [trade_records[i:i + 500] for i in range(0, len(trade_records), 500)]
    for seq, records in enumerate(batches, start=1):
        body = {
            "batch_id": str(uuid.uuid4()),  # generated once; reused by every retry inside send()
            "store_id": store_id,
            "business_date": business_date,
            "batch_seq": seq,
            "trade_records": records,
        }
        result = send("POST", "/trade-records", body)
        for warning in result.get("warnings", []):
            print("warning:", warning)

    # 2. Daily close, after the last batch.
    send("POST", "/daily-close", {
        "batch_id": str(uuid.uuid4()),
        "store_id": store_id,
        "business_date": business_date,
        "batch_count": len(batches),
        **control_totals,  # trade_record_count, item_row_count, net_sales_amount, ...
    })

    # 3. Reconciliation result, about 30 minutes later.
    time.sleep(30 * 60)
    while True:
        status = send("GET", f"/daily-close/{store_id}/{business_date}")
        if status["status"] != "RECEIVED":
            return status
        time.sleep(15 * 60)


if __name__ == "__main__":
    result = push_business_date(
        store_id="US001",
        business_date="2026-10-14",
        trade_records=[],  # load from your POS
        control_totals={
            "trade_record_count": 0,
            "item_row_count": 0,
            "payment_row_count": 0,
            "net_sales_amount": "0.00",
            "tax_amount": "0.00",
            "tip_amount": "0.00",
            "payment_total_amount": "0.00",
            "currency": "USD",
            "closed_at": "2026-10-15T00:12:00-07:00",
        },
    )
    print(result["status"], result.get("differences"))

Note

In production, persist each batch together with its batch_id before sending, so a restarted process resends the same batch_id instead of creating a new one.