Docs / REST API / Requests & Responses

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 envelopetotal 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, null leaves 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.