API reference
The workspace HTTP API served by ai.thundera.eu.
Conventions
- Base URL:
https://ai.thundera.eu. JSON in and out unless noted. - Auth is a Thundera session: the
thundera_token/thundera_refreshcookies, orAuthorization: Bearer <access token>. Other cookies are ignored. Refreshed cookies from the auth service are passed through inSet-Cookie. - Errors are
{"error": "message"}with the status codes listed per endpoint. - Request bodies are limited to 64 KiB (
413).
| Method | Path | Auth |
|---|---|---|
| GET | /health | none |
| GET | /metrics | none |
| GET | /api/plans | none |
| GET | /api/models | none |
| GET | /api/session | cookie / bearer |
| GET POST | /api/conversations | required |
| GET | /api/conversations/:id | required |
| POST | /api/conversations/:id/messages | required |
| POST | /api/chat/stream | required |
| POST | /api/chat | required |
GET /health
Liveness and readiness. 200 {"status":"ok","service":"thundera-ai-web"}.
GET /metrics
Prometheus text format: thundera_ai_http_requests_total.
GET /api/plans
A cached (60 s) proxy of the central catalogue for app=ai, plus billingUrl. If billing is unreachable the last good response is served, or an empty list.
{ "plans": [ { "id": "ai-pro", "app_slug": "ai", "tier": "pro", "billing_period": "monthly",
"name": "AI Pro", "unit_amount_cents": 999, "currency": "USD", "features": [], "limits": {} } ],
"billingUrl": "https://auth.thundera.eu/billing" }
GET /api/models
Alias to send as modelId in chat requests:
{ "models": [ { "id": "smart", "name": "Smart", "provider": "thundera", "modelId": "smart",
"description": "Balanced quality for writing and analysis.", "available": true } ] }
GET /api/session
Always 200; 503 if the auth service is unavailable.
{ "logged": true, "user": { "id": "…", "name": "…", "email": "…" } }
{ "logged": false, "user": null }
GET, POST /api/conversations
GET returns your conversations, most recent first. POST body {"title"?: string ≤ 200} creates one and returns 201 with the conversation. Errors: 400 invalid title, 401, 503 storage unavailable.
GET /api/conversations/:id
The conversation with its messages[] (id, role, content, status, modelId, createdAt; status is partial, final, cancelled or error). Errors: 400 invalid id, 404 not yours or missing.
POST …/:id/messages
POST /api/conversations/:id/messages
Body {"content": string, "modelId"?: string} appends a user message; 201 with the message. 400 if content is empty, 404 if the conversation is not yours.
POST /api/chat/stream
Streams an answer as Server-Sent Events.
{ "message": "Explain CRDTs briefly", "modelId": "smart", "conversationId": "<uuid>", "disclosureAccepted": true }
message and disclosureAccepted: true are required; modelId defaults to smart and must be an alias from /api/models; conversationId is optional but, when given, must be one of yours — the exchange is saved to it. Events:
| Event | Data |
|---|---|
conversation | {conversationId, messageId} — sent first when the conversation is persisted |
delta | {text} — next chunk of the answer |
done | {conversationId, messageId} — finished successfully |
error | {error, conversationId} — failed; no done follows |
Errors before the stream starts: 400 (disclosure, message, model or conversation id), 401, 404 (conversation), 429 (20/min), 503 (auth, gateway or storage). Budget and rate failures inside a stream arrive as an error event with a readable message.
POST /api/chat
Same body, non-streaming. 200 {"provider":"thundera-models","modelId","message"}. Failures: 402 allowance used up, 429, 503.