{"openapi":"3.1.0","info":{"title":"Kramerius API","description":"Dohledá UUID stránky a metadata v Kramerius digitálních knihovnách (NDK, ČDK\ni regionální instance z registru `digitalniknihovna.cz`).\n\n## Použití\n\n- Veřejný endpoint přes gateway: `POST /kramerius/api/` (routuje se na `POST /resolve`).\n- Request je JSON body (`Content-Type: application/json`).\n- `GET /kramerius/api/` bez parametrů zobrazí interaktivní dokumentaci (Swagger UI);\n  `GET` s parametry nebo body vrací `405 method_not_allowed`.\n- Interaktivní webové UI nástroje běží na `/kramerius/`.\n\n## Režimy dohledání\n\n1. **Wikidatový lookup** — povinné `wd` (QID, např. `Q96613911`) a `page`.\n   Volitelná zpřesnění: `volume`, `volume_years`, `issue`, `issue_type`\n   (shoda podle `mods:note` v `streams/BIBLIO_MODS`), `issue_date`\n   (`YYYY`, `YYYY-MM`, `YYYY-MM-DD` i `DD.MM.YYYY`), `supplement`.\n   UUID dokumentu se získá z Wikidat (typicky `P13032`, u některých položek `P8752`).\n2. **Přímý UUID lookup** — povinné `uuid` (UUID stránky v Krameriu).\n   Projde hierarchii stránka → vydání → svazek → dokument a vrátí všechna metadata.\n   U stránek z příloh (`supplement`) vrací `issue_uuid` rodičovského čísla\n   (`periodicalitem`), aby `issue`/`issue_date`/`citation` zůstaly konzistentní.\n\nOběma režimy lze doplňovat `source_url` (resp. legacy `input_url`) s URL konkrétní\nKramerius instance, `manual_limit` (10–400) a `manual_depth` (3–16) pro manuální\nfallback a `debug` pro ladicí výstup (`debug_trace`, `failure_context`).\n\n## Sémantika výsledku\n\n- `ok=true`, `selection_required=false` — jeden i více platných automatických výsledků\n  (klient může použít `results[0]`).\n- `selection_required=true` — automatické dohledání není jednoznačné; klient má\n  přepnout do ručního výběru (endpoint `/manual-browser`), typicky HTTP 300 s kandidáty.\n\n## Struktura výsledku (`results[]`)\n\n`api_base`, `view_url` (odkaz do vieweru zdrojové instance), `document_uuid`,\n`volume_uuid`, `issue_uuid`, `page_uuid`, `document` (název), `volume_nr`,\n`volume_years`, `issue`, `issue_type`, `issue_date` (normalizované ISO podle\ndostupné přesnosti), `supplement`, `page`, `page_label`, `citation`.\nKandidáti pro ruční výběr (HTTP 300) mají navíc `preview_url` a `full_preview_url`.\n\n## Limity výsledků\n\nEndpoint vrací max. prvních 25 výsledků v dávce. `results_meta.minimum_total_count`\nje minimální jistý počet nalezených položek; `results_meta.has_more=true` znamená,\nže existují další kandidáti (offset/cursor API neposkytuje).\n\n## Chybové kódy (`error_code`)\n\n- `selection_required` (300) — více kandidátů, nutný ruční výběr.\n- `missing_required_params` (400), `invalid_wd` (400) — validace vstupu.\n- `cors_origin_blocked`, `cors_origin_not_allowed` (403) — CORS (vrací gateway/PHP vrstva).\n- `wikidata_uuid_not_found` (404), `wikidata_entity_not_found` (404),\n  `document_not_found` (404), `api_base_not_found` (404), `upstream_not_found` (404),\n  `all_wd_candidates_failed` (404).\n- `method_not_allowed` (405) — nepodporovaná metoda nebo GET s parametry.\n- `missing_document_uuid` (422), `page_not_found` (422), `empty_document` (422),\n  `volume_not_found`, `issue_not_found`, `no_matching_issue`, `no_pages_in_issue`.\n- `invalid_upstream_json` (502), `registry_unavailable` (502).\n\nChybová odpověď má tvar `{ ok: false, error, error_code, selection_required: false,\nresults: [], results_meta, status }`. Při `debug=true` se vrací navíc `error_detail`,\n`debug_trace` a `failure_context`.\n\n## CORS\n\nCORS je řízeno environment proměnnými; bez whitelistu originů je vypnuté.\nRežim `whitelist` (výchozí, jen povolené originy: `KRAMERIUS_API_CORS_ALLOWED_ORIGINS`)\nnebo `blacklist` (povolené vše kromě `KRAMERIUS_API_CORS_BLOCKED_ORIGINS`, podporuje\nmasku `*.domena.tld`). Volitelně `KRAMERIUS_API_CORS_ALLOW_CREDENTIALS`,\n`KRAMERIUS_API_CORS_ALLOWED_HEADERS`, `KRAMERIUS_API_CORS_EXPOSE_HEADERS`,\n`KRAMERIUS_API_CORS_MAX_AGE` (výchozí `600`). K dispozici jsou kratší aliasy bez\nprefixu `KRAMERIUS_API_` (`CORS_MODE`, `CORS_ALLOWED_ORIGINS`, …). Se zapnutými\ncredentials se nesmí použít wildcard `*`.\n\n## Testy\n\n- CORS smoke test: `bash kramerius/tests/cors-smoke-test.sh`\n- Integrační smoke test endpointu: `kramerius/tests/kramerius-integration-smoke-test.py`\n- PHP integrační test s mockovanými upstream odpověďmi:\n  `php kramerius/tests/api-endpoint-integration-test.php`\n","version":"2.1.0"},"paths":{"/health":{"get":{"tags":["monitoring"],"summary":"Kontrola živosti služby","description":"Vrátí stav služby pro liveness proby. Odpověď je vždy `ok=true, status=200`.","operationId":"health_health_get","responses":{"200":{"description":"Služba je živá","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Get"},"example":{"ok":true,"status":200}}}}}}},"/metrics":{"get":{"tags":["monitoring"],"summary":"Prometheus metriky","description":"Vrátí metriky ve formátu Prometheus (text/plain; version=0.0.4).","operationId":"metrics_metrics_get","responses":{"200":{"description":"Metriky ve formátu Prometheus","content":{"application/json":{"schema":{}},"text/plain":{"schema":{"type":"string"}}}}}}},"/":{"post":{"tags":["resolve"],"summary":"Vyřešení odkazu (alias /resolve)","description":"Alias endpointu `/resolve` pro zpětnou kompatibilitu. Přijímá stejný JSON payload.","operationId":"resolve_root__post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"properties":{"input":{"type":"string","description":"Vstupní odkaz, UUID, Wikidata QID nebo OAI identifikátor."},"page":{"type":"string","description":"Číslo stránky nebo rozsah (např. `14` nebo `14–31`)."},"manual_limit":{"type":"integer","description":"Volitelný horní limit počtu manuálních kandidátů (rozsah 10–400)."},"manual_depth":{"type":"integer","description":"Volitelná hloubka manuálního prohledávání hierarchie (rozsah 3–16)."}},"type":"object","title":"Payload"}}},"required":true},"responses":{"200":{"description":"Úspěšné vyřešení, viz /resolve.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Resolve Root  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/resolve":{"post":{"tags":["resolve"],"summary":"Vyřešení odkazu na stránku","description":"Dohledá UUID stránky v Krameriu podle vstupu nebo metadata stránky podle UUID. Dva režimy: (1) Wikidatový lookup `wd` + `page` (nepovinně `uuid` pro přímý lookup), (2) Přímý UUID lookup `uuid`. Návratová hodnota obsahuje `results[]` s metadaty (document, volume, issue, page, citation).","operationId":"resolve_resolve_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"properties":{"uuid":{"type":"string","description":"UUID stránky (režim 2). Má prioritu před `wd`."},"wd":{"type":"string","description":"Wikidata QID (režim 1), např. `Q12345`."},"page":{"type":"string","description":"Číslo stránky (povinné v režimu 1), např. \"140\"."},"volume":{"type":"string","description":"Ročník nebo svazek."},"volume_years":{"type":"string","description":"Období ročníku (např. `1831` nebo `1830-1831`)."},"issue":{"type":"string","description":"Číslo vydání."},"issue_type":{"type":"string","description":"Typ vydání, používá se pro zpřesnění podle `mods:note` (např. `večerní`)."},"issue_date":{"type":"string","description":"Datum vydání v ISO formátu (YYYY, YYYY-MM nebo YYYY-MM-DD)."},"supplement":{"type":"string","description":"Číslo přílohy (např. `1` nebo \"2. příloha\")."},"source_url":{"type":"string","description":"Zdrojová URL instance Kramerius (pokud není obsaženo v `uuid`/`wd`)."},"input_url":{"type":"string","description":"Legacy alias pro `source_url`."},"manual_limit":{"type":"integer","description":"Volitelný horní limit počtu manuálních kandidátů (rozsah 10–400)."},"manual_depth":{"type":"integer","description":"Volitelná hloubka manuálního prohledávání hierarchie (rozsah 3–16)."},"api_base":{"type":"string","description":"Volitelný explicitní API base Kramerius."},"debug":{"type":"boolean","description":"Pokud `true`, vrací se `debug_trace` pro debugging."}},"type":"object","title":"Payload"},"examples":{"uuid_lookup":{"summary":"Přímý UUID lookup (režim 2)","value":{"uuid":"35d64c24-9cf2-408b-9147-935f1323be20","debug":false}},"wikidata_lookup":{"summary":"Wikidatový lookup (režim 1)","value":{"wd":"Q96613911","page":"140","volume":"3","volume_years":"1849","issue":"18","issue_date":"1849-04-01","debug":false}}}}},"required":true},"responses":{"200":{"description":"Úspěšné dohledání (`ok: true`). Struktura: `results[]` obsahuje `document`, `volume_nr`, `issue`, `page_uuid`, `page_label`, `citation` atd.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Resolve Resolve Post"},"example":{"ok":true,"selection_required":false,"results_meta":{"offset":0,"batch_size":25,"returned_count":1,"minimum_total_count":1,"has_more":false},"results":[{"api_base":"https://ndk.cz/search/api/v5.0","view_url":"https://ndk.cz/uuid/uuid:35d64c24-9cf2-408b-9147-935f1323be20","document_uuid":"ae811ecf-435d-11dd-b505-00145e5790ea","volume_uuid":"b1a8ec04-435d-11dd-b505-00145e5790ea","issue_uuid":"e5204673-435d-11dd-b505-00145e5790ea","page_uuid":"35d64c24-9cf2-408b-9147-935f1323be20","document":"Blahověst","volume_nr":"3","volume_years":"1849","issue":"18","issue_type":"","issue_date":"1849-04-01","supplement":"","page":"140","page_label":"140","citation":"Blahověst: Katolický týdenník pro Čechy, roč. 3 (1849), č. 18, 1. dubna 1849, str. 140"}]}}}},"300":{"description":"`selection_required: true` — automatické dohledání není jednoznačné, uživatel musí ručně vybrat z kandidátů v `results[]` (každý kandidát má navíc `preview_url` a `full_preview_url`). Klient má přepnout do manuálního režimu (viz endpoint `/manual-browser`).","content":{"application/json":{"example":{"ok":false,"error_code":"selection_required","selection_required":true,"manual_user_message":"Bylo nalezeno více odpovídajících výsledků. Vyberte prosím správnou stránku.","results":[{"page_uuid":"35d64c24-9cf2-408b-9147-935f1323be20","view_url":"https://ndk.cz/uuid/uuid:35d64c24-9cf2-408b-9147-935f1323be20","document":"Blahověst","issue":"18","page":"140","preview_url":"https://ndk.cz/.../preview","full_preview_url":"https://ndk.cz/.../full"}]}}}},"400":{"description":"Chyba ve vstupu. `error_code`: `missing_required_params` (chybí `page`, resp. `issue`/`issue_date`), `invalid_wd` (neplatný formát QID)."},"403":{"description":"CORS origin není povolen (`cors_origin_not_allowed`, `cors_origin_blocked`)."},"404":{"description":"Zdroj nebyl nalezen. `error_code`: `wikidata_uuid_not_found`, `wikidata_entity_not_found`, `document_not_found`, `api_base_not_found`, `upstream_not_found`, `all_wd_candidates_failed`."},"405":{"description":"Nepodporovaná metoda (`method_not_allowed`)."},"422":{"description":"Dohledání selhalo. `error_code`: `missing_document_uuid` (Wikidata bez UUID dokumentu), `page_not_found`, `empty_document`, `volume_not_found`, `issue_not_found`, `no_matching_issue`, `no_pages_in_issue`. Pole `selection_required` říká, zda má klient nabídnout ruční pokračování.","content":{"application/json":{"example":{"ok":false,"error":"Položka na Wikidatech neobsahuje UUID dokumentu.","error_code":"missing_document_uuid","selection_required":false,"results":[],"results_meta":{"offset":0,"batch_size":25,"returned_count":0,"minimum_total_count":0,"has_more":false},"status":422}}}},"502":{"description":"Chyba nadřazené služby. `error_code`: `invalid_upstream_json`, `registry_unavailable`."}}}},"/manual-browser":{"post":{"tags":["resolve"],"summary":"Manuální procházení stránek","description":"Vrátí seznam stránek pro ruční výběr, když automatické dohledání selže. Podporuje procházení podle volume/issue/date.","operationId":"manual_browser_manual_browser_post","requestBody":{"content":{"application/json":{"schema":{"properties":{"document_uuid":{"type":"string","description":"UUID dokumentu."},"page":{"type":"string","description":"Číslo stránky."},"volume":{"type":"string","description":"Ročník."},"issue":{"type":"string","description":"Číslo."},"issue_date":{"type":"string","description":"Datum čísla."}},"additionalProperties":true,"type":"object","title":"Payload"}}},"required":true},"responses":{"200":{"description":"Seznam stránek pro manuální výběr.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Manual Browser Manual Browser Post"}}}},"400":{"description":"Chybějící povinné parametry."},"422":{"description":"Stránka nebyla nalezena."},"502":{"description":"Nadřazená služba nedostupná."}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}},"tags":[{"name":"resolve","description":"Hlavní endpoint pro dohledání stránky v Krameriu."},{"name":"monitoring","description":"Liveness a Prometheus metriky."}]}