Ingestione dei file#
Ci sono due modi per far arrivare dei byte a EnConvert: caricare tu stesso il file come multipart/form-data, oppure passare un url e lasciare che sia l'API a recuperare la risorsa. Quale dei due puoi usare dipende dall'endpoint, non dal tuo piano.
Quale endpoint accetta cosa#
| Famiglia di endpoint | Come arrivano i byte | Nome del campo |
|---|---|---|
| Formati dati, documenti, immagini | Upload multipart/form-data, un file per richiesta |
file |
Pagine web (url-to-pdf, url-to-screenshot, url-to-markdown, website-to-pdf, website-to-screenshot) |
Body JSON, la pagina la recupera EnConvert | url |
POST /v2/ingest/files |
multipart/form-data, molti file per job |
files |
POST /v2/perceive, POST /v2/ingest |
Body JSON, la pagina la recupera EnConvert | url |
Non esiste una terza via. Gli endpoint di upload file non recuperano un URL al posto tuo, comprese le route generiche anything-to-pdf e anything-to-markdown. Per trasformare una pagina live in un PDF, chiama invece url-to-pdf. Per sapere quali formati accetta ciascun endpoint, vedi formati supportati.
Caricare un file locale#
Il campo del form si chiama file, e ogni endpoint di upload file V1 ne accetta esattamente uno. Tutto il resto nel form è opzionale.
| Campo del form | Tipo | Descrizione |
|---|---|---|
file |
file | Il file da convertire. La sua estensione deve essere accettata dall'endpoint. |
output_filename |
string |
Nome base personalizzato per l'output. L'estensione di destinazione viene aggiunta automaticamente. |
job_id |
string |
ID del job fornito dal client per il recupero in caso di timeout. Fai polling su GET /v1/convert/status/{job_id} se la connessione cade. |
pdf_options |
string |
Stringa JSON di opzioni PDF, sugli endpoint che producono un PDF. |
direct_download |
boolean |
Accettato solo per parità di forma con gli endpoint URL. Qui non ha effetto: un upload risponde sempre con la busta JSON qui sotto, qualunque cosa invii. |
curl#
curl -X POST https://api.enconvert.com/v1/convert/anything-to-pdf \
-H "X-API-Key: sk_your_private_key" \
-F "[email protected]" \
-F "direct_download=false"
La risposta è un JSON con un link di download pre-firmato:
{
"presigned_url": "https://spaces.example.com/...signed...",
"object_key": "env/files/{project_id}/anything-to-pdf/quarterly-report_20260714_101530123.pdf",
"filename": "quarterly-report_20260714_101530123.pdf",
"file_size": 51240,
"conversion_time_seconds": 2.1,
"job_id": null
}
Gli stessi valori vengono riportati negli header di risposta X-Object-Key, X-File-Size, X-Conversion-Time e X-Filename, così puoi leggerli senza analizzare il body. Scarica il file tempestivamente: il link ha vita breve, e URL firmati spiega esattamente quanto.
Python#
import requests
with open("quarterly-report.docx", "rb") as f:
response = requests.post(
"https://api.enconvert.com/v1/convert/anything-to-pdf",
headers={"X-API-Key": "sk_your_private_key"},
files={"file": ("quarterly-report.docx", f)},
data={"direct_download": "false"},
)
response.raise_for_status()
result = response.json()
# Download the PDF from the pre-signed URL.
pdf = requests.get(result["presigned_url"]).content
with open("quarterly-report.pdf", "wb") as out:
out.write(pdf)
Node.js#
import { readFile, writeFile } from "node:fs/promises";
const form = new FormData();
form.append(
"file",
new Blob([await readFile("quarterly-report.docx")]),
"quarterly-report.docx"
);
form.append("direct_download", "false");
const response = await fetch(
"https://api.enconvert.com/v1/convert/anything-to-pdf",
{ method: "POST", headers: { "X-API-Key": "sk_your_private_key" }, body: form }
);
const result = await response.json();
const pdf = await fetch(result.presigned_url).then((r) => r.arrayBuffer());
await writeFile("quarterly-report.pdf", Buffer.from(pdf));
Molti file in una sola chiamata#
POST /v2/ingest/files è l'unico endpoint che accetta più di un file. Ripeti il campo files, fino a 200 file per job:
curl -X POST https://api.enconvert.com/v2/ingest/files \
-H "X-API-Key: sk_your_private_key" \
-F "[email protected]" \
-F "[email protected]" \
-F "[email protected]" \
-F "max_words=700" \
-F "webhook_url=https://your-app.example.com/hooks/ingest"
Ogni file viene convertito in Markdown, suddiviso in chunk e assemblato in un unico deliverable JSONL. Il job è sempre asincrono e risponde 202 Accepted con un job_id. Tutti i dettagli sono sulla pagina dell'endpoint ingest.
Lasciare che sia EnConvert a recuperare il file#
Sugli endpoint URL invii un body JSON invece di un form, e il recupero lo fa l'API:
curl -X POST https://api.enconvert.com/v1/convert/url-to-markdown \
-H "X-API-Key: sk_your_private_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/report"}'
url accetta una stringa o un array di stringhe. Un array commuta la richiesta in modalità asincrona ed è trattato in elaborazione batch.
Sorgenti dietro un login#
Tre campi opzionali permettono al recupero di trasportare credenziali. Tutti e tre richiedono un piano con accesso alla basic auth (Indie in su).
| Parametro | Tipo | Default | Descrizione |
|---|---|---|---|
auth |
object |
null |
Credenziali HTTP Basic Auth: {"username": "...", "password": "..."}. |
cookies |
array |
null |
Array di oggetti cookie iniettati prima della navigazione. Massimo 50 per richiesta. Ognuno richiede name, value e, in alternativa, domain o url. |
headers |
object |
null |
Header HTTP personalizzati inviati con le richieste. Massimo 20 per richiesta. Non possono includere header bloccati: host, content-length, transfer-encoding, connection, upgrade, te, trailer. |
auth, e un header Authorization (per esempio un Bearer token) passato tramite headers, vengono inviate solo all'origin di destinazione, mai alle sottorisorse di terze parti richieste dalla pagina. Questo impedisce la fuga di credenziali verso host pubblicitari, di analytics o CDN.
Indirizzi privati e interni#
L'url deve essere un indirizzo pubblico http:// o https://. Prima che venga recuperato qualsiasi contenuto, l'URL viene filtrato e rifiutato con 400 Bad Request quando:
- usa uno schema diverso da
http/https; - incorpora credenziali come
https://user:pass@host/(usa invece il campoauth); - punta a
localhost, a un hostname di metadata cloud o a un IP che risolve in un intervallo privato, loopback, link-local, riservato o comunque non pubblico; - usa una notazione IP non standard (ottale, esadecimale o packed-integer) che potrebbe risolversi in modo ambiguo.
Vale per l'URL seed, per ogni URL di un batch e per le pagine scoperte dagli endpoint di crawl website-to-*.
Detto chiaramente: EnConvert gira fuori dalla tua rete. Non può raggiungere http://10.0.0.5/report.docx, un hostname .internal o qualsiasi cosa che risolva solo dentro la tua VPC. O rendi il file raggiungibile dalla rete pubblica, oppure leggi tu stesso i byte e caricali.
Che fine fa il nome del tuo file#
Il nome che invii svolge due compiti.
Sceglie il convertitore. L'estensione decide quale percorso di input viene eseguito, quindi dai al file il nome giusto. Un file chiamato report senza estensione viene rifiutato da qualsiasi endpoint dotato di allowlist di estensioni.
Fa da base per il nome dell'output. Il nome del file di output è costruito così:
{base}_{YYYYMMDD_HHMMSSmmm}.{ext}
Il timestamp UTC viene sempre aggiunto in coda, così due conversioni dello stesso file non collidono mai. base viene risolto in questo ordine:
output_filename, se ne hai inviato uno. Se ci hai incluso l'estensione di destinazione, l'estensione viene prima rimossa, così non ottienireport.pdf_20260405_123456789.pdf.- Il nome del file caricato senza la sua estensione.
report.docxproducereport_20260405_123456789.pdf. - Per le conversioni da URL, il dominio.
https://example.com/pageproduceexample_20260405_123456789.pdf. - In mancanza di tutto questo, il letterale
output.
La chiave di storage viene sanificata prima che il risultato venga scritto: sopravvive solo il basename, .. viene rimosso, i caratteri <>:"|?* vengono eliminati e gli spazi diventano underscore. Carica My Report (final).docx e il PDF finisce sotto My_Report_(final)_20260405_123456789.pdf. Quel percorso ti viene restituito come object_key e ha la forma {env}/files/{project_id}/{endpoint}/{filename}.
I nomi dei file inviati a POST /v2/ingest/files sono inoltre limitati a 255 caratteri.
Il tetto di dimensione e il 413#
Il tetto di upload è per piano e vale per ogni singolo file.
| Piano | Upload massimo | Byte |
|---|---|---|
| Founding | 5 MB | 5242880 |
| Indie | 15 MB | 15728640 |
| Studio | 50 MB | 52428800 |
| Production | 150 MB | 157286400 |
| Enterprise | Negoziato | Da contratto |
La dimensione viene misurata sulla parte caricata stessa mentre il body arriva in streaming, non su un header che controlli tu, quindi anche un upload chunked senza Content-Length viene verificato allo stesso modo. Superare il limite restituisce 413 Payload Too Large prima che venga svolto qualsiasi lavoro di conversione e prima che venga addebitata qualsiasi op:
{
"detail": {
"error": "File too large",
"file_size": 10485760,
"max_size": 5242880,
"tier": "free",
"key_type": "private"
}
}
file_size e max_size sono entrambi in byte. tier è lo slug del piano (free, starter, pro, business, enterprise), non il nome visualizzato che vedi sulla pagina dei prezzi, quindi un progetto Studio riporta "tier": "pro". key_type è private, public o dashboard, con fallback a unknown.
Superare il gate della dimensione non è l'ultimo ostacolo. Una quota mensile esaurita risponde 402 Payment Required e troppe richieste in una finestra rispondono 429, entrambi descritti in rate limit e quote.
Content type e magic byte#
Gli upload passano due controlli, in quest'ordine.
1. L'allowlist delle estensioni. Ogni endpoint dichiara quali estensioni accetta. Una discordanza è 400 Bad Request:
{
"detail": "Invalid file format '.pdf' for png-to-jpeg. Allowed: .png"
}
2. Il sniff dei magic byte. I primi byte del file vengono confrontati con il gruppo dichiarato dalla sua estensione. Una discordanza ad alta confidenza è anch'essa 400:
{
"detail": "File content does not match the 'png-to-jpeg' input type."
}
È quello che ottieni se rinomini un PDF in .png e lo carichi: i byte iniziano con %PDF-, l'estensione dice PNG, e i due non concordano. Il controllo esiste perché è l'estensione a instradare la tua richiesta. Senza di esso, i byte PDF arriverebbero a un decoder di immagini e otterresti un errore opaco nelle profondità del convertitore invece di un 400 chiaro sulla soglia, e un file etichettato male di proposito finirebbe in mano a un parser che non avrebbe mai dovuto vederlo.
Due cose che questo non fa, entrambe da sapere:
- Il
Content-Typedella parte non viene mai ispezionato. Non c'è alcuna allowlist MIME in tutto il percorso di upload, quindiapplication/octet-streamva bene. L'estensione del nome file è l'unica cosa che instrada la richiesta, ed è per questo che inviare un file senza estensione fallisce. - I formati testuali non hanno una firma affidabile e saltano del tutto il sniff:
.json,.csv,.xml,.yaml,.toml,.md,.html,.svg,.txt. Un file.jsonche in realtà contiene CSV viene accettato in questa fase e fallisce più avanti, nel parser.
Le firme binarie riconosciute sono PNG, JPEG, GIF, WebP, HEIC/HEIF, PDF e il gruppo office (un contenitore ZIP per .docx/.xlsx/.pptx/ODF/EPUB, oppure il vecchio OLE2 per .doc/.xls/.ppt). Il sniff è volutamente fail-open: i byte che non riconosce passano invece di essere bloccati.
File di grandi dimensioni: la checklist#
- Controlla prima il tetto. Un 413 costa poco all'API e molto a te, perché per guadagnartelo hai caricato l'intero body.
- Aspettati che l'upload sia sincrono.
async_modeesiste solo suurl-to-pdf,url-to-screenshoteurl-to-markdown. Gli endpoint di upload file lo ignorano e convertono sempre dentro la richiesta. Vedi job sincroni e asincroni. - Invia un
job_idgenerato da te. Se un proxy davanti a te chiude la connessione prima che la conversione finisca, il lavoro si completa comunque. Fai polling suGET /v1/convert/status/{job_id}e ottieni{"status": "processing"}, poi{"status": "success", "presigned_url": ..., "object_key": ...}oppure{"status": "failed", "error": ...}. Riusare un tuo ID fa ripartire quel job; riusare l'ID di un altro progetto restituisce409. - Metti in conto i timeout. Una richiesta è limitata a 300 secondi end to end, dopodiché ricevi
504con{"error": "Request timeout"}. Le conversioni office basate su LibreOffice hanno un proprio limite di 120 secondi, esposto anch'esso come504. - Gestisci il
503conRetry-After: 10. Le conversioni di file girano dietro un gate di ammissione con una coda di attesa limitata. Quando la coda è piena la richiesta viene rifiutata subito invece di mettersi in fila, quindi riprova dopo l'intervallo indicato nell'header. - Per molti documenti, cambia endpoint.
POST /v2/ingest/filesaccetta fino a 200 file, restituisce subito202con unjob_ide accetta unwebhook_urlcosì non fai mai polling. Vedi webhook. - Scarica tempestivamente. I link di output sono firmati e scadono. Se il tuo è scaduto, rileggi l'endpoint di stato: ogni polling conia un link nuovo sullo stesso oggetto in storage.
Domande frequenti#
Quale nome di campo si aspetta l'API EnConvert per l'upload di un file?#
file, inviato come multipart/form-data, un file per richiesta, su ogni endpoint di conversione V1 che accetta un upload. L'eccezione è POST /v2/ingest/files, che usa files e ne accetta fino a 200 per job.
EnConvert può scaricare il file da un URL invece di farmelo caricare?#
Solo sugli endpoint URL (url-to-pdf, url-to-screenshot, url-to-markdown, website-to-pdf, website-to-screenshot) e sugli endpoint V2 /v2/perceive e /v2/ingest. Gli endpoint di conversione con upload file non hanno alcun parametro url. Qualsiasi URL che passi deve essere raggiungibile pubblicamente: gli indirizzi privati, di loopback, link-local e di metadata cloud vengono rifiutati con 400.
Perché il mio upload ha restituito 413 Payload Too Large?#
Il file era più grande del tetto per file del tuo piano: 5 MB su Founding, 15 MB su Indie, 50 MB su Studio e 150 MB su Production. Il body della risposta porta un oggetto detail con error, file_size, max_size, tier e key_type, così puoi mostrare al chiamante i numeri esatti.
Perché il mio upload PNG fallisce con "File content does not match"?#
I primi byte del file appartengono a un formato diverso da quello dichiarato dall'estensione, per esempio un PDF rinominato in .png. Invia il file con la sua estensione reale. I formati testuali come .json e .csv non vengono mai controllati a livello di byte, quindi questo errore compare solo per i tipi binari.
Posso caricare un file di grandi dimensioni in modo asincrono?#
Non sugli endpoint di upload file V1: convertono sempre nella richiesta. Invia un job_id generato dal client e fai polling su GET /v1/convert/status/{job_id} per sopravvivere a una connessione caduta, oppure usa POST /v2/ingest/files, che è asincrono per progettazione e può chiamare un webhook quando il job termina.