Riferimento API di Patu
Ogni richiesta è autenticata con un'intestazione X-Api-Key. L'URL di base è https://patu.dev. Invia i byte grezzi dell'asset come corpo della richiesta. Patu ottimizza immagini, SVG, JavaScript, CSS e font; gli asset non immagine sono instradati in base al Content-Type della richiesta (vedi Asset non immagine). I libri EPUB, gli archivi fumetto CBZ/CBR e i documenti Office sono ottimizzati come container completo (vedi Ottimizzazione EPUB, Ottimizzazione CBZ/CBR, Ottimizzazione Office, Ottimizzazione PDF).
POST /v1/compress
Comprimi una singola immagine. Restituisce i byte (in linea, valore predefinito) o un manifest JSON (archiviato).
| Parametro | Predefinito | Significato |
|---|---|---|
mode | inline | inline restituisce i byte della variante scelta; stored salva ogni variante e restituisce un manifest. |
formats | avif,webp,fallback | Elenco separato da virgole. fallback corrisponde a JPEG per le foto, WebP/PNG senza perdita per grafica o trasparenza. |
widths | originale | Elenco di larghezze in pixel separate da virgole. Mai ingrandita oltre la larghezza sorgente. |
target | per chiave | Il punteggio ssimulacra2 a cui puntare (più alto = più vicino all'originale, file più grande). Tipicamente 75–85. |
Per la modalità in linea, il formato restituito rispetta anche l'intestazione Accept quando formats non è impostato.
Risposta in linea
I byte dell'immagine, più intestazioni che descrivono il risultato:
X-Patu-Format | formato di trasmissione scelto (avif/webp/jpeg, oppure js/css/woff2/svg) |
X-Patu-Original-Bytes / X-Patu-Output-Bytes | dimensioni di input e output |
X-Patu-Encoded-Bytes | per JS/CSS, la dimensione dopo che l'edge comprime la risposta (Brotli/gzip): ciò che attraversa realmente la rete |
X-Patu-Latency-Ms | tempo di elaborazione lato server per questa richiesta, in millisecondi |
X-Patu-Integrity | token Subresource Integrity (sha384-…) dei byte serviti, pronto per <script>/<link> integrity="…" |
X-Patu-Score | punteggio ssimulacra2 (misurato, oppure previsto nel percorso rapido) |
X-Patu-Verified | true se il punteggio è stato misurato in questa richiesta, false se previsto dal modulo di apprendimento |
X-Patu-Cache | hit o miss nella cache di deduplicazione in-process |
X-Patu-Passthrough | true quando nessuna variante supera l'originale, quindi l'originale viene restituito invariato |
Risposta archiviata (manifest)
{
"id": "<64-hex content hash>",
"url": "https://cdn.patu.dev/a/<id>",
"original": { "width": 1024, "height": 683, "bytes": 240133, "format": "jpeg" },
"variants": [
{ "format": "avif", "width": 1024, "height": 683, "bytes": 30215,
"score": 81.5, "url": "https://cdn.patu.dev/a/<id>/1024.avif",
"integrity": "sha384-…" }
],
"created_at": "2026-06-29T12:00:00Z"
}
L'url radice è un unico URL negoziato per contenuto: l'edge restituisce il miglior formato consentito dall'Accept della richiesta. Ogni url di variante è un URL esplicito, immutabile e indirizzato per contenuto, per un formato e una larghezza esatti. Gli URL sono assoluti su cdn.patu.dev quando la CDN è abilitata (impostazione predefinita), oppure relativi al pod /v1/asset/<id>/… quando è disabilitata. Ripubblicare la stessa immagine con nuove widths estende il manifest con solo le nuove larghezze; una richiesta identica viene deduplicata e restituisce il manifest esistente invariato. Ogni variante porta il proprio token integrity, e l'edge lo serve come X-Patu-Integrity.
Asset non immagine: JS, CSS, SVG e font
Lo stesso endpoint /v1/compress (in linea o mode=stored) ottimizza testo e font. Non hanno magic byte, quindi instradali con il Content-Type della richiesta.
| Content-Type | Cosa fa Patu |
|---|---|
text/javascript, text/css | Minifica con esbuild (solo spazi bianchi e sintassi sicura; gli identificatori non vengono mai rinominati, quindi i nomi esportati e globali sopravvivono). L'edge lo comprime (Brotli/gzip) alla consegna. |
font/ttf, font/otf, font/woff, font/collection | Converte in WOFF2. Subsetting opzionale dei glifi (di seguito). |
font/woff2 | È già il formato di destinazione; servito invariato. |
image/svg+xml | Sanificato (script, gestori di eventi e riferimenti esterni rimossi, CSP rafforzata alla consegna), poi ottimizzato nel path e nella struttura con silk. |
L'output non supera mai l'input. X-Patu-Format vale js, css, woff2 o svg, e X-Patu-Score non si applica (-1). Codice e font archiviati sono serviti dall'edge come le immagini, con lo stesso integrity.
Per JS e CSS, confronta X-Patu-Encoded-Bytes, non X-Patu-Output-Bytes. La sola minificazione (Output-Bytes) vale solo pochi punti percentuali; il vero guadagno è minify più il Brotli dell'edge, che è ciò che riporta Encoded-Bytes (la dimensione che attraversa la rete): su un corpus reale, questo significa circa -90% su JS e -93% su CSS rispetto alla sorgente grezza.
| Parametro | Si applica a | Significato |
|---|---|---|
subset-unicodes | font | Suddivide in base a intervalli di code point, ad es. 0-FF,20AC,2000-206F. Elimina i glifi non usati, il guadagno più grande per i font. Esadecimale, prefisso U+ opzionale. |
subset-text | font | Suddivide sui glifi che coprono questa stringa (unita a subset-unicodes). Utile per un set di caratteri piccolo e noto; per un intero sito calcola i code point in fase di build e passa subset-unicodes. |
# minify a stylesheet curl -X POST "https://patu.dev/v1/compress" \ -H "X-Api-Key: $PATU_KEY" -H "Content-Type: text/css" \ --data-binary @app.css -o app.min.css # convert + subset a font to a Latin WOFF2 curl -X POST "https://patu.dev/v1/compress?subset-unicodes=0-FF,20AC" \ -H "X-Api-Key: $PATU_KEY" -H "Content-Type: font/ttf" \ --data-binary @Inter.ttf -o Inter.woff2
Ottimizzazione EPUB
POST /v1/epub ottimizza un intero container EPUB: le immagini incorporate passano per la stessa ricerca di qualità di /v1/compress, e i font TTF/OTF vengono sottoposti a subsetting sui glifi effettivamente usati dal libro. Invia i byte grezzi del file .epub come corpo della richiesta.
| Parametro | Predefinito | Significato |
|---|---|---|
profile | compat | Profilo di ottimizzazione. compat è l'unico profilo accettato da P1; qualsiasi altro valore restituisce 400. |
La quota è un'unità per immagine elaborata più una per font sottoposto a subsetting, conteggiata dal manifest prima che venga eseguita qualsiasi codifica. Una richiesta rifiutata per quota, dimensione o DRM non consuma nulla.
Sincrono e asincrono
I corpi fino a 10 MB (10,485,760 byte) inclusi vengono ottimizzati in linea e restituiti direttamente nella risposta. I corpi più grandi vengono accodati e la risposta è 202:
{ "id": "a1b2c3d4e5f67890", "status_url": "/v1/epub/a1b2c3d4e5f67890" }
Esegui il polling di GET /v1/epub/{id}: 202 finché il job è ancora in esecuzione, 200 con l'.epub ottimizzato una volta terminato. I risultati asincroni scadono 24 ore dopo il completamento. Un riavvio del pod elimina ogni job ancora in sospeso in memoria; la sua GET risponde quindi 404: invia di nuovo la richiesta originale.
Intestazioni di risposta
X-Patu-Original-Bytes / X-Patu-Output-Bytes | dimensioni del container in input e output |
X-Patu-Images-Processed | numero di immagini effettivamente ricodificate |
X-Patu-Fonts-Subset | numero di font effettivamente sottoposti a subsetting |
X-Patu-Quota-Used | la quota effettivamente consumata da questa richiesta (immagini elaborate + font sottoposti a subsetting) |
X-Patu-Passthrough | true quando la ricompressione degli elementi ottimizzati non ha prodotto alcun guadagno, quindi i byte originali vengono restituiti invariati |
Dimensione massima di caricamento per piano
| Piano | Caricamento massimo |
|---|---|
| Free | 25 MB |
| Basic | 25 MB |
| Pro | 50 MB |
| Business | 150 MB |
Errori
400 | container epub non valido, oppure profile sconosciuto |
413 | caricamento superiore alla dimensione massima del piano (sopra). Non conta ai fini della quota. |
422 | il libro usa una cifratura DRM e non può essere ottimizzato |
429 | la quota mensile verrebbe superata. Non conta ai fini della quota. |
# optimize a book (sync if <= 10 MB) curl -X POST "https://patu.dev/v1/epub" \ -H "X-Api-Key: $PATU_KEY" \ --data-binary @book.epub -o book.optimized.epub # a larger book: submit, then poll the async result curl -X POST "https://patu.dev/v1/epub" -H "X-Api-Key: $PATU_KEY" \ --data-binary @big-book.epub curl "https://patu.dev/v1/epub/a1b2c3d4e5f67890" -H "X-Api-Key: $PATU_KEY" \ -o big-book.optimized.epub
Ottimizzazione CBZ/CBR
POST /v1/cbz ottimizza un intero archivio fumetto: ogni pagina passa per la stessa ricerca di qualità a parità di formato usata da /v1/compress (una pagina JPEG resta JPEG, una pagina PNG resta PNG), tutto il resto nell'archivio - ComicInfo.xml, qualsiasi altro file collaterale - passa invariato, e l'ordine e i nomi delle pagine non cambiano mai. Invia i byte grezzi del file .cbz o .cbr come corpo della richiesta; un archivio CBR (RAR) viene convertito in modo trasparente in CBZ.
| Parametro | Predefinito | Significato |
|---|---|---|
profile | compat | Profilo di ottimizzazione. compat è l'unico profilo accettato; qualsiasi altro valore restituisce 400. |
max_width | nativa | Ridimensiona ogni pagina al massimo a questa larghezza prima della ricerca/ricodifica (mai ingrandita). Intero, 0–8192; fuori intervallo restituisce 400. |
La quota è un'unità per pagina elaborata, conteggiata dall'archivio prima che venga eseguita qualsiasi codifica. Una richiesta rifiutata per quota o dimensione non consuma nulla.
Sempre asincrono
Ogni richiesta a /v1/cbz viene accodata e la risposta è 202 - non esiste un percorso sincrono, perché anche la ricerca di qualità su tutte le pagine di un archivio fumetto piccolo può richiedere tempo:
{ "id": "a1b2c3d4e5f67890", "status_url": "/v1/cbz/a1b2c3d4e5f67890" }
Esegui il polling di GET /v1/cbz/{id}: 202 finché il job è ancora in esecuzione, 200 con il file .cbz ottimizzato una volta terminato. I risultati asincroni scadono 24 ore dopo il completamento. Un riavvio del pod elimina ogni job ancora in sospeso in memoria; la sua GET risponde quindi 404 - invia di nuovo la richiesta originale.
Intestazioni di risposta (su GET)
Content-Type | sempre application/vnd.comicbook+zip, indipendentemente dal fatto che l'input fosse CBZ o CBR |
X-Patu-Container-Converted | cbz, presente solo quando l'input era un archivio CBR convertito in CBZ |
Errori
400 | container cbz/cbr non valido, oppure profile/max_width sconosciuto |
413 | caricamento superiore alla dimensione massima del piano, oppure l'archivio supera il budget di dimensione decompressa. Non conta ai fini della quota. |
429 | la quota mensile verrebbe superata. Non conta ai fini della quota. |
# submit a comic archive, then poll the async result
curl -X POST "https://patu.dev/v1/cbz" -H "X-Api-Key: $PATU_KEY" \
--data-binary @issue-01.cbz
curl "https://patu.dev/v1/cbz/a1b2c3d4e5f67890" -H "X-Api-Key: $PATU_KEY" \
-o issue-01.optimized.cbz
Ottimizzazione Office
POST /v1/office ottimizza un intero documento Office (.docx, .pptx, .odt, .odp, .ods): le immagini incorporate passano per la stessa ricerca di qualità a parità di formato usata da /v1/compress (un'immagine JPEG resta JPEG, una PNG resta PNG), tutto il resto - markup, stili, relazioni, oggetti incorporati - passa invariato. Il formato del documento non cambia mai: nessuna conversione, nessun ridimensionamento (nessun max_width) e nessuna scelta di profile. Invia i byte grezzi del documento come corpo della richiesta. .xlsx (fogli di calcolo OOXML) viene rifiutato in base al nome - vedi Rifiuti di seguito - .ods è accettato.
La quota è un'unità per immagine elaborata, conteggiata dal documento prima che venga eseguita qualsiasi codifica. Una richiesta rifiutata per quota, dimensione o uno dei rifiuti descritti di seguito non consuma nulla.
Sincrono e asincrono
I corpi fino a 10 MB (10,485,760 byte) inclusi vengono ottimizzati in linea e restituiti direttamente nella risposta. I corpi più grandi vengono accodati e la risposta è 202:
{ "id": "a1b2c3d4e5f67890", "status_url": "/v1/office/a1b2c3d4e5f67890" }
Esegui il polling di GET /v1/office/{id}: 202 finché il job è ancora in esecuzione, 200 con il documento ottimizzato una volta terminato. I risultati asincroni scadono 24 ore dopo il completamento. Un riavvio del pod elimina ogni job ancora in sospeso in memoria; la sua GET risponde quindi 404 - invia di nuovo la richiesta originale.
Rifiuti
Un documento viene rifiutato per intero, prima di qualsiasi codifica, quando corrisponde a uno di questi casi - ciascuno ha un proprio messaggio 400 distinto:
| Motivo | Dettaglio |
|---|---|
| Formato OLE legacy | un binario pre-2007 .doc/.xls/.ppt - rilevato dal suo magic number compound-file, non dall'estensione, quindi viene rifiutato indipendentemente dal fatto che il file abbia un nome errato |
| Firmato digitalmente | il pacchetto contiene una parte di firma (_xmlsignatures/ di OOXML o META-INF/documentsignatures.xml di ODF) - ricomprimere una qualsiasi voce invaliderebbe la firma |
| Foglio di calcolo .xlsx | rifiutato in base al nome del file, non al contenuto: i fogli di calcolo non contengono praticamente mai immagini incorporate che valga la pena ottimizzare. Questo controllo richiede il nome file originale del client, che POST /v1/office non ha modo di conoscere dalla sola richiesta - non scatta mai via HTTP. Scatta invece dalla CLI patu, che conosce il percorso reale. |
Errori
400 | documento office non valido, oppure uno dei rifiuti sopra |
413 | caricamento superiore alla dimensione massima del piano, oppure il documento supera il budget di dimensione decompressa. Non conta ai fini della quota. |
429 | la quota mensile verrebbe superata. Non conta ai fini della quota. |
# optimize a document (sync if <= 10 MB)
curl -X POST "https://patu.dev/v1/office" \
-H "X-Api-Key: $PATU_KEY" \
--data-binary @report.docx -o report.optimized.docx
Ottimizzazione PDF
POST /v1/pdf ottimizza un intero PDF e restituisce un PDF. Le immagini JPEG incorporate passano per la stessa ricerca di qualità di /v1/compress (un JPEG resta un JPEG, un'immagine in scala di grigi resta in scala di grigi), e i flussi codificati in modo inefficiente (ASCII85, RunLength, LZW, Flate debole) vengono ricompressi senza perdita. Testo, font, disegni vettoriali, link e moduli non vengono toccati; le immagini JPEG 2000, JBIG2 e CCITT (tipiche delle scansioni) restano come sono. Prima di inviare il risultato, ogni pagina viene renderizzata e confrontata con l'originale; se una pagina diverge, o se nulla si è ridotto, il documento originale torna invariato con X-Patu-Passthrough: true. target (ssimulacra2, predefinito 85) fissa l'obiettivo di qualità delle immagini. Un PDF inviato a POST /v1/compress segue la stessa elaborazione, in linea fino a 10 MB, e torna anch'esso come PDF.
La quota è un'unità per ogni JPEG incorporato effettivamente ricodificato; il passaggio senza perdita sui flussi è gratuito. Una richiesta respinta per quota, dimensione o uno dei rifiuti seguenti non consuma nulla.
Sincrono e asincrono
I corpi fino a 10 MB (10.485.760 byte) vengono ottimizzati in linea e restituiti direttamente nella risposta. I corpi più grandi vengono messi in coda e ricevono un 202:
{ "id": "a1b2c3d4e5f67890", "status_url": "/v1/pdf/a1b2c3d4e5f67890" }
Interrogate GET /v1/pdf/{id}: 202 finché il lavoro è in corso, 200 con il PDF ottimizzato quando termina. I risultati asincroni scadono 24 ore dopo il completamento. Un riavvio del pod scarta ogni lavoro ancora in attesa in memoria; il suo GET risponde allora 404 - reinviate la richiesta originale.
Rifiuti
Un documento viene rifiutato per intero, prima di qualsiasi codifica, quando corrisponde a uno di questi casi; ciascuno ha il proprio messaggio 400:
| Motivo | Dettaglio |
|---|---|
| Cifrato | la riscrittura eliminerebbe o romperebbe la cifratura |
| Firmato digitalmente | qualsiasi modifica ai byte invalida la firma |
| PDF/A | la conformità d'archivio non sopravvivrebbe alla riscrittura senza controllo |
Errori
400 | PDF illeggibile, una struttura che Patu non può riscrivere in sicurezza, o uno dei rifiuti sopra |
413 | caricamento oltre la dimensione massima del piano; su /v1/compress, un PDF oltre 10 MB (usate /v1/pdf). Non conta nella quota. |
429 | la quota mensile verrebbe superata. Non conta nella quota. |
# optimize a PDF (sync if <= 10 MB)
curl -X POST "https://patu.dev/v1/pdf" \
-H "X-Api-Key: $PATU_KEY" \
--data-binary @invoice.pdf -o invoice.optimized.pdf
POST /v1/compress/batch
Comprimi molte immagini in un'unica richiesta (solo modalità archiviata). Invia multipart/form-data con una parte file per ogni immagine; widths, formats, target e async si applicano a tutte le parti.
{
"results": [
{ "id": "…", "original": {…}, "variants": [{…}], "created_at": "…" },
{ "error": "image exceeds the pixel limit", "status": 413 }
]
}
Uno slot per ogni immagine in input, in ordine. Un'immagine non valida fa fallire solo il proprio slot, non l'intero batch. Con async=1 la risposta è 202 e ogni slot è un manifest in sospeso (i suoi URL rispondono 202 finché non è codificato). Ogni immagine conta come un'unità di quota mensile; l'intero batch conta come una sola unità di rate limit. Limiti: al massimo PATU_BATCH_MAX (20) parti e una dimensione totale del payload limitata per richiesta.
GET /v1/asset/{id}
Recupera un asset archiviato tramite negoziazione del contenuto. La risposta include ETag, Vary: Accept e un Cache-Control cacheable.
| Forma | Restituisce |
|---|---|
GET /v1/asset/{id} | La larghezza più grande, nel miglior formato consentito dall'intestazione Accept (preferenza AVIF > WebP > JPEG). |
GET /v1/asset/{id}?w=768 | La larghezza archiviata più piccola ≥ 768 (o la più grande disponibile se nessuna soddisfa la condizione), negoziata per formato. |
GET /v1/asset/{id}/{width}.{ext} | Una variante esatta, ad es. /v1/asset/{id}/768.avif. Immutabile, con cache aggressiva. |
La negoziazione segue l'intestazione Accept (RFC 9110, incluse le esclusioni q=0): Accept: image/jpeg restituisce JPEG, image/avif;q=0 salta AVIF, e */* o l'assenza di intestazione restituisce il migliore (AVIF). Per un controllo deterministico del formato indipendente dal client, usa gli URL espliciti {width}.{ext} (ad es. nei tag <source type>).
Altri endpoint
GET /v1/stats | Autenticato. I seed di qualità per formato attuali del modulo di apprendimento, i punteggi attesi e i conteggi dei campioni. |
GET /healthz, GET /readyz | Liveness e readiness. Nessuna autenticazione. |
GET /metrics | Metriche Prometheus (conteggio richieste per classe/formato/stato, istogramma di latenza, byte risparmiati). |
Limiti e backpressure
I limiti per chiave impediscono a un singolo tenant di saturare il servizio. I valori sottostanti sono quelli del piano Free; i piani a pagamento li aumentano. Rispetta Retry-After e ti regolerai in modo ottimale.
| Limite | Piano Free | In caso di superamento |
|---|---|---|
| Frequenza | 60 req/min per chiave | 429 + Retry-After |
| Quota giornaliera | 25 req/giorno per chiave | 429 + Retry-After (secondi fino alle 00:00 UTC) |
| Quota mensile | 750 req/mese per chiave | 429 + Retry-After (secondi fino al giorno 1, 00:00 UTC) |
| Codifiche simultanee | 4 in corso | 503 + Retry-After: 2, riprova, non considerarlo un errore |
Entrambe le finestre di quota si azzerano alle 00:00 UTC (quella giornaliera ogni notte, quella mensile il giorno 1). Una richiesta rifiutata con 429 o 413 non conta ai fini della quota. Un 503 significa che gli encoder sono momentaneamente saturi, non che la richiesta fosse errata: attendi Retry-After secondi e reinvia. Gli hit di cache e i reinvii di immagini già archiviate non sono mai soggetti a rate limit da parte dell'encoder. widths non viene mai ingrandita oltre la larghezza sorgente, quindi una richiesta per una larghezza maggiore dell'originale restituisce invece la larghezza originale.
Errori
401 | X-Api-Key mancante o non valida |
400 | immagine non supportata o corrotta |
413 | payload superiore a 25 MB (25,000,000 byte), immagine superiore a 25,000,000 pixel, oppure una clip animata oltre il limite di frame/pixel per frame. Non conta ai fini della quota. |
429 | quota di frequenza, giornaliera o mensile superata, vedi Retry-After. Non conta ai fini della quota. |
503 | encoder momentaneamente saturi, riprova dopo Retry-After |
501 | modalità archiviata richiesta ma lo storage non è configurato |
I corpi di errore sono in JSON: {"error": "..."}.