# Platform API reference

> The multi-business partner API: create businesses, register guests, write counters, generate QRs and read everything back. Isolated tenants, idempotent writes, budget protection.

Base URL: `https://api.sadakio.com/platform/v1`
Auth: `Authorization: Bearer pk_YOUR_KEY` — keys come from [self-serve signup](https://app.sadakio.com/gelistirici/kayit), scopes are `read` and `write`.
Machine contract: [platform/v1/openapi.yaml](https://api.sadakio.com/platform/v1/openapi.yaml)

Three rules hold on every endpoint:

- A foreign id answers **404**, never 403. The API is not an existence oracle: another partner's business does not exist for you.
- Errors are `{"error": {"code", "message", "fix"}}` and `fix` tells you what to DO.
- Every write accepts an `Idempotency-Key` (header or body). A repeated request with the same key returns the original result with `replayed: true`, nothing is ever processed twice.

## Businesses

### GET /businesses

Everything you have provisioned, newest first.

```
curl https://api.sadakio.com/platform/v1/businesses \
  -H "Authorization: Bearer pk_YOUR_KEY"
```

```json
{"data": [{"id": 42, "name": "Kahve Dünyası", "slug": "kahve-dunyasi",
           "managed": true, "join_url": "https://app.sadakio.com/join/kahve-dunyasi",
           "created_at": "2026-09-22T10:00:00Z"}]}
```

### POST /businesses

Creates a real, isolated tenant. `program` picks the loyalty preset, in `managed` mode the engine decides what an event earns.

```
curl -X POST https://api.sadakio.com/platform/v1/businesses \
  -H "Authorization: Bearer pk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Kahve Dünyası", "sector": "kafe", "program": "damga"}'
```

The response is `201` with the business object.

### GET /businesses/{id}

One business, with its `join_url`. Your guests sign themselves up on that page.

### GET /businesses/{id}/qr.png

The business's guest sign-up QR as a print-ready branded PNG. «A QR generator for your cafés» is literally this call.

```
curl https://api.sadakio.com/platform/v1/businesses/42/qr.png \
  -H "Authorization: Bearer pk_YOUR_KEY" -o cafe-qr.png
```

## Guests

### GET /businesses/{id}/guests

Paginated guest list. `q` filters by name, `cursor` and `limit` follow exactly the Business API contract, phones come back masked (KVKK).

```json
{"data": [{"id": 184, "name": "Elif", "masked_phone": "+•••••••4579",
           "joined_at": "2026-09-16T08:31:00Z"}],
 "next_cursor": 184}
```

### POST /businesses/{id}/guests

Registration by phone. A new guest answers `201`, a second attempt with the same phone answers `200` with `created: false`: a retried signup form tells «already there» from «just created» without guessing.

### DELETE /businesses/{id}/guests/{guest_id}

KVKK erasure. The partner is the data controller for their businesses: when your guest demands deletion you carry it out. The guest is anonymized, counters are zeroed, the audit ledger keeps only ids and numbers.

## Counters and events

### POST /businesses/{id}/guests/{gid}/adjust

Adds `delta` to a named counter. Raw and direct: stamps, points, tokens, whatever you name.

```
curl -X POST .../guests/184/adjust \
  -H "Authorization: Bearer pk_YOUR_KEY" \
  -H "Idempotency-Key: visit-2026-09-22-184" \
  -H "Content-Type: application/json" \
  -d '{"counter": "damga", "delta": 1}'
```

```json
{"data": {"counter": "damga", "value": 5, "entry_id": 901, "replayed": false}}
```

### POST /businesses/{id}/guests/{gid}/events

Managed mode: you say «a visit happened» and the engine decides what it earns from the business's own program. `amount` carries the ticket total, `stamps` the stamp count.

### GET /businesses/{id}/guests/{gid}/counters

Every counter of the guest in one flat object: `{"damga": 5, "puan": 120}`. An unknown guest id answers 404, same as adjust and events.

## Protections

| Protection | Value | On breach |
|---|---|---|
| Rate | 30 requests per second, 50,000 per day | `429` with `Retry-After` |
| Magnitude per write | 1,000 | `422 validation_failed` |
| Daily total magnitude | 100,000 | `429 budget_exceeded`, resets at midnight UTC |

Budgets protect the partner from their own bugs too: code stuck in a loop cannot mint unlimited balance. If a limit is tight for your account, ask [biz@sadakio.com](mailto:biz@sadakio.com) to raise it.
