Metadata-Version: 2.5
Name: sadakio
Version: 0.1.0
Summary: Sadakio API client — read a business's own guests, visits and retention
Project-URL: Homepage, https://sadakio.com/gelistirici
Project-URL: Examples, https://github.com/lio-maker/sadakio-examples
License-Expression: MIT
License-File: LICENSE
Keywords: api,cafe,loyalty,retention,sadakio,sdk
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Description-Content-Type: text/markdown

# sadakio

Python client for the [Sadakio](https://sadakio.com) Public API. It reads
**one business's own** guests, visits and retention numbers.

Sadakio is the operating layer for small hospitality businesses in Türkiye:
guest base, loyalty programme, return visits, QR menu, Apple Wallet cards. If
you are building for cafés, salons or shops — your own product, a dashboard for
a client, or an integration with a POS you already sell — this is the read side
of that data without you having to build and run a loyalty engine.

Read-only today, because the Public API v1 is read-only.

```bash
pip install sadakio
```

The same name works in both ecosystems: `npm install sadakio` and
`pip install sadakio`.

## Get a key

A business owner creates the key in the Sadakio panel under **Ayarlar → API**.
The raw key is shown once, at creation; only its fingerprint is stored.

Each key belongs to exactly one business and carries the `read` scope. It can
never name another business's row — a foreign id answers `404`, not `403`, so
the API cannot be used to discover what exists elsewhere.

## Use it

```python
import os
from sadakio import Sadakio

sadakio = Sadakio(api_key=os.environ["SADAKIO_API_KEY"])

# one page
page = sadakio.list_guests(limit=50)
print(page["data"], page["next_cursor"])

# or every guest, without writing the pagination yourself
for guest in sadakio.iter_guests(updated_since=last_sync):
    print(guest["name"], guest["visits_count"], guest["masked_phone"])

# returned-guest numbers for the last month
summary = sadakio.retention(period="month")["data"]
```

There is an async client with the same surface:

```python
from sadakio import AsyncSadakio

async with AsyncSadakio(api_key=key) as sadakio:
    async for guest in sadakio.iter_guests():
        ...
```

| Call | What it answers |
|---|---|
| `list_guests(**params)` / `iter_guests(**params)` | The guest base. `updated_since` matches guests who visited **or** were created since that moment, so an incremental sync never misses a brand-new guest. |
| `get_guest(id)` | One guest with their loyalty cards and balances. |
| `list_visits(**params)` / `iter_visits(**params)` | The earn-event feed: stamps, points, cashback, redeems. |
| `retention(**params)` | Returned-guest numbers over a window. |

## Options

```python
Sadakio(
    api_key=...,                               # required
    base_url="https://api.sadakio.com/api/v1", # default
    timeout_s=30.0,                            # default
    max_retries=2,                             # 429 and 5xx only
)
```

Retries happen only where waiting can actually help — a rate limit or a server
error — and wait exactly as long as `Retry-After` says. A `404` is never
retried, because waiting cannot make it true.

## Four things worth knowing before you trust a number

**Phones are always masked.** Last four digits only, and there is no unmasked
path. This is a KVKK decision, not a scope you can request your way past.

**Pagination is by cursor.** `iter_guests()` and `iter_visits()` walk it for you and stops on a null
cursor — not on an empty page, which is the usual way a hand-written sync
truncates itself.

**A reversed earn keeps its row**, with `reversed_at` set. History is never
deleted, so exclude reversed rows yourself when you count.

**No money figure is invented.** `retention()` returns
`estimated_returned_value` only when you supply a real `avg_ticket`.

## Errors

Every failure is a `SadakioError` carrying `status`, `code` and a message that
includes the one step that fixes it. `err.is_retryable` tells you whether trying
again could plausibly work.

```python
from sadakio import SadakioError

try:
    sadakio.get_guest(guest_id)
except SadakioError as err:
    if err.code == "not_found":
        ...
```

## Contract

The machine-readable contract is served publicly, without a key, at
`https://api.sadakio.com/api/v1/openapi.yaml`. Response fields may be **added**
over time, never renamed or removed — so parse leniently and a release will not
break you.

Working with an AI assistant? There is an MCP server too: `uvx sadakio-mcp`.

Examples you can read and run: <https://github.com/lio-maker/sadakio-examples>

Docs: <https://sadakio.com/gelistirici> · Questions: biz@sadakio.com

Python 3.10 and up. MIT licensed. Built by the Sadakio team, and pull requests are welcome.
