Requests & Responses¶
The API is consistent across resources: JSON bodies, the same pagination on every list, predictable status codes, and one error shape. Learn it once and every endpoint reads the same.
Content type¶
Send and receive JSON. For requests with a body (POST, PUT), set:
Content-Type: application/json
Responses are always JSON except 204 No Content, which has an empty body.
Pagination¶
Every list endpoint (/users, /groups, /nas) takes the same three query parameters:
| Parameter | Default | Range | Purpose |
|---|---|---|---|
page |
1 |
≥ 1 | Which page to return |
size |
50 |
1–500 | Items per page |
search |
— | Case-insensitive substring filter (username / group name / NAS name) |
curl -H "X-API-Key: $KEY" \
"https://your-host/api/v1/users?search=acme&page=2&size=100"
Lists come back in a page envelope — total is the count across all pages, so you can tell
when to stop:
{
"total": 128,
"page": 2,
"size": 100,
"items": [ "…" ]
}
To page through everything, request page=1,2,3… until page * size >= total.
A few endpoints aren't paginated
GET /sessions, GET /users/{username}/sessions, and GET /auth-logs return a capped list
(with a count) rather than a page envelope — they're bounded by a limit instead. Each is noted
on its own page.
Status codes¶
| Code | When |
|---|---|
200 OK |
Successful read or update |
201 Created |
A POST created a resource (the new object is returned) |
204 No Content |
A DELETE (or member add/remove) succeeded — empty body |
400 Bad Request |
A value was invalid (e.g. an unknown password_type) |
401 Unauthorized |
Missing, invalid, revoked, or expired key |
403 Forbidden |
Valid key, but it lacks the endpoint's scope |
404 Not Found |
The user / group / NAS doesn't exist |
409 Conflict |
It already exists (duplicate username, group, NAS, or membership) |
422 Unprocessable Entity |
The JSON body failed schema validation |
Errors¶
Errors raised by the API carry a single string in detail:
{ "detail": "User 'alice' already exists" }
Schema-validation failures (422) use FastAPI's structured form, listing each bad field:
{
"detail": [
{ "loc": ["body", "username"], "msg": "field required", "type": "value_error.missing" }
]
}
Always branch on the HTTP status code; treat detail as a human-readable message, not a stable
machine key.
Dates & formats¶
- User expiration (
expiration) is a FreeRADIUS-style string:"31 Dec 2026 23:59:59". On update,nullleaves it unchanged,""clears it, and a string sets it. - Key expiry (
expires_at, on API-key creation) is an ISO-8601 timestamp:"2026-12-31T23:59:59Z". - Timestamps in responses (session times, auth dates) are ISO-8601.
Side effects¶
Some writes do more than touch the database. In particular, creating, updating, or deleting a NAS restarts FreeRADIUS in the background so the change takes effect. It's noted on the endpoints that do it — see NAS.
Related¶
- Authentication & Scopes — auth headers and scope errors
- Users · Groups · NAS
MonsterOps