API & OpenAPI docs
Everything the admin UI does, it does over Taranac’s own REST API — the web
interface is just a client. That API is fully described by an OpenAPI schema
generated from the code itself, and the platform can serve that schema as
interactive documentation: Swagger UI, Redoc and the raw
openapi.json.
That documentation surface is off by default. It is a single environment flag away, and this page explains what you get when you turn it on, why you probably should not leave it on in production, and where to browse it without installing anything.
The schema is never generated and never leaves the box.
no auth anyone who can reach the host can read the full API shape.
/docs, /redoc and /openapi.json return 404. On — they are served, and served without authentication.Browse it on the public demo
Section titled “Browse it on the public demo”The quickest way to see the API is the public demo instance, which runs with the docs surface enabled on purpose:
- demo.taranac.pro/docs — Swagger UI
- demo.taranac.pro/redoc — Redoc, with the tags nested into collapsible domain groups
- demo.taranac.pro/openapi.json — the raw schema, for generating a client
The demo runs the same images as your install, so what you read there is what your instance exposes.
The three routes
Section titled “The three routes”| Route | What it is |
|---|---|
/docs | Swagger UI — the flat, ordered tag list, with an Authorize button and try-it-out request forms |
/redoc | Redoc — the same schema rendered as a reference document, with tags nested into domain groups (Network, AAA · TACACS+, NAC, Configuration Tracker, …) |
/openapi.json | The raw OpenAPI schema, for client generators, Postman/Insomnia imports and API diffing |
Note that all three are mounted at the application root, not under /api/.
The bundled edge (the edge container) already proxies them; a third-party
reverse proxy in front of Taranac that only forwards /api/* will not reach
them.
Turning it on
Section titled “Turning it on”DOCS_ENABLED lives in the .env file in your deployment bundle directory and
defaults to false:
DOCS_ENABLED=false # Swagger/Redoc/openapi.json — keep off in productionSet it to true and recreate the api container so it picks the new value up:
./taranac up -d # recreates containers whose env changedTo turn it back off, set it to false and run ./taranac up -d again. When the
flag is off, FastAPI is built with no documentation URLs at all: the three routes
404, and the schema is never even generated.
The API link in the admin UI
Section titled “The API link in the admin UI”When the flag is on, admins get an API item in the sidebar’s Advanced
section that opens /docs in a new tab. The UI reads the flag from the backend
(it is carried on /auth/me as docs_enabled), so the link can never point at a
disabled endpoint — no flag, no link.
What the schema documents
Section titled “What the schema documents”The schema is not raw FastAPI output — the tag taxonomy, the per-tag descriptions and the Markdown overview at the top are maintained in the product as a first-class surface, and CI fails if a route carries a tag that is not described. The conventions it documents are the same ones the whole API follows:
| Convention | Behaviour |
|---|---|
| Base path | Management endpoints live under /api/v1. What sits outside it: the health probes (/healthz, /ready), the three documentation routes above, and EST enrollment at /.well-known/est/. The deliberately public surfaces (captive-portal flows, CRL and certificate downloads) keep the /api/v1 prefix — they are public by their guard, not by their path. |
| Auth | POST /api/v1/auth/login returns a token; send it as Authorization: Bearer <token>. In Swagger UI, paste it into Authorize. A login that runs into a second factor answers mfa_required with a short-lived mfa_token instead — the session token then comes from POST /api/v1/mfa/verify. |
| Token purpose | Every JWT states what it is for. The Authorization header accepts purpose: session and nothing else unless an endpoint widens that explicitly — the forced-password-change token (password_change) opens POST /api/v1/auth/change-password, and the token redeemed from an e-mailed setup link (mfa_enrollment, 15 minutes) opens the enrollment endpoints. Anything else is a 401. The half-authenticated tokens are never bearer tokens at all: mfa and the portal’s byod_mfa are spent in the body of their own verify call, and the two are not interchangeable. |
| Authorization | Endpoints are gated by RBAC permissions — GET /api/v1/rbac/schema lists them. Admin accounts bypass the gate. |
| Errors | A consistent body { "detail", "code", "field" }, localized from the Accept-Language header (en / ru). |
| Validation | Request bodies are strict: unknown fields are rejected with 422, lengths and ranges are bounded, blank optional text normalizes to null. |
| Timestamps | Stored and returned in UTC (ISO-8601); clients localize. |
| Pagination | List endpoints accept page, per_page, search, sort_by, sort_order and return { items, total, total_by_filter, page, per_page, pages }. |
| Rate limits | Not global. A handful of endpoints an attacker would hammer — login, password reset, POST /api/v1/mfa/verify, the portal’s guest flows — are limited per client IP. Over the limit is 429 with a Retry-After header and the body { "detail", "retry_after" }, which is deliberately not the standard error envelope. /mfa/verify also carries a per-token budget: five wrong codes burn that token’s jti, and the check runs before the provider does, so a burnt token costs no TOTP maths and sends no push. |
| Response headers | Every response carries X-Request-ID (echoed from the request if you send one — quote it in a support ticket) and X-API-Version: 1. A dataset export adds X-Taranac-Export-Rows, plus X-Taranac-Export-Truncated: true when the row ceiling cut it short — check it, or a truncated export looks complete. |
Tags are grouped by domain — Authentication & Access, Identity, Network, AAA · TACACS+, AAA · RADIUS, AAA · Policy & Logs, NAC, NAC · PKI, Configuration Tracker, Monitoring & Ops, System Settings, Reports, System — which is the fastest way to find the endpoints behind a screen you are looking at in the UI.
Callers that hold no bearer token
Section titled “Callers that hold no bearer token”Not everything that talks to this API logs in. These surfaces are described in the same schema, and they are worth knowing before you write a client — or point a scanner at an instance and wonder why something answered:
| Surface | How it authenticates |
|---|---|
GET /.well-known/est/cacerts, GET /.well-known/est/csrattrs | Nothing, per RFC 7030. Like the two below, they answer only while the nac.est_enabled setting is on. |
POST /.well-known/est/simpleenroll | HTTP Basic — verified against the local users table by argon2id hash, so a directory-only account (which has no local hash) is refused — or mutual TLS. |
POST /.well-known/est/simplereenroll | Mutual TLS only. Both enrollment routes answer 202 with Retry-After when the request lands in the approval queue instead of being issued. |
GET /api/v1/public/crl/{ca_id}.crl and .pem | Nothing. This is the CRL distribution point embedded in every certificate the internal CA issues, so it has to answer clients that will never log in. |
GET / POST /api/v1/public/cert-download/{token} | The token in the URL. GET previews and spends nothing; POST consumes it, single-use. |
GET / POST /api/v1/public/est-approval/{token} | The one-time token from the approval e-mail. GET renders the request, POST decides it. |
The captive portal’s guest flow under /api/v1/captive-portal/ | The guest session token; the sponsor’s decision arrives on GET /api/v1/captive-portal/sponsor/callback, rate-limited per IP. The /sessions routes on the same prefix are ordinary RBAC-gated admin endpoints. |
/api/v1/mfa/internal/ | The X-Internal-Key shared secret — these are called by the RADIUS/NAC daemons during a live authentication, never by a browser. |
/api/v1/ncm/collector/ | A one-time enrollment token, then per-request signing by the enrolled collector. |
Mutual TLS reaches the API as headers — X-SSL-Client-Verify, X-SSL-Client-S-DN,
X-SSL-Client-Serial — set by whatever terminates the client-certificate
handshake. They are believed only when the same request carries an
X-Est-Proxy-Key matching the nac.est_mtls_proxy_secret setting, compared in
constant time; with no secret configured, mutual TLS counts as not deployed and
those headers are ignored outright. The bundled frontend vhost blanks all four
on every inbound request, so a client cannot smuggle its own.
Related
Section titled “Related”- CLI & scripts — the
./taranacwrapper and the.envfile this flag lives in - Settings reference — the runtime settings surface, edited in the UI rather than in
.env - RBAC — the permissions that gate every endpoint
- Versioning & releases — what a version covers, and what that means for API stability