Patu

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).

ParametroPredefinitoSignificato
modeinlineinline restituisce i byte della variante scelta; stored salva ogni variante e restituisce un manifest.
formatsavif,webp,fallbackElenco separato da virgole. fallback corrisponde a JPEG per le foto, WebP/PNG senza perdita per grafica o trasparenza.
widthsoriginaleElenco di larghezze in pixel separate da virgole. Mai ingrandita oltre la larghezza sorgente.
targetper chiaveIl 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-Formatformato di trasmissione scelto (avif/webp/jpeg, oppure js/css/woff2/svg)
X-Patu-Original-Bytes / X-Patu-Output-Bytesdimensioni di input e output
X-Patu-Encoded-Bytesper JS/CSS, la dimensione dopo che l'edge comprime la risposta (Brotli/gzip): ciò che attraversa realmente la rete
X-Patu-Latency-Mstempo di elaborazione lato server per questa richiesta, in millisecondi
X-Patu-Integritytoken Subresource Integrity (sha384-…) dei byte serviti, pronto per <script>/<link> integrity="…"
X-Patu-Scorepunteggio ssimulacra2 (misurato, oppure previsto nel percorso rapido)
X-Patu-Verifiedtrue se il punteggio è stato misurato in questa richiesta, false se previsto dal modulo di apprendimento
X-Patu-Cachehit o miss nella cache di deduplicazione in-process
X-Patu-Passthroughtrue 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-TypeCosa fa Patu
text/javascript, text/cssMinifica 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/collectionConverte in WOFF2. Subsetting opzionale dei glifi (di seguito).
font/woff2È già il formato di destinazione; servito invariato.
image/svg+xmlSanificato (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.

ParametroSi applica aSignificato
subset-unicodesfontSuddivide 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-textfontSuddivide 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.

ParametroPredefinitoSignificato
profilecompatProfilo 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-Bytesdimensioni del container in input e output
X-Patu-Images-Processednumero di immagini effettivamente ricodificate
X-Patu-Fonts-Subsetnumero di font effettivamente sottoposti a subsetting
X-Patu-Quota-Usedla quota effettivamente consumata da questa richiesta (immagini elaborate + font sottoposti a subsetting)
X-Patu-Passthroughtrue quando la ricompressione degli elementi ottimizzati non ha prodotto alcun guadagno, quindi i byte originali vengono restituiti invariati

Dimensione massima di caricamento per piano

PianoCaricamento massimo
Free25 MB
Basic25 MB
Pro50 MB
Business150 MB

Errori

400container epub non valido, oppure profile sconosciuto
413caricamento superiore alla dimensione massima del piano (sopra). Non conta ai fini della quota.
422il libro usa una cifratura DRM e non può essere ottimizzato
429la 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.

ParametroPredefinitoSignificato
profilecompatProfilo di ottimizzazione. compat è l'unico profilo accettato; qualsiasi altro valore restituisce 400.
max_widthnativaRidimensiona 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-Typesempre application/vnd.comicbook+zip, indipendentemente dal fatto che l'input fosse CBZ o CBR
X-Patu-Container-Convertedcbz, presente solo quando l'input era un archivio CBR convertito in CBZ

Errori

400container cbz/cbr non valido, oppure profile/max_width sconosciuto
413caricamento superiore alla dimensione massima del piano, oppure l'archivio supera il budget di dimensione decompressa. Non conta ai fini della quota.
429la 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:

MotivoDettaglio
Formato OLE legacyun 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 digitalmenteil 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 .xlsxrifiutato 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

400documento office non valido, oppure uno dei rifiuti sopra
413caricamento superiore alla dimensione massima del piano, oppure il documento supera il budget di dimensione decompressa. Non conta ai fini della quota.
429la 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:

MotivoDettaglio
Cifratola riscrittura eliminerebbe o romperebbe la cifratura
Firmato digitalmentequalsiasi modifica ai byte invalida la firma
PDF/Ala conformità d'archivio non sopravvivrebbe alla riscrittura senza controllo

Errori

400PDF illeggibile, una struttura che Patu non può riscrivere in sicurezza, o uno dei rifiuti sopra
413caricamento oltre la dimensione massima del piano; su /v1/compress, un PDF oltre 10 MB (usate /v1/pdf). Non conta nella quota.
429la 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.

FormaRestituisce
GET /v1/asset/{id}La larghezza più grande, nel miglior formato consentito dall'intestazione Accept (preferenza AVIF > WebP > JPEG).
GET /v1/asset/{id}?w=768La 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/statsAutenticato. I seed di qualità per formato attuali del modulo di apprendimento, i punteggi attesi e i conteggi dei campioni.
GET /healthz, GET /readyzLiveness e readiness. Nessuna autenticazione.
GET /metricsMetriche 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.

LimitePiano FreeIn caso di superamento
Frequenza60 req/min per chiave429 + Retry-After
Quota giornaliera25 req/giorno per chiave429 + Retry-After (secondi fino alle 00:00 UTC)
Quota mensile750 req/mese per chiave429 + Retry-After (secondi fino al giorno 1, 00:00 UTC)
Codifiche simultanee4 in corso503 + 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

401X-Api-Key mancante o non valida
400immagine non supportata o corrotta
413payload 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.
429quota di frequenza, giornaliera o mensile superata, vedi Retry-After. Non conta ai fini della quota.
503encoder momentaneamente saturi, riprova dopo Retry-After
501modalità archiviata richiesta ma lo storage non è configurato

I corpi di errore sono in JSON: {"error": "..."}.