# Conventies

JSON-conventies, centen, datums, fouten, rate limits en idempotency. Lees dit één keer en de rest is voorspelbaar.

## Basis

- Base URL: `https://api.cijfr.com`, alle paden beginnen met `/api/v1`.
- Request- en response-bodies zijn JSON. Stuur `Content-Type: application/json` bij een body.
- Gelukt: `{ "data": … }`. Mislukt: `{ "error": "code" }` met een passende HTTP-status.
- Datums zijn `YYYY-MM-DD`. Tijdstippen zijn ISO 8601 in UTC.
- Bedragen zijn hele centen: `total_incl: 12100` is €121,00. Geen kommagetallen.
- Btw per regel is een `vatTreatment`, bij producten een tarief in basispunten (`vatRateBps`, 2100 = 21%).

## Lijsten en limieten

Lijst-endpoints geven de nieuwste records terug met een vaste limiet: facturen 50, klanten, producten, abonnementen en checkouts 100. Er zijn nog geen paginatie- of filterparameters.

De API is bewust klein: lezen en aanmaken. Wijzigen, verwijderen en creditnota's doe je in de app. Een vergrendelde factuur is ook voor de API onveranderbaar.

## Rate limit

Per sleutel gelden 60 verzoeken per minuut. Daarover antwoordt de API `429` met `rate_limited` en een `Retry-After`-header in seconden.

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 23

{"error":"rate_limited"}
```

## Idempotency

Elke `POST` accepteert een `Idempotency-Key`-header (maximaal 200 tekens). De API bewaart het antwoord per sleutel. Hetzelfde verzoek met dezelfde key speelt het bewaarde antwoord opnieuw af, zonder dubbel aan te maken. Dezelfde key met een ándere body is een `409 idempotency_conflict`.

Loopt het eerste verzoek nog, dan krijgt een herhaling binnen twee minuten `409 idempotency_in_flight`. Gebruik keys die uniek zijn per logische operatie, bijvoorbeeld je eigen order-id.

## Fouten

| Status | `error` | Betekenis |
| --- | --- | --- |
| 400 | `validation` | Body klopt niet met het schema, of een id bestaat niet in je organisatie |
| 401 | `unauthorized` | Sleutel ontbreekt, is onbekend, ingetrokken of verlopen |
| 402 | `plan_limit_api` | Je pakket heeft geen API-toegang (Studio nodig) |
| 402 | `billing_readonly` | Je abonnement blokkeert versturen; aanmaken als concept kan nog |
| 403 | `forbidden` | Scope ontbreekt, profiel komt niet overeen, of platform-only |
| 409 | `idempotency_conflict` | Zelfde Idempotency-Key met een andere body |
| 409 | `idempotency_in_flight` | Zelfde key loopt nog, probeer het zo meteen opnieuw |
| 429 | `rate_limited` | Meer dan 60 verzoeken per minuut, zie `Retry-After` |
| 500 | `generic` | Fout aan de kant van Cijfr |
| 501 | `unavailable` | De API is tijdelijk niet beschikbaar |

## Versies en status

De huidige versie is `v1`. Wijzigingen binnen `v1` zijn backwards compatible: er kunnen velden bijkomen, bestaande velden verdwijnen niet.

`GET https://api.cijfr.com/api/health` is onbeveiligd en toont of de app en Supabase Auth gezond zijn. Handig voor een uptime-check.

