---
seo_title: API upload file: multipart, URL e limiti | EnConvert
meta_desc: Tutti i modi per inviare byte all'API EnConvert: upload multipart o un URL recuperato dall'API, con tetti di dimensione, gestione del 413 e regole sui nomi file.
keywords: api upload file, upload file multipart form data, errore 413 payload too large api, dimensione massima file per piano, validazione magic byte upload, nome file di output api, convertire file da url api, limiti di upload api rest
---

# 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](/it/docs/endpoints/convert/data-formats.md), [documenti](/it/docs/endpoints/convert/documents.md), [immagini](/it/docs/endpoints/convert/images.md) | Upload `multipart/form-data`, un file per richiesta | `file` |
| [Pagine web](/it/docs/endpoints/convert/web-pages.md) (`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`](/it/docs/endpoints/ingest.md) | `multipart/form-data`, molti file per job | `files` |
| [`POST /v2/perceive`](/it/docs/endpoints/perceive.md), [`POST /v2/ingest`](/it/docs/endpoints/ingest.md) | 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](/it/docs/endpoints/convert/documents/anything-to-pdf.md) e [anything-to-markdown](/it/docs/endpoints/convert/documents/anything-to-markdown.md). Per trasformare una pagina live in un PDF, chiama invece [url-to-pdf](/it/docs/endpoints/convert/web-pages/url-to-pdf.md). Per sapere quali formati accetta ciascun endpoint, vedi [formati supportati](/it/docs/reference/supported-formats.md).

---

## 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

```bash
curl -X POST https://api.enconvert.com/v1/convert/anything-to-pdf \
  -H "X-API-Key: sk_your_private_key" \
  -F "file=@quarterly-report.docx" \
  -F "direct_download=false"
```

La risposta è un JSON con un link di download pre-firmato:

```json
{
    "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](/it/docs/concepts/signed-urls.md) spiega esattamente quanto.

### Python

```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

```javascript
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:

```bash
curl -X POST https://api.enconvert.com/v2/ingest/files \
  -H "X-API-Key: sk_your_private_key" \
  -F "files=@handbook.pdf" \
  -F "files=@pricing.xlsx" \
  -F "files=@faq.docx" \
  -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](/it/docs/endpoints/ingest.md).

---

## 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:

```bash
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](/it/docs/guides/batch-processing.md).

### 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`. |

<div class="alert alert-info">
<strong>Scope delle credenziali:</strong> le credenziali dell'oggetto <code>auth</code>, e un header <code>Authorization</code> (per esempio un Bearer token) passato tramite <code>headers</code>, vengono inviate <strong>solo all'origin di destinazione</strong>, mai alle sottorisorse di terze parti richieste dalla pagina. Questo impedisce la fuga di credenziali verso host pubblicitari, di analytics o CDN.
</div>

### 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 campo `auth`);
- 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:

1. `output_filename`, se ne hai inviato uno. Se ci hai incluso l'estensione di destinazione, l'estensione viene prima rimossa, così non ottieni `report.pdf_20260405_123456789.pdf`.
2. Il nome del file caricato senza la sua estensione. `report.docx` produce `report_20260405_123456789.pdf`.
3. Per le conversioni da URL, il dominio. `https://example.com/page` produce `example_20260405_123456789.pdf`.
4. 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:

```json
{
    "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`.

<div class="alert alert-warning">
<strong>5 MB si esauriscono in fretta.</strong> Sul piano Founding un PDF scansionato di 40 pagine, o una presentazione con qualche foto a tutta pagina, di solito è già oltre la soglia. Non esiste alcun percorso di upload chunked o riprendibile: la soluzione è un file più piccolo o un piano più grande.
</div>

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](/it/docs/reference/rate-limits.md).

---

## 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`:

```json
{
    "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`:

```json
{
    "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-Type` della parte non viene mai ispezionato. Non c'è alcuna allowlist MIME in tutto il percorso di upload, quindi `application/octet-stream` va 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 `.json` che 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

1. **Controlla prima il tetto.** Un 413 costa poco all'API e molto a te, perché per guadagnartelo hai caricato l'intero body.
2. **Aspettati che l'upload sia sincrono.** `async_mode` esiste solo su `url-to-pdf`, `url-to-screenshot` e `url-to-markdown`. Gli endpoint di upload file lo ignorano e convertono sempre dentro la richiesta. Vedi [job sincroni e asincroni](/it/docs/concepts/sync-and-async.md).
3. **Invia un `job_id` generato da te.** Se un proxy davanti a te chiude la connessione prima che la conversione finisca, il lavoro si completa comunque. Fai polling su `GET /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 restituisce `409`.
4. **Metti in conto i timeout.** Una richiesta è limitata a 300 secondi end to end, dopodiché ricevi `504` con `{"error": "Request timeout"}`. Le conversioni office basate su LibreOffice hanno un proprio limite di 120 secondi, esposto anch'esso come `504`.
5. **Gestisci il `503` con `Retry-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.
6. **Per molti documenti, cambia endpoint.** `POST /v2/ingest/files` accetta fino a 200 file, restituisce subito `202` con un `job_id` e accetta un `webhook_url` così non fai mai polling. Vedi [webhook](/it/docs/guides/webhooks.md).
7. **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.
