Build on the Frontelio API.
A REST API over JSON. Bearer JWT for user-facing operations; per-tenant API keys for machine-to-machine. Rate-limited, versioned, and stable.
The Frontelio API is a conventional REST + JSON surface over the same domain as the web and mobile apps. Anything you can do in the UI you can do from the API — there is no separate admin-only or legacy surface. Resources are tenant-scoped: every authenticated request resolves a single tenant, and the platform never returns rows from other tenants.
We version the platform as a whole, not per-endpoint. Breaking changes are announced in the "What's new" section on the docs home at least one minor version before they land. Day-to-day API additions (new endpoints, new optional fields) are non-breaking and ship continuously.
Base URL
For all examples in these docs:
https://api.frontelio.com/api/v1Self-hosted installations will have their own base URL — but every example path below (e.g. /auth/login, /access/verify) is the suffix appended to your base.
Authentication
Every endpoint (with a handful of explicitly-public exceptions listed below) requires authentication. There are two authentication schemes:
- Bearer JWT (user-facing). The primary scheme. Obtain by calling
POST /auth/loginwith email + password. Token expires after 15 minutes; the mobile and web clients automatically refresh viaPOST /auth/refreshon a 401. Pass asAuthorization: Bearer <accessToken>. - Per-tenant API key (machine-to-machine). Specifically for reader bridges and integrations. Mint in
/admin/access → API keys. Format:mk_+ 32 random bytes. Pass asAuthorization: Bearer <apiKey>— same header, different secret class. The API distinguishes them by prefix.
Public endpoints (no auth required, but rate-limited per IP): POST /auth/login, POST /auth/signup, POST /auth/refresh, POST /auth/invite/<code>/redeem, POST /access/visitors/<inviteCode>/redeem, POST /access/webhooks/<integrationId>.
Rate limits
We rate-limit per IP using @nestjs/throttler. The default limit is 120 requests per minute per IP, which is comfortable for normal app usage. A few hot endpoints have stricter caps:
| Endpoint | Limit | Window | Why |
|---|---|---|---|
| POST /auth/login | 10 | 60s | Brute-force defence |
| POST /auth/signup | 5 | 60s | Anti-spam new-tenant signups |
| POST /auth/refresh | 30 | 60s | Replay protection |
| POST /access/visitors/:c/redeem | 5 | 5 min | Magic-link guessing defence |
| All other endpoints | 120 | 60s | Per-IP default |
A throttled response is HTTP 429 Too Many Requests. If you need a higher limit for legitimate machine-to-machine traffic (a busy reader bridge, a high-volume webhook receiver), email support@frontelio.comwith your tenant ID and we'll raise it.
Error shape
Every 4xx / 5xx response follows the NestJS standard:
{
"statusCode": 400,
"message": "outletId must be a valid UUID",
"error": "Bad Request"
}message is either a string (single error) or an array of strings (validation errors from class-validator). Always check the statusCodefield — it's the canonical signal. Common statuses:
400— your request body or query is malformed. Body ofmessagetells you which field.401— no token, expired token, or token signed by the wrong key (e.g. across tenant boundaries). Refresh and retry.403— authenticated but not authorized for this resource or action. Check the user's role in/users.404— resource doesn't exist in your tenant. We deliberately return 404 (not 403) on cross-tenant lookups so attackers can't enumerate IDs across tenants.409— write would violate a uniqueness constraint (duplicate email, duplicate outlet name within a company, etc.).429— rate-limited (see above).500— server error. We've logged it; if you can repro, email us with the request ID from the response headers.
Pagination
Collection endpoints accept ?page and ?limit query params. Default limit=50; max limit=200. Response shape:
{
"data": [ ... 50 user rows ... ],
"meta": {
"total": 137,
"page": 1,
"limit": 50,
"pages": 3
}
}For large exports use ?limit=200 and paginate, or use the dedicated /admin/export/* endpoints that stream NDJSON without pagination overhead.