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.


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.

Il link è una credenziale bearer. 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.

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.

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.

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

direct_download non salta lo storage. L'artefatto viene prima caricato, poi riletto e trasmesso a te. Per questo un artefatto oltre la sua finestra di retention risponde 410 Gone 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.

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
Comportamento delle chiavi pubbliche: Per le chiavi pubbliche e dashboard, direct_download è forzato a true, ma la risposta è un oggetto JSON con un presigned_url (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.

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

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.

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.

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.