API reference
Every client — the four websites and the three Flutter apps — talks to one REST API, versioned under /api/v1. The contract is an OpenAPI 3.1 document generated from the controllers themselves.
Interactive
Swagger UI
https://api.helsen-med.com/docs — browse and try every operation. Available when the deployment enables it (SWAGGER_ENABLED, on by default).
In the repository
docs/api/openapi.json
The committed contract. CI fails if it differs from a fresh generation, so it is never stale on main.
In the repository
docs/endpoints.md
A generated table of every endpoint, who may call it and its rate limit.
How the contract is kept honest
- The OpenAPI document is generated with
pnpm --filter @mp/api gen:openapi; SDKs and the endpoint table withpnpm gen:sdk. - Each operation carries two extensions read from the same decorators the guards enforce:
x-access(public,authenticated, or a list of roles) andx-rate-limit. - The TypeScript client
@mp/sdkis generated from the document and used by every web surface. The Dart client (packages_dart/mp_api) is hand-written on purpose; only its error catalog and operation list are generated, and a drift test checks every path it calls exists.
Conventions every endpoint follows
| Topic | Rule |
|---|---|
| Base path | /api/v1; health at /health (liveness) and /ready (readiness, checks the database). |
| Authentication | Bearer JWT access token (15 minutes) plus a rotating refresh token. |
| Money | Integer minor units with an ISO currency code. On the wire the amount is a string, never a JSON number. |
| IDs | UUID v7 (time-sortable). Orders and products also have a short human reference. |
| Idempotency | Every write that touches money or orders requires an Idempotency-Key header; a retry replays the stored response rather than acting twice. |
| Pagination | Cursor-based on every list endpoint, never offsets. |
| Errors | Every error carries a stable code from the catalog in packages/contracts/src/errors.ts, e.g. order.ILLEGAL_TRANSITION. |
| Tenancy | A seller only ever sees its own rows. Reading another seller's resource returns common.NOT_FOUND — never 403, which would confirm the id exists. |
| Rate limit | A global per-IP limit (120 requests per minute unless configured), with stricter limits on sensitive routes such as sign-in and OTP. |
| Timestamps | UTC, ISO 8601. |
Endpoint groups
The generated table lists roughly 400 operations. They group by audience:
- Public and buyer: auth, catalog, search, cart, checkout, orders, returns, reviews, questions, addresses, notifications, conversations, privacy.
- Seller: seller-onboarding, seller-orders, seller-shipments, seller-returns, seller-earnings, seller-invoices, seller-staff, seller-performance, inventory, media.
- Delivery: dispatch, driver, driver-onboarding, delivery-zones.
- Staff: the
admin-*groups (catalog, orders, disputes, chargebacks, commission plans, seller and driver onboarding, users, audit, feature flags, content, support) and finance.