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.