# 03 — API Contract

- Base URL: `/api/v1`
- Auth
  - Tenant apps: Sanctum personal access tokens (PATs) issued via `/api/v1/tenant-auth/login` + abilities.
  - Super Admin: session-authenticated Filament (web only).
- Common
  - Pagination: `page`, `per_page` (max 100).
- Errors: `{ error: { code, title, message, meta? } }` across public + tenant APIs.
  - Rate limits: per-tenant and per-device for tenant apps; 429 with `x-rate-limit-*` headers.

Key Endpoints (abridged)
- Auth: `/api/v1/tenant-auth/login`, `/tenant-auth/exchange`, `/tenant-auth/logout`, `/tenant-auth/me`.
- Tenants (Super Admin only): list/read; no create via API for MVP.
- Events (tenant): CRUD, publish, archive; unique by `tenant_id + slug`.
- Photos (tenant): signed upload URL, create, list, moderate, feature.
- Emotions & Tasks: list, tenant overrides; task library scoping.
- Purchases & Ledger: create purchase intent, webhook ingest, ledger list.
- Settings: read/update tenant theme, limits, legal page links.
- Notifications: `GET /api/v1/tenant/notifications/logs` (filterable by `type` / `status`) exposes recent package/limit alerts with timestamps and retry context.

Guest Polling (no WebSockets in v1)
- GET `/events/{token}/stats` — lightweight counters for Home info bar.
  - Response: `{ online_guests: number, tasks_solved: number, latest_photo_at: ISO8601 }`.
  - Cache: `Cache-Control: no-store`; include `ETag` for conditional requests.
- GET `/events/{token}/photos?since=<ISO8601|cursor>` — incremental gallery refresh.
  - Response: `{ data: Photo[], next_cursor?: string, latest_photo_at: ISO8601 }`.
  - Use `If-None-Match` or `If-Modified-Since` to return `304 Not Modified` when unchanged.
- Legacy slug-based guest endpoints have been removed; tokens are mandatory for public access.

Webhooks
- Payment provider events, media pipeline status, and deletion callbacks. All signed with shared secret per provider.
- RevenueCat webhook: `POST /api/v1/webhooks/revenuecat` signed via `X-Signature` (HMAC SHA1/256). Dispatches `ProcessRevenueCatWebhook` to credit tenants and sync subscription expiry.

### Error Responses

- Every non-2xx response returns a JSON body with the `error` envelope:

```json
{
  "error": {
    "code": "photo_limit_exceeded",
    "title": "Upload Limit Reached",
    "message": "Es wurden 120 von 120 Fotos hochgeladen. Bitte kontaktiere das Team für ein Upgrade.",
    "meta": {
      "limit": 120,
      "used": 120,
      "remaining": 0
    }
  }
}
```

- `code` is stable for clients; `title` is a short human-friendly label; `message` is localized; `meta` may contain structured data (e.g. `trace_id`, quota counts) when relevant.
- Guests and tenant admins consume the same structure to surface tailored UI (toast banners, upload dialogs, etc.).

Public Gallery
- `GET /gallery/{token}`: returns event snapshot + branding colors; responds with `410` once the package gallery window expires.
- `GET /gallery/{token}/photos?cursor=&limit=`: cursor-based pagination of approved photos. Response shape `{ data: Photo[], next_cursor: string|null }`.
- `GET /gallery/{token}/photos/{photo}/download`: streams or redirects to an approved original. Returns `404` if the asset is gone.

Tenant Admin Downloads
- `GET /tenant/events/{event}/photos/archive`: authenticated ZIP export of all approved photos for an event. Returns `404` when none exist.
