Joe Archondis

July 3, 2026 · 8 min read

Business AI Automation

How to Connect a POS System to an AI Agent

How to Connect a POS System to an AI Agent

The Shawar'Mama owner was mid-investor meeting when someone asked what last week's best-selling item was. Before I built this system, that question meant opening Zelty on his phone, navigating to the right report, and waiting for the data to load. He'd usually say he'd follow up later. Not ideal when you're trying to close.

After the integration, he typed the question into Telegram. Eleven seconds later: "Shawarma Classic — 312 units across 6 locations. Up 14% from the previous week. République led with 89 units." He didn't leave the conversation. The investor got the answer in real time.

Here's exactly how that works: the architecture, the Python code, and the decisions that matter before you build a restaurant POS AI integration of your own.

Why POS Data Is Hard to Query in Real Time

Most POS systems were built for reports, not for external API consumers. You log in, select a date range, pick a view, wait. Even the better systems with REST APIs give you flat structured data: order records, item quantities, timestamps, location codes. None of that is queryable in natural language.

Zelty is one of the better options in the French restaurant market. It has a real REST API, API key authentication, and documentation that's actually maintained. But "accessible via API" and "answerable by an AI agent" are not the same thing. The gap between them is the actual engineering.

To answer "how did Saturday compare to last week?" the agent needs to resolve the time references to real dates, identify which API calls to make, pull and aggregate data across multiple locations, then format a human-readable response. Each step has failure modes. Getting all of them reliable and fast enough to feel instant — that's the work.

How the System Is Structured

Three components. A typed Python wrapper around the Zelty REST API. A Claude-powered orchestration layer with tool use, where each tool maps to a category of business question. A Telegram interface where the owner types questions and reads answers.

The data flow for a single question:

The owner already had Telegram open for the review management bot I built first. No new app. No login. Same conversation thread, a different type of question. That familiarity matters more than you'd think — adoption was immediate because there was nothing new to learn.

Building the POS API Client

Zelty uses Bearer token authentication. Each request includes the API key in the Authorization header. A session-based client handles this once rather than per-request:

from dataclasses import dataclass
import requests
from datetime import date

@dataclass
class SaleRecord:
    order_id: str
    location_id: str
    timestamp: str
    total: float
    items: list[dict]

    @classmethod
    def from_zelty(cls, raw: dict) -> "SaleRecord":
        return cls(
            order_id=raw["id"],
            location_id=raw["restaurant_id"],
            timestamp=raw["created_at"],
            total=float(raw["total_price"]),
            items=raw.get("order_lines", [])
        )

class ZeltyClient:
    BASE_URL = "https://api.zelty.fr/2.7"

    def __init__(self, api_key: str):
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        })

    def get_sales(
        self,
        location_id: str,
        start_date: date,
        end_date: date
    ) -> list[SaleRecord]:
        response = self.session.get(
            f"{self.BASE_URL}/orders",
            params={
                "restaurant_id": location_id,
                "from": start_date.isoformat(),
                "to": end_date.isoformat(),
                "status": "closed"
            }
        )
        response.raise_for_status()
        return [SaleRecord.from_zelty(r) for r in response.json()["orders"]]

Two things you'll hit early. First: Zelty uses internal UUIDs for products. "Shawarma Classic" in the owner's UI is an opaque ID in the API response. You need a product ID mapping table synced nightly from Zelty's catalog endpoint and stored in PostgreSQL. Without it, the agent answers with UUIDs the owner can't read.

Second: rate limits. The first version pulled data across all 6 locations in parallel. Zelty throttled it immediately. Adding a small delay between location requests — the same pattern I used for GMB polling in the review system — fixed it cleanly. Six locations, 3-second stagger, no rate limit errors since.

Giving Claude Access to Your Data

The agent has three tools. Each maps to a category of question the owner actually asks:

TOOLS = [
    {
        "name": "get_daily_summary",
        "description": "Get total revenue, covers, and average ticket for a specific day across one or all locations",
        "input_schema": {
            "type": "object",
            "properties": {
                "date": {
                    "type": "string",
                    "description": "ISO date string, e.g. 2026-07-02"
                },
                "location": {
                    "type": "string",
                    "description": "Location name or 'all' for all locations"
                }
            },
            "required": ["date"]
        }
    },
    {
        "name": "get_top_items",
        "description": "Get best-selling items by quantity for a given period and location",
        "input_schema": {
            "type": "object",
            "properties": {
                "period": {
                    "type": "string",
                    "description": "today, yesterday, last_saturday, this_week, or last_week"
                },
                "location": {"type": "string", "description": "Location name or 'all'"},
                "limit": {"type": "integer", "description": "Items to return, default 5"}
            },
            "required": ["period"]
        }
    },
    {
        "name": "compare_periods",
        "description": "Compare revenue, covers, or average ticket between two time periods",
        "input_schema": {
            "type": "object",
            "properties": {
                "period_a": {"type": "string"},
                "period_b": {"type": "string"},
                "metric": {
                    "type": "string",
                    "description": "revenue, covers, or avg_ticket"
                },
                "location": {"type": "string"}
            },
            "required": ["period_a", "period_b", "metric"]
        }
    }
]

Tool description quality directly determines Claude's decision quality. "Get best-selling items by quantity for a given period and location" tells the model exactly when to call this. A vague description like "get item data" leads to wrong tool selection, unnecessary clarifying questions, or the agent returning a hallucinated answer instead of executing a tool at all.

When Claude picks the wrong tool, the response contains wrong or missing data. The owner notices. Trust erodes faster than it builds. Getting descriptions precise upfront prevents most production failures — it's the cheapest fix you can make.

Resolving "Last Saturday" Without Breaking

"Last Saturday" is not an ISO date string. "This week" depends on today's date. The agent needs to resolve these before hitting the Zelty API — and doing this inside Claude is a mistake.

from datetime import date, timedelta

def resolve_period(period: str, today: date) -> tuple[date, date]:
    if period == "today":
        return today, today
    elif period == "yesterday":
        d = today - timedelta(days=1)
        return d, d
    elif period == "last_saturday":
        days_back = (today.weekday() - 5) % 7
        if days_back == 0:
            days_back = 7
        last_sat = today - timedelta(days=days_back)
        return last_sat, last_sat
    elif period == "last_week":
        monday = today - timedelta(days=today.weekday())
        return monday - timedelta(days=7), monday - timedelta(days=1)
    elif period == "this_week":
        monday = today - timedelta(days=today.weekday())
        return monday, today
    else:
        raise ValueError(f"Unknown period: {period}")

Handle this in Python, not in LLM reasoning. Timezone handling, week boundary definitions, "last month vs. past 30 days" ambiguity — all of these are deterministic problems. Give them deterministic solutions. Relying on Claude to do date arithmetic introduces a failure mode that only surfaces in edge cases, usually in production.

For genuinely ambiguous inputs, the tool returns a clarifying prompt rather than guessing. If the owner asks "how did we do this weekend?" on a Tuesday, the system replies: "Did you mean last weekend (Friday to Sunday) or the current week so far?" One wrong-data response erodes more trust than ten non-answers. Asking is the right call.

Which POS Systems Can You Connect?

The architecture works with any POS that has a REST API. Zelty is one of the more developer-friendly options in the French market. Here's how the common systems compare:

POS System API Access Key Consideration
Zelty REST, API key Good docs, rate limits apply, French market focus
Square REST, OAuth 2.0 Strong developer tooling, US-focused, good sandbox
Toast REST, partner program API access requires partner approval, approval takes weeks
Lightspeed REST, OAuth 2.0 Good for retail + hospitality, solid API coverage
Deliverect REST, webhooks Aggregates delivery platforms; useful for delivery-heavy ops
Legacy systems No API Database access or scheduled CSV export as fallback

For systems without APIs — which covers more legacy POS installs than you'd expect — the fallback is scheduled export files. A CSV or PDF exported on a schedule, parsed by Python, loaded into PostgreSQL. Less real-time than an API, but functional. If you're building for a client on a legacy system, ask whether the POS vendor offers any kind of export automation before writing a scraper.

Frequently Asked Questions

What POS systems support AI agent integration?

Any POS with a REST API can be connected to an AI agent. Square, Toast, Lightspeed, Zelty, and Deliverect all have documented APIs. Legacy systems without APIs require database access or scheduled export workarounds — workable, but more fragile than a proper API integration. Check whether your POS vendor offers a developer program before planning the architecture.

How long does it take to build a restaurant POS AI integration?

Expect 2 to 4 weeks for a production-ready system: one week for the POS API client and data normalization, one week for the AI agent tools and Claude integration, and one week for the Telegram interface and edge case handling. The product ID mapping table and the period resolution logic are where most of the unexpected time goes. The morning digest scheduler adds a day or two on top.

Does this require Claude specifically, or can you use another model?

Any model with tool use support works: Claude, GPT-4o, Gemini. Claude's tool use API is well-documented and behaves predictably, which is why it's my default. For a POS agent where most requests are simple queries — revenue for a date, top items for a period — Claude Haiku or Gemini Flash handle the load at low cost. The heavy model isn't needed here.

What can a POS AI agent realistically answer?

Revenue, covers, average ticket, item-level sales, location comparisons, period-over-period trends. Anything the POS API exposes as structured data. What it can't do: predict future performance, explain why a trend happened, or access data from systems the POS isn't connected to. Define the scope clearly with the client before you build — this sets accurate expectations and simplifies the tool definitions.

What is the most common failure mode in a POS AI integration?

Ambiguous time references. When an owner asks "how did we do this weekend?" on a Tuesday, the system has to decide between last weekend and the current partial week. Guessing wrong and returning incorrect data destroys trust faster than not answering at all. The fix is simple: return a clarifying question for genuinely ambiguous periods rather than picking one. One wrong answer undoes a week of good responses.

Working on something similar?

I build AI agents and low-latency systems. If you're trying to solve a version of this, let's talk.

Get in touch

Author: Joe Archondis — AI systems engineer and HFT infrastructure builder.

Last updated: 2026-07-03