Platform API reference
Base URL: https://api.sadakio.com/platform/v1
Auth: Authorization: Bearer pk_YOUR_KEY — keys come from self-serve signup, scopes are read and write.
Machine contract: 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"}}andfixtells you what to DO. - Every write accepts an
Idempotency-Key(header or body). A repeated request with the same key returns the original result withreplayed: 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"
{"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).
{"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}'
{"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 [email protected] to raise it.
This page as .md: /en/docs/platform.md