---
seo_title: URL di download firmati: scadenza e retention | EnConvert
meta_desc: Ogni output EnConvert arriva come URL firmato a tempo limitato. Cosa contiene l'URL, quanto dura e come trasmettere i byte direttamente con direct_download.
keywords: scadenza url presigned, url di download firmato api, parametro direct_download, finestra di conservazione file, url presigned s3 15 minuti, rifirmare url di download, consegna output conversione, errore 410 gone artefatto scaduto
---

# URL di Download Firmati

Di default EnConvert non mette il file convertito nel body della risposta. Carica il file su object storage e restituisce un URL firmato: un normale link HTTPS che porta con sé la propria autorizzazione nella query string e smette di funzionare 15 minuti dopo essere stato emesso.

---

## Perché l'output è un link

Due motivi, entrambi pratici.

La risposta resta piccola. Una risposta di conversione è di poche centinaia di byte di JSON qualunque sia il peso dell'output, quindi il tuo client analizza un'unica forma prevedibile sia che il risultato sia un file Markdown da 4 KB, sia che sia uno ZIP da 140 MB di un intero sito. Significa anche che una risposta di stato batch può trasportare 400 risultati senza trasportare 400 file.

I byte arrivano dallo storage, non dall'API. I download sono serviti direttamente dal livello di storage, quindi un client lento che scarica un PDF di grandi dimensioni non tiene occupato un worker dell'API e non ricade negli stessi timeout di proxy che limitano una richiesta di conversione. Il link non richiede l'header `X-API-Key`, ed è questo che lo rende sicuro da passare a un browser, a un consumer di coda o a un `curl` in uno script di shell.

<div class="alert alert-warning">
<strong>Il link è una credenziale bearer.</strong> Chiunque abbia l'URL può scaricare quel file finché non scade. Non c'è un secondo controllo sull'API key. Tratta un URL firmato come una password con una vita di 15 minuti: non registrarlo nei log, non inserirlo in un issue tracker pubblico e non incollarlo in un canale condiviso.
</div>

---

## Com'è fatto l'URL

È un normale URL GET AWS SigV4 in path-style verso l'host di storage:

```
https://<region>.digitaloceanspaces.com/<bucket>/<object_key>
  ?X-Amz-Algorithm=AWS4-HMAC-SHA256
  &X-Amz-Credential=<key>%2F<date>%2F<region>%2Fs3%2Faws4_request
  &X-Amz-Date=<timestamp>
  &X-Amz-Expires=900
  &X-Amz-SignedHeaders=host
  &X-Amz-Signature=<hex>
```

L'host e il bucket dipendono dal deployment, quindi leggili dall'URL che hai ricevuto invece di scriverli a mano nel codice. La forma non cambia: una GET semplice, nessun header richiesto, `X-Amz-Expires=900`.

La chiave dell'oggetto al suo interno è deterministica e ha un namespace per progetto:

```
{env}/files/{project_id}/{endpoint}/{filename}
live/files/4127/v2-perceive/per_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7_markdown.md
```

La firma è limitata a quel prefisso. A un progetto può essere consegnata solo una firma sulle proprie chiavi, quindi un `object_key` appartenente al progetto di qualcun altro non può essere trasformato in un URL funzionante.

---

## Scadenza: 15 minuti, V1 e V2

Ogni URL firmato emesso da EnConvert vive 900 secondi. Non esiste alcun parametro di richiesta per allungarlo o accorciarlo.

| Dove compare | Campo |
|------------------|-------|
| Risposta di conversione sincrona V1 | `presigned_url` |
| Stato batch V1, per elemento | `download_url` |
| Stato batch V1, modalità ZIP | `zip_download_url` |
| Polling dello stato del job V1 | `presigned_url` |
| Perceive V2, per output | `outputs.<name>.url`, insieme a `expires_in: 900` |
| Batch perceive V2, modalità ZIP | `zip.url` |
| Ingest V2, al completamento | `output_url` |

Gli URL firmati sono riutilizzabili, non monouso. Lo stesso URL continua a funzionare per GET ripetute finché non trascorrono i 15 minuti. Nulla lo invalida prima, e scaricarlo una volta non lo consuma.

Se ti serve il file per più di 15 minuti, scarica i byte e conservali tu. La rifirma ti dà un link nuovo, non un accesso permanente.

---

## Rifirma

Un link scaduto non è un file perso. Richiedilo di nuovo all'API e questa conia una nuova firma sullo stesso oggetto salvato:

| Job | Rifirma con |
|-----|--------------|
| Conversione V1 asincrona o batch | `GET /v1/convert/batch/{batch_id}` |
| Conversione sincrona V1 interrogata con il tuo id | `GET /v1/convert/status/{job_id}` |
| Operazione perceive V2 | `GET /v2/perceive/{operation_id}` |
| Batch perceive V2 | `GET /v2/perceive/batch/{job_id}` |
| Job ingest V2 | `GET /v2/ingest/{job_id}` |

Ognuno di questi endpoint ricostruisce gli URL dalle object key salvate a ogni chiamata. La rifirma non renderizza nulla, non converte nulla e non addebita ops. Funziona finché l'oggetto è ancora nello storage, che è l'altro orologio di questa pagina.

Un comportamento da gestire nel codice: se la firma non può essere prodotta, il campo torna `null` invece di far fallire la richiesta. Un polling di stato non restituisce mai un 500 a causa di una chiave obsoleta. Quindi controlla che `url`, `download_url` e `output_url` non siano `null` prima di dereferenziarli.

---

## La retention è un altro orologio

Questa è la distinzione che si sbaglia più spesso, quindi eccola in una riga per ciascuna:

- **La scadenza della firma (15 minuti)** decide per quanto tempo funziona un dato URL.
- **La retention (da ore a giorni, in base al piano)** decide per quanto tempo il file esiste.

Sono indipendenti, il che significa che entrambi i casi confusi sono reali:

**Un URL scaduto non significa che il file sia sparito.** Quindici minuti dopo una conversione il link è morto, ma l'oggetto è quasi certamente ancora lì. Interroga di nuovo il job e riottieni un link funzionante.

**Un URL valido non garantisce che il file ci sia ancora.** Se rifirmi un output del piano Founding al minuto 59 e usi il link al minuto 62, nel frattempo lo sweep di retention potrebbe aver eliminato l'oggetto. La firma è valida; l'oggetto no. Il download fallisce a livello di storage, non a livello di API.

La durata della retention è impostata per piano sul tuo abbonamento, e la finestra del piano Founding è di un'ora, abbastanza breve da incontrarla per caso durante lo sviluppo. La tabella per piano è in [Rate limit e quote](/it/docs/reference/rate-limits.md).

Altre due cose sulla retention che vale la pena sapere:

- L'eliminazione viene pianificata e poi eseguita a intervalli, quindi un file può sopravvivere alla propria finestra di qualche minuto. Non costruirci sopra. È tolleranza dello sweeper, non un periodo di grazia.
- Per i progetti con un add-on di storage non viene pianificata alcuna eliminazione. I loro output restano finché non vengono rimossi deliberatamente e contano invece sulla quota di storage dell'add-on.

Separatamente dalla finestra del tuo piano, le catture di HTML renderizzato usate per il punteggio di qualità del render vengono conservate per 90 giorni e possono essere disattivate per singola richiesta con l'header `X-Enconvert-No-Capture: true`. I file sorgente caricati su `POST /v2/ingest/files` vengono eliminati non appena il JSONL è assemblato, con un limite di sicurezza di 24 ore se qualcosa va storto prima.

---

## direct_download: trasmettere invece i byte

Se seguire un URL è un passaggio in più che non vuoi, chiedi i byte nel body della risposta.

### Su perceive V2

`direct_download: true` su `POST /v2/perceive` sostituisce completamente la busta JSON. Il body della risposta è l'artefatto, servito con il content type dell'artefatto stesso.

```bash
curl -X POST https://api.enconvert.com/v2/perceive \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/pricing",
    "outputs": ["markdown"],
    "direct_download": true
  }' \
  --output pricing.md
```

Richiede esattamente un output che produca un artefatto: `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links` o `images`, tutti descritti nella [pagina perceive](/it/docs/endpoints/perceive.md). Chiedine due e la chiamata restituisce `400` elencando ciò che hai inviato. `structured` non conta, perché è JSON inline e non un file salvato, quindi può viaggiare insieme senza infrangere la regola.

I metadati che sarebbero stati nel body JSON si spostano negli header: `X-Operation-Id`, `X-Object-Key` e `X-Cache-Hit` sempre, più `X-Render-Quality`, `X-Source-Status-Code`, `X-Content-Hash` e `X-Warnings-Count` quando quei valori esistono. La risposta porta anche `Content-Disposition: attachment`, `Content-Length` e `Cache-Control: no-transform`.

Due endpoint GET accettano `direct_download` come parametro di query:

- `GET /v2/perceive/{operation_id}?direct_download=true&output=markdown` trasmette un artefatto di un'operazione passata. `output` è obbligatorio quando l'operazione ha prodotto più di un artefatto, e un nome sconosciuto restituisce `404`.
- `GET /v2/perceive/batch/{job_id}?direct_download=true` trasmette lo ZIP del batch per i batch con `output_mode: "zip"` il cui archivio è pronto, e altrimenti risponde `400`.

`POST /v2/perceive/batch` rifiuta `direct_download` con `422`. Imposta invece `output_mode` su `"zip"` e scarica l'archivio.

<div class="alert alert-info">
<strong>direct_download non salta lo storage.</strong> L'artefatto viene prima caricato, poi riletto e trasmesso a te. Per questo un artefatto oltre la sua finestra di retention risponde <code>410 Gone</code> con un messaggio che ti dice di rieseguire la richiesta, invece di restituire silenziosamente byte vuoti. Il risparmio è un round trip, non una scrittura su storage.
</div>

### Sulle conversioni V1

Anche V1 ha un campo `direct_download`, con un comportamento diverso da quello di V2. Sugli endpoint URL decide se l'API restituisce i byte grezzi del file oppure un corpo JSON con un URL di download presigned. Sugli endpoint di upload viene accettato per parità di forma con la richiesta, ma non ha effetto: quegli endpoint rispondono sempre con il corpo JSON.

| Tipo di endpoint | Tipo di chiave | Default | Che cosa torna |
|---|---|---|---|
| Endpoint di upload file | Tutte le chiavi | `true` | JSON con `presigned_url`, qualunque valore imposti. Qui il campo è inerte. |
| Endpoint URL | Chiave privata | `false` | JSON con `presigned_url`. Imposta `true` per i byte grezzi. |
| Endpoint URL | Chiave pubblica / dashboard | `true` (forzato) | JSON con `presigned_url` |

<div class="alert alert-info">
<strong>Comportamento delle chiavi pubbliche:</strong> Per le chiavi pubbliche e dashboard, <code>direct_download</code> è forzato a <code>true</code>, ma la risposta è un oggetto JSON con un <code>presigned_url</code> (non byte grezzi). Questo evita problemi di timeout del reverse proxy con file di grandi dimensioni che possono richiedere da 60 a 120 secondi per essere convertiti.
</div>

Su un endpoint URL, una chiave privata con `direct_download=true` restituisce i byte grezzi del file:

```bash
curl -X POST https://api.enconvert.com/v1/convert/url-to-pdf \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com", "direct_download": true}' \
  --output output.pdf
```

Restrizioni:

- `direct_download` non può essere usato con `async_mode: true` (restituisce `400`)
- `direct_download` non può essere usato con più URL (restituisce `400`)

Entrambe le restrizioni derivano dallo stesso fatto: una volta che la chiamata ha risposto `202` non resta più una risposta in cui mettere i byte. I risultati asincroni si raccolgono tramite polling o webhook. Consulta [Job sincroni e asincroni](/it/docs/concepts/sync-and-async.md).

### Header di risposta

Le conversioni di file caricati, e le conversioni da URL fatte con una chiave pubblica o dashboard, ripetono i metadati della conversione negli header oltre che nel corpo JSON:

| Header | Descrizione |
|--------|-------------|
| `X-Object-Key` | Percorso di storage del file convertito |
| `X-File-Size` | Dimensione del file convertito in byte |
| `X-Conversion-Time` | Tempo impiegato per la conversione in secondi |
| `X-Filename` | Nome del file generato |

Una risposta a byte grezzi (endpoint URL, chiave privata, `direct_download: true`) porta quei quattro header più `Content-Disposition: attachment; filename="{filename}"`, `Content-Length` e `Cache-Control: no-transform`. Una conversione da URL che restituisce JSON a una chiave privata non ne porta nessuno, quindi leggi il corpo.

---

## Domande frequenti

### Quanto restano validi gli URL di download firmati di EnConvert?

Quindici minuti, ovvero 900 secondi, sia su V1 sia su V2. La busta dell'artefatto V2 lo indica esplicitamente come `expires_in: 900`. Non esiste alcun parametro per estenderlo. Recupera di nuovo il job o l'operazione per coniare un URL nuovo sullo stesso file.

### Posso usare un URL firmato più di una volta?

Sì. Gli URL firmati non sono monouso. Lo stesso link serve GET ripetute finché non trascorrono i 15 minuti, e scaricarlo non lo invalida.

### Il mio URL di download è scaduto. Il file è stato eliminato?

Quasi certamente no. La scadenza della firma e la retention del file sono due orologi separati. Interroga di nuovo il job o l'operazione (`GET /v1/convert/batch/{batch_id}`, `GET /v2/perceive/{operation_id}`, `GET /v2/ingest/{job_id}`) e ottieni un link firmato di fresco senza alcun costo in ops, purché il file sia ancora dentro la finestra di retention del tuo piano.

### Per quanto tempo EnConvert conserva i miei file convertiti?

La retention è impostata per piano, e il piano Founding conserva gli output per un'ora. I piani a pagamento li conservano più a lungo, e i progetti con un add-on di storage non vengono mai ripuliti. I numeri per piano sono in [Rate limit e quote](/it/docs/reference/rate-limits.md).

### Cosa significa un 410 Gone su un artefatto di perceive?

L'oggetto ha superato la finestra di retention del tuo piano ed è stato eliminato dallo storage. `direct_download` rilegge l'artefatto dallo storage prima di trasmetterlo, quindi un artefatto scaduto risponde `410` invece di un body vuoto. Riesegui la richiesta perceive per rigenerarlo.

### Come ottengo i byte grezzi invece di un URL di download?

Imposta `direct_download: true`. Su `POST /v2/perceive` richiede esattamente un output che produca un artefatto e il body della risposta diventa quell'artefatto. Sugli endpoint URL V1 con una chiave privata restituisce i byte del file, e non può essere combinato con `async_mode` né con più URL.
