API endpointy¶
Backend FastAPI exposuje REST API přes https://nemoreport-ai-backend-v2.sliplane.app. Tato kapitola dokumentuje všechny endpointy, jejich auth, rate limity a request/response shapes.
Auto-generated OpenAPI docs
<backend>/docs (Swagger UI) — automaticky generované z FastAPI route signatures + Pydantic models. Tato dokumentace popisuje použití + sémantiku.
Auth schémata¶
| Schéma | Použití | Header |
|---|---|---|
| JWT Bearer | User-facing endpointy (frontend) | Authorization: Bearer <supabase_jwt> |
| Admin Hash | /admin/* endpointy |
X-Admin-Hash: <hash> nebo ?hash=<hash> query |
| HMAC | /reports/{id}/attachments/system (Nette) |
X-Nette-Signature: <hmac_sha256> |
| No auth | /health, public docs |
— |
Health check¶
GET /health¶
Sliplane healthcheck endpoint, never auth-required.
User profile¶
GET /me¶
Auth: JWT Rate limit: 60/minute
Vrací profile usera derived z JWT claims + DB lookup.
Response:
{
"id": "uuid",
"email": "user@example.com",
"personal_tenant_id": "uuid",
"active_tenant_id": "uuid",
"display_name": "Jan Novák"
}
Models config¶
GET /models¶
Auth: JWT (volitelně, pro per-tenant override) Rate limit: 60/minute
Vrací dostupné LLM modely + default. Frontend použije pro chat model picker.
Reports (legacy v1 + v2)¶
GET /reports¶
Auth: JWT
Response: list ReportSummary (RLS scoped per tenant)
POST /reports¶
Auth: JWT
Use case: legacy MHTML upload (sync, pre-Phase B). Pro nové uploady použijte POST /ingest (async pipeline).
GET /reports/{report_id}¶
Auth: JWT
Response: ReportDetail s metadata, parsed_markdown, parsed_metadata.
GET /reports/{report_id}/sections¶
Auth: JWT
Response: list SectionInfo. Pro v2 reports vrací parsed_sections rows; pro v1 fallback regex split na clean_text.
GET /reports/{report_id}/sections/{section_name}¶
Auth: JWT Response: full section markdown.
Ingestion (Phase B)¶
POST /ingest¶
Auth: JWT
Rate limit: 20/hour
Body: multipart/form-data
- file: PDF / PNG / JPEG / WEBP / MHTML / HTML / DOCX (max 50 MB)
- title: optional string
Response 202 Accepted:
{
"id": "uuid",
"status": "uploaded",
"filename": "report.pdf",
"size_bytes": 374123,
"content_type": "application/pdf",
"title": "..."
}
Zpracování je async — frontend subscribe na Realtime channel report-{id} pro live status updates.
Errors:
- 400 empty_file
- 413 Request Entity Too Large
- 415 Unsupported Media Type
- 503 mistral_unavailable (pro PDF/image cesta pokud chybí MISTRAL_API_KEY)
GET /ingest/{report_id}/status¶
Auth: JWT Polling fallback pokud Realtime drop.
Response:
{
"id": "uuid",
"status": "embedding",
"ingestion_started_at": "2026-04-30T08:00:00Z",
"ingestion_finished_at": null,
"ingestion_error": null,
"ingestion_cost_cents": 18
}
GET /ingest/{report_id}/sections¶
Auth: JWT
Response: list parsed_sections rows pro daný report (po status='parsed').
GET /ingest/{report_id}/figures¶
Auth: JWT Response: list figures s thumbnails URLs + annotation_json.
[
{
"id": "uuid",
"section_name": "Riziko povodní",
"page_number": 5,
"storage_url": "https://cubdrgjdkatyecrgckwp.supabase.co/storage/v1/object/...",
"annotation_source": "gemini",
"annotation_quality_score": 0.85,
"annotation_json": { "image_type": "map_flood", "summary": "...", "entities": [...], ... }
}
]
DELETE /ingest/{report_id}¶
Auth: JWT (must be tenant member) Cascade: smaže report + všechny attachments + figures + parsed_sections + chunks. Storage objekty zůstávají orphan (admin cleanup TODO).
GET /ingest/{report_id}/attachments¶
Auth: JWT Response: list attachments (Nette + user_upload).
POST /ingest/{report_id}/uploads¶
Auth: JWT
Use case: User přidá další soubor do reportu (folder model)
Body: multipart/form-data (stejné jako POST /ingest)
Effect: vytvoří attachment row s source='user_upload', enqueue run_ingestion('attachment', ...).
DELETE /ingest/{report_id}/attachments/{attachment_id}¶
Auth: JWT
Restriction: user může smazat jen source='user_upload' (Nette systémové = immutable).
Retrieval (Phase C)¶
POST /retrieve¶
Auth: JWT Rate limit: 60/minute
Body:
{
"query": "občanská vybavenost obchody dostupnost",
"scope": {
"type": "folder",
"report_id": "uuid"
},
"top_k": 5
}
scope.type:
- "folder" (Phase C MVP) — single report container, retrieves chunks from main + all attachments
- "section" (plánované) — WHERE section_slug = X
- "multi_report" (plánované) — WHERE report_id IN (...)
Response:
{
"chunks": [
{
"id": "uuid",
"report_id": "uuid",
"attachment_id": null,
"section_id": "uuid",
"figure_id": null,
"table_id": null,
"content_type": "text",
"source_type": "main",
"section_name": "Občanská vybavenost",
"section_slug": "obcanska-vybavenost",
"attachment_filename": null,
"source_label": "Občanská vybavenost",
"content": "Sídla společností v domě...",
"content_tokens": 220,
"order_in_doc": 6,
"embedding_type": "text",
"rrf_score": 0.0327,
"vector_rank": 1,
"vector_dist": 0.34,
"bm25_rank": 1,
"bm25_score": 0.20,
"rerank_score": 0.7151
}
],
"embed_ms": 360,
"retrieval_ms": 58,
"rerank_ms": 667,
"scope_type": "folder",
"top_k": 5,
"fusion": "hybrid_rrf",
"reranked": true,
"used_hyde": false
}
Errors:
- 404 report not in tenant scope
- 501 scope.type not yet implemented (zatím jen folder)
- 503 embedding unavailable
Chat (Phase A.1, Phase D rewrite TBD)¶
POST /chat¶
Auth: JWT Rate limit: 30/minute
Aktuálně používá full report text. Phase D bude rewire na /retrieve chunks.
Body:
{
"report_id": "uuid",
"conversation_id": "uuid",
"message": "Jaká je občanská vybavenost?",
"model_override": null
}
Response: SSE stream (Server-Sent Events) s JSON-encoded chunks (kvůli \n\n v markdown obsahu).
Conversations + feedback (Phase A.1)¶
GET /conversations?report_id=...¶
POST /conversations — create new¶
GET /conversations/{conv_id}/messages¶
PATCH /conversations/{conv_id} — rename¶
DELETE /conversations/{conv_id}¶
POST /feedback — thumbs up/down per message¶
Migration (v1 → v2 testers)¶
GET /migrate/status¶
Auth: JWT (preauth check by email) Vrací claim status pro user (nebyl claim / claim ready / claimed).
POST /migrate/claim¶
Auth: JWT
Rate limit: 5/minute
Importuje v1 reporty do v2 schema (přes nemoreport.import_v1() PL/pgSQL function).
Admin (HASH-gated)¶
GET /admin/verify¶
Vrací { ok: true } pokud hash valid.
Feedback dashboard¶
GET /admin/feedback— list feedback rowsGET /admin/feedback/stats—{total, up, down}per report
LLM providers¶
GET /admin/providers— list per-tenant overridesPUT /admin/providers/{provider_id}— update config
Migration¶
POST /admin/migrate/preauth— preregistrace email → claim readyGET /admin/migrate/stats— claim flow analyticsPOST /admin/migrate/reverse-claim— undo claim
Cost tracking¶
GET /admin/cost/global?days=N— top 10 tenants by cost (Phase B B.13)GET /admin/cost/tenant/{tenant_id}?days=N— per-tenant breakdown + top reports
Embed backfill (Phase C C.13)¶
POST /admin/embed/{target_kind}/{target_id}— enqueue embed_target tasktarget_kind:report|attachment- Idempotent — worker DELETE existing chunks před re-insert
Retrieve diagnostic (Phase C E2E test)¶
POST /admin/retrieve/{report_id}— bypass JWT, full retrieve flow- Body:
{ "query": "...", "top_k": 5 } - Pro testing rerank + diversity bez JWT setup
Nette HMAC integration (Phase B.11, ready pre-Phase E)¶
POST /reports/{report_id}/attachments/system¶
Auth: HMAC nad canonical string <report_id>|<nette_id>|<attachment_type>|<sha256(file_bytes)>
Header: X-Nette-Signature: <hex_hmac_sha256>
Body: multipart/form-data
- file: PDF / image
- nette_id: string (Nette přidělené ID — idempotence)
- attachment_type: string (vyjadreni_cetin, geo_plan, foto, ...)
- note: optional string
Response 201 Created:
Idempotence: stejný (report_id, nette_id) vrací existující attachment místo duplicate insert (UNIQUE partial index).
GET /reports/{report_id}/attachments/{attachment_id}/status¶
Auth: HMAC (stejný canonical string method) Response: status + ingestion_cost_cents pro Nette polling.
OpenAPI spec¶
<backend>/openapi.json (auto-generated). <backend>/docs (Swagger UI), <backend>/redoc (ReDoc UI).
Nett tým může z OpenAPI generovat client SDK.
Common errors¶
| HTTP | Příčina | Akce |
|---|---|---|
| 401 | JWT missing/invalid/expired | Frontend sign-out + re-login |
| 403 | Admin hash invalid nebo HMAC verify fail | Check ENV |
| 404 | Resource not in tenant scope | RLS deny → user nemůže vidět cizí data |
| 413 | File > 50 MB | Chunkovat client-side |
| 415 | Unsupported MIME | Allowlist kontrola |
| 422 | Pydantic validation | Check request body shape |
| 429 | Rate limit | Backoff + retry |
| 500 | Server error | Check Sliplane logs |
| 503 | External API unavailable (Mistral, Gemini, Cohere) | Graceful fallback nebo wait |