Files
2026-09-03 10:03:10 +02:00

9.6 KiB

API

Vsechny cesty jsou uvedene relativne. Verejne se volaji s prefixem /apps/html-to-pdf, ktery Caddy pred predanim do containeru odstranuje.

Odpovedi jsou JSON, vyjimkou je stazeni hotoveho PDF.

POST /convert

Synchronni prevod. Vraci primo application/pdf.

Urceno pro mensi dokumenty. Pokud konverze presahne SYNC_TIMEOUT_SECONDS (vychozi 60 s), job se zrusi a sluzba vrati HTTP 413 s odkazem na asynchronni endpoint. Zruseni se loguje, nikdy nezmizi potichu.

Hlavicky odpovedi:

Hlavicka Vyznam
X-Page-Count pocet stranek vysledku
X-Engine-Used engine, ktery dokument vyrenderoval
X-Missing-Assets pocet assetu, ktere se nepodarilo nacist
X-Warnings pocet varovani

Pokud je X-Missing-Assets nenulovy, v PDF neco chybi. Detaily jsou dostupne jen u asynchronni cesty, kde se vraci cely seznam.

Vysledek se nikam neuklada. Vznikne v docasnem adresari, odesle se v odpovedi a hned po jejim odeslani se i s pracovnim adresarem smaze. Neexistuje adresa, ze ktere by sel stahnout znovu. Kdo potrebuje vysledek pozdeji, pouzije POST /jobs.

POST /jobs

Zaradi konverzi do fronty. Vraci HTTP 202.

{
  "job_id": "1f0e...",
  "status": "queued",
  "created_at": "2026-08-27T10:00:00Z",
  "result_url": "/apps/html-to-pdf/jobs/1f0e.../result"
}

GET /jobs/{job_id}

Stav jobu.

{
  "job_id": "1f0e...",
  "status": "running",
  "created_at": "2026-08-27T10:00:00Z",
  "started_at": "2026-08-27T10:00:01Z",
  "finished_at": null,
  "expires_at": null,
  "progress": {
    "pages_rendered": 420,
    "chunks_done": 9,
    "chunks_total": 21,
    "pass_number": 1
  },
  "engine_used": null,
  "page_count": null,
  "missing_assets": [],
  "warnings": [],
  "error": null,
  "result_url": null
}

Stavy: queued, running, done, failed, cancelled, expired.

Pole pass_number rozlisuje prvni a druhy pruchod. Druhy pruchod nastava jen tehdy, kdyz se generuje obsah se skutecnymi cisly stranek, takze u takoveho dokumentu ukazatel postupu probehne dvakrat.

GET /jobs/{job_id}/result

Stahne hotove PDF. Soubor se streamuje, nenacita se cely do pameti. Stahovat lze opakovane, dokud vysledek nevyprsi.

  • 404 job neexistuje nebo uz expiroval
  • 409 job jeste nedobehl nebo skoncil chybou

DELETE /jobs/{job_id}

Zrusi bezici job nebo smaze hotovy vysledek hned, bez cekani na expiraci. Soubor z uloziste zmizi okamzite, zaznam jobu zustava jeste po dobu TTL, aby bylo videt, co se s nim stalo.

Uchovavani souboru

Nic se neuklada trvale. Vsechno zije jen v docasnem adresari STORAGE_DIR a v pameti procesu.

Co Kde skonci Jak dlouho
zdrojove HTML jen v pameti behem konverze do konce konverze
vysledek POST /convert docasny adresar jobu do odeslani odpovedi, pak se maze
vysledek POST /jobs docasny adresar jobu JOB_RESULT_TTL_SECONDS od dokonceni, vychozi 1 hodina
meziprodukty renderu docasny adresar jobu do dokonceni jobu, pak se mazou
vysledek neuspesneho jobu nikde pracovni adresar se maze hned

Presny cas expirace je v poli expires_at u GET /jobs/{job_id}. Po nem se smaze soubor i zaznam jobu a dalsi dotaz vraci 404 job_not_found.

Uloziste neni trvale. Restart sluzby znamena ztratu rozpracovanych jobu i hotovych vysledku, ktere jeste nikdo nestahl. Rozpracovane joby se oznaci jako failed s kodem service_restarted. Pri startu se navic smazou adresare jobu, ktere po restartu zustaly bez zaznamu.

GET /health

Stav sluzby, verze, dostupnost enginu a stav fronty. U Chromia se dostupnost overuje skutecnym nastartovanim prohlizece.

status je ok, pokud je k dispozici alespon jeden engine, jinak degraded.

GET /version

Nazev aplikace, verze a aktualni root_path.

GET /docs

Swagger UI. OpenAPI dokument obsahuje servers s prefixem /apps/html-to-pdf, takze tlacitko Try it out vola spravnou verejnou cestu.

Telo requestu

Stejne pro /convert i /jobs. Povinne je pouze source, vsechno ostatni ma pouzitelnou vychozi hodnotu.

{
  "source": {
    "url": "https://example.com/dokument.html",
    "html": null,
    "base_url": null
  },
  "engine": "auto",
  "page": {
    "format": "A4",
    "orientation": "portrait",
    "margin": { "top": "20mm", "right": "15mm", "bottom": "20mm", "left": "15mm" }
  },
  "page_numbers": {
    "enabled": false,
    "format": "{page} / {pages}",
    "position": "bottom-center",
    "mode": "auto",
    "start_at": 1
  },
  "toc": { "enabled": false, "depth": 3, "title": "Obsah" },
  "outline": true,
  "pdf_profile": null,
  "assets": { "allow_remote": true, "timeout_seconds": 10 },
  "chunking": { "enabled": true, "pages_per_chunk": 50 },
  "wait_for": { "state": "load", "selector": null, "timeout_seconds": 30 },
  "filename": null,
  "callback_url": null
}

source

Vyplnene musi byt prave jedno z poli url a html, jinak sluzba vraci 422. base_url slouzi k rozpadu relativnich cest a pouziva se hlavne spolu s html. Pri pouziti url se base_url odvodi z finalni adresy po presmerovanich.

engine

  • weasyprint spravne strankovani, nizka pametova narocnost, nespousti JavaScript
  • chromium zvladne i dokumenty dokreslovane JavaScriptem
  • auto zvoli chromium, pokud dokument obsahuje aktivni skripty, jinak weasyprint. Pokud render pres weasyprint selze, sluzba to zaloguje a zopakuje ho pres chromium, coz se objevi ve warnings.

page.format

Nazev formatu (A4, A5, Letter) nebo explicitni rozmer (210mm 297mm). U nazvu se uplatni i orientation, u explicitniho rozmeru je orientace dana poradim hodnot.

page_numbers.mode

  • css cislovani resi CSS countery pri renderu. Nejhezci vysledek, ale funguje jen kdyz se cely dokument renderuje najednou, protoze v kazde casti se citac stranek restartuje. Vyzaduje engine: weasyprint a chunking.enabled: false, jinak sluzba vraci 400 s kodem unsupported_combination.
  • overlay cisla se dopisi do hotoveho PDF jako pruhledna vrstva. Jedina moznost u clenenych dokumentu a u Chromia.
  • auto zvoli css u necleneneho dokumentu renderovaneho WeasyPrintem, jinak overlay.

Cislovaci vrstvu kresli WeasyPrint i tehdy, kdyz dokument vyrenderovalo Chromium. Bez nainstalovaneho WeasyPrintu proto cislovani stranek nefunguje.

toc

Generuje obsah s odkazy a skutecnymi cisly stranek. Vyzaduje engine: weasyprint, protoze Chromium neumi rict, na ktere strance nadpis skoncil. Pri jine kombinaci sluzba vraci 400.

Nadpisy bez atributu id ho dostanou automaticky.

Pokud je dokument rozdelen na casti, odkazy v obsahu nejsou klikatelne, protoze cil lezi v jine casti. Cisla stranek jsou spravna. Sluzba na to upozorni ve warnings.

outline

Zalozky PDF generovane z nadpisu h1 az h6. Vypnuti se resi CSS pravidlem bookmark-level: none, takze funguje i uvnitr jednotlivych casti.

pdf_profile

Predava se WeasyPrintu jako pdf_variant. Povolene hodnoty: pdf/a-1b, pdf/a-2b, pdf/a-3b, pdf/a-4b, pdf/ua-1.

assets

allow_remote: false zakaze stahovani externich assetu. Nedostupne assety render nezastavi, ale objevi se v missing_assets a v logu.

chunking

pages_per_chunk je cilova velikost casti. Skutecny pocet stranek je znamy az po renderu, deleni proto vychazi z odhadu podle mnozstvi textu, obrazku a radku tabulek. Rez vznika vzdy jen mezi primymi potomky kontejneru, takze tabulka ani odstavec se nikdy nerozdeli.

Delici body jsou elementy section, article, h1, elementy s atributem data-chunk a elementy se stylem obsahujicim page-break-before nebo break-before. Pokud dokument zadny takovy nema, renderuje se vcelku a sluzba to zaloguje.

obrazky v tabulkach

WeasyPrint neumi vyresit procentualni sirku obrazku uvnitr bunky tabulky. Sirku bunky v tu chvili jeste nezna, obrazek vyjde nulove siroky a z PDF zmizi bez jakekoliv chyby, takze se neobjevi ani v missing_assets.

Sluzba proto u enginu weasyprint u obrazku uvnitr td a th prepise width: <n>% na width: auto a procentualni atribut width odstrani. Pokud obrazek nema zadne max-width, doplni se max-width: 100%, aby z bunky nevystoupil. Obrazek se tim vykresli ve sve vlastni velikosti omezene bunkou, coz je to, co dokument zamyslel.

Chromia se to netyka, ten takove obrazky rozvrhne spravne, a uprava se u nej neprovadi.

wait_for

Pouziva jen Chromium. state je load, domcontentloaded nebo networkidle, selector navic ceka na konkretni element.

callback_url

Po dokonceni nebo selhani jobu na nej sluzba posle POST se stejnym telem, jake vraci GET /jobs/{job_id}. Neuspech se loguje a nekolikrat zopakuje, job se kvuli nemu neoznaci jako failed.

Chyby

{
  "error_code": "blocked_target",
  "message": "Cilova adresa smeruje do privatniho nebo vyhrazeneho rozsahu a je zablokovana.",
  "detail": { "url": "http://127.0.0.1/a.html", "host": "127.0.0.1" }
}
error_code HTTP Kdy nastane
invalid_request 400 chybny vstup
blocked_target 400 adresa smeruje do zakazaneho rozsahu nebo ma nepovolene schema
unsupported_combination 400 nepodporovana kombinace parametru, napriklad obsah s Chromiem
limit_exceeded 400 prekrocen nakonfigurovany limit
source_unavailable 502 zdrojovy dokument se nepodarilo stahnout
render_timeout 504 render presahl casovy limit
sync_too_long 413 synchronni konverze presahla limit, pouzijte POST /jobs
engine_unavailable 503 pozadovany engine neni k dispozici
queue_full 503 fronta je plna
job_not_found 404 job neexistuje nebo expiroval
result_not_ready 409 job jeste nedobehl
service_restarted v tele jobu sluzba se restartovala drive, nez job dobehl
internal_error 500 neocekavana chyba