URLs de téléchargement signées#
Par défaut, EnConvert ne place pas votre fichier converti dans le corps de la réponse. Le fichier est téléversé vers le stockage objet et l'API renvoie une URL signée : un lien HTTPS ordinaire qui porte sa propre autorisation dans la chaîne de requête et cesse de fonctionner 15 minutes après son émission.
Pourquoi la sortie est un lien#
Deux raisons, toutes deux pratiques.
La réponse reste légère. Une réponse de conversion fait quelques centaines d'octets de JSON quel que soit le poids de la sortie : votre client analyse donc une seule forme prévisible, que le résultat soit un fichier Markdown de 4 KB ou un ZIP de 140 MB contenant un site entier. Cela signifie aussi qu'une réponse de statut de lot peut porter 400 résultats sans porter 400 fichiers.
Les octets viennent du stockage, pas de l'API. Les téléchargements sont servis directement par la couche de stockage : un client lent qui récupère un PDF volumineux ne monopolise donc pas un worker de l'API et ne se heurte pas aux mêmes timeouts de proxy qui bornent une requête de conversion. Le lien ne nécessite aucun en-tête X-API-Key, ce qui permet de le confier sans risque à un navigateur, à un consommateur de file d'attente ou à un curl dans un script shell.
À quoi ressemble l'URL#
C'est une URL GET AWS SigV4 standard, en style chemin, pointant vers l'hôte de stockage :
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'hôte et le bucket dépendent du déploiement : lisez-les depuis l'URL qui vous a été fournie plutôt que de les coder en dur. La forme, elle, ne change pas : un simple GET, aucun en-tête requis, X-Amz-Expires=900.
La clé d'objet qu'elle contient est déterministe et cloisonnée par projet :
{env}/files/{project_id}/{endpoint}/{filename}
live/files/4127/v2-perceive/per_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7_markdown.md
La signature est limitée à ce préfixe. Un projet ne peut jamais recevoir de signature que sur ses propres clés : une object_key appartenant au projet de quelqu'un d'autre ne peut donc pas être transformée en URL fonctionnelle.
Expiration : 15 minutes, V1 et V2#
Chaque URL signée émise par EnConvert vit 900 secondes. Aucun paramètre de requête ne permet de la rallonger ou de la raccourcir.
| Où elle apparaît | Champ |
|---|---|
| Réponse de conversion sync V1 | presigned_url |
| Statut de lot V1, par élément | download_url |
| Statut de lot V1, mode ZIP | zip_download_url |
| Interrogation du statut d'un job V1 | presigned_url |
| Perceive V2, par sortie | outputs.<name>.url, accompagné de expires_in: 900 |
| Perceive V2 par lot, mode ZIP | zip.url |
| Ingest V2, à la fin du traitement | output_url |
Les URL signées sont réutilisables, pas à usage unique. La même URL continue de fonctionner pour des GET répétés jusqu'à l'écoulement des 15 minutes. Rien ne l'invalide plus tôt, et la télécharger une fois ne la consomme pas.
S'il vous faut le fichier plus de 15 minutes, téléchargez les octets et stockez-les vous-même. Re-signer vous donne un nouveau lien, pas un accès permanent.
Re-signature#
Un lien expiré n'est pas un fichier perdu. Redemandez à l'API et elle forge une nouvelle signature sur le même objet stocké :
| Job | Re-signer avec |
|---|---|
| Conversion V1 async ou par lot | GET /v1/convert/batch/{batch_id} |
| Conversion V1 sync interrogée par votre propre identifiant | GET /v1/convert/status/{job_id} |
| Opération perceive V2 | GET /v2/perceive/{operation_id} |
| Lot perceive V2 | GET /v2/perceive/batch/{job_id} |
| Job ingest V2 | GET /v2/ingest/{job_id} |
Chacun de ces endpoints reconstruit les URL à partir des clés d'objet persistées, à chaque appel. Re-signer ne rend rien, ne convertit rien et ne facture aucune op. Cela fonctionne tant que l'objet est encore dans le stockage, ce qui est l'autre horloge de cette page.
Un comportement à prévoir dans votre code : si la signature ne peut pas être produite, le champ revient à null plutôt que de faire échouer la requête. Une interrogation de statut ne renvoie jamais de 500 à cause d'une clé périmée. Vérifiez donc la présence de null sur url, download_url et output_url avant de les déréférencer.
La rétention est une autre horloge#
C'est la distinction sur laquelle on se trompe le plus, la voici donc en une ligne chacune :
- L'expiration de la signature (15 minutes) décide combien de temps une URL donnée fonctionne.
- La rétention (de quelques heures à quelques jours, selon le plan) décide combien de temps le fichier existe.
Elles sont indépendantes, ce qui veut dire que les deux cas déroutants sont bien réels :
Une URL expirée ne signifie pas que le fichier a disparu. Quinze minutes après une conversion, le lien est mort mais l'objet est presque certainement toujours là. Interrogez le job à nouveau et vous récupérez un lien fonctionnel.
Une URL valide ne garantit pas que le fichier est toujours là. Si vous re-signez une sortie sur le plan Founding à 59 minutes et utilisez le lien à 62 minutes, le balayage de rétention a pu supprimer l'objet entre-temps. La signature est valide ; l'objet ne l'est pas. Le téléchargement échoue au niveau de la couche de stockage, pas au niveau de l'API.
La durée de rétention est définie par plan sur votre abonnement, et la fenêtre du plan Founding est d'une heure, assez courte pour être atteinte par accident pendant le développement. Le tableau par plan se trouve dans Limites de débit et quotas.
Deux autres choses à savoir sur la rétention :
- La suppression est planifiée, puis balayée à intervalle régulier : un fichier peut donc survivre à sa fenêtre de quelques minutes. Ne construisez rien là-dessus. C'est du jeu dans le balayeur, pas un délai de grâce.
- Les projets dotés d'une option de stockage ne voient aucune suppression planifiée. Leurs sorties restent jusqu'à ce qu'elles soient retirées délibérément et comptent à la place dans le quota de stockage de l'option.
Indépendamment de la fenêtre de votre plan, les captures de HTML rendu utilisées pour la notation de la qualité de rendu sont conservées 90 jours et peuvent être désactivées par requête avec l'en-tête X-Enconvert-No-Capture: true. Les fichiers sources téléversés vers POST /v2/ingest/files sont supprimés dès que le JSONL est assemblé, avec un filet de sécurité à 24 heures si quelque chose se passe mal avant.
direct_download : recevoir les octets à la place#
Si suivre une URL est un aller-retour supplémentaire dont vous ne voulez pas, demandez les octets dans le corps de la réponse.
Sur perceive V2#
direct_download: true sur POST /v2/perceive remplace entièrement l'enveloppe JSON. Le corps de la réponse est l'artefact, servi avec le type de contenu propre à l'artefact.
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
Il exige exactement une sortie produisant un artefact : markdown, html_cleaned, html_raw, screenshot, screenshot_full_page, pdf, links ou images, toutes décrites sur la page perceive. Demandez-en deux et l'appel renvoie 400 en listant ce que vous avez envoyé. structured ne compte pas, car il s'agit de JSON en ligne plutôt que d'un fichier stocké : il peut donc accompagner la requête sans enfreindre la règle.
Les métadonnées qui auraient figuré dans le corps JSON passent dans les en-têtes : X-Operation-Id, X-Object-Key et X-Cache-Hit toujours, plus X-Render-Quality, X-Source-Status-Code, X-Content-Hash et X-Warnings-Count lorsque ces valeurs existent. La réponse porte aussi Content-Disposition: attachment, Content-Length et Cache-Control: no-transform.
Deux endpoints GET acceptent direct_download comme paramètre de requête :
GET /v2/perceive/{operation_id}?direct_download=true&output=markdowndiffuse un artefact d'une opération passée.outputest obligatoire lorsque l'opération a produit plus d'un artefact, et un nom inconnu renvoie404.GET /v2/perceive/batch/{job_id}?direct_download=truediffuse le ZIP du lot pour les lots enoutput_mode: "zip"dont l'archive est prête, et répond400sinon.
POST /v2/perceive/batch rejette direct_download avec 422. Définissez output_mode sur "zip" et téléchargez l'archive à la place.
410 Gone avec un message vous invitant à relancer la requête, plutôt que de renvoyer silencieusement des octets vides. Ce que vous économisez, c'est un aller-retour, pas une écriture dans le stockage.
Sur les conversions V1#
La V1 possède elle aussi un champ direct_download, au comportement différent de celui de la V2. Sur les endpoints URL, il décide si l'API renvoie les octets bruts du fichier ou un corps JSON contenant une URL de téléchargement présignée. Sur les endpoints de téléversement, il est accepté pour garder la même forme de requête, mais il reste sans effet : ces endpoints répondent toujours avec le corps JSON.
| Type d'endpoint | Type de clé | Valeur par défaut | Ce que vous recevez |
|---|---|---|---|
| Endpoints de téléversement de fichiers | Toutes les clés | true |
Du JSON avec presigned_url, quoi que vous mettiez. Le champ est inerte ici. |
| Endpoints URL | Clé privée | false |
Du JSON avec presigned_url. Mettez true pour les octets bruts. |
| Endpoints URL | Clé publique / dashboard | true (forcé) |
Du JSON avec presigned_url |
direct_download est forcé à true, mais la réponse est un objet JSON contenant une presigned_url (et non des octets bruts). Cela évite les problèmes de timeout du reverse-proxy avec les fichiers volumineux dont la conversion peut prendre 60 à 120 secondes.
Sur un endpoint URL, une clé privée avec direct_download=true renvoie les octets bruts du fichier :
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
Restrictions :
direct_downloadne peut pas être utilisé avecasync_mode: true(renvoie400)direct_downloadne peut pas être utilisé avec plusieurs URL (renvoie400)
Les deux restrictions découlent du même fait : il ne reste plus de réponse où mettre les octets une fois que l'appel a répondu 202. Les résultats async se récupèrent par interrogation ou par webhook. Voir Tâches synchrones et asynchrones.
En-têtes de réponse#
Les conversions de fichiers téléversés, et les conversions d'URL faites avec une clé publique ou dashboard, répètent les métadonnées de conversion dans des en-têtes en plus du corps JSON :
| En-tête | Description |
|---|---|
X-Object-Key |
Chemin de stockage du fichier converti |
X-File-Size |
Taille du fichier converti en octets |
X-Conversion-Time |
Durée de la conversion en secondes |
X-Filename |
Nom de fichier généré |
Une réponse en octets bruts (endpoint URL, clé privée, direct_download: true) porte ces quatre en-têtes plus Content-Disposition: attachment; filename="{filename}", Content-Length et Cache-Control: no-transform. Une conversion d'URL qui renvoie du JSON à une clé privée n'en porte aucun : lisez le corps.
Questions fréquentes#
Combien de temps les URL de téléchargement signées EnConvert restent-elles valides ?#
Quinze minutes, soit 900 secondes, en V1 comme en V2. L'enveloppe d'artefact V2 l'indique explicitement avec expires_in: 900. Aucun paramètre ne permet de la prolonger. Récupérez à nouveau le job ou l'opération pour forger une nouvelle URL sur le même fichier.
Puis-je utiliser une URL signée plus d'une fois ?#
Oui. Les URL signées ne sont pas à usage unique. Le même lien sert des GET répétés jusqu'à l'écoulement des 15 minutes, et le télécharger ne l'invalide pas.
Mon URL de téléchargement a expiré. Le fichier a-t-il été supprimé ?#
Presque certainement pas. L'expiration de la signature et la rétention des fichiers sont deux horloges distinctes. Interrogez à nouveau le job ou l'opération (GET /v1/convert/batch/{batch_id}, GET /v2/perceive/{operation_id}, GET /v2/ingest/{job_id}) et vous obtenez un lien fraîchement signé sans aucun coût en ops, tant que le fichier est encore dans la fenêtre de rétention de votre plan.
Combien de temps EnConvert conserve-t-il mes fichiers convertis ?#
La rétention est définie par plan, et le plan Founding conserve les sorties pendant une heure. Les plans payants les conservent plus longtemps, et les projets dotés d'une option de stockage ne sont jamais balayés. Les chiffres par plan se trouvent dans Limites de débit et quotas.
Que signifie un 410 Gone sur un artefact perceive ?#
L'objet a dépassé la fenêtre de rétention de votre plan et a été supprimé du stockage. direct_download relit l'artefact depuis le stockage avant de le diffuser : un artefact périmé répond donc 410 plutôt qu'un corps vide. Relancez la requête perceive pour le régénérer.
Comment obtenir les octets bruts au lieu d'une URL de téléchargement ?#
Définissez direct_download: true. Sur POST /v2/perceive, il exige exactement une sortie produisant un artefact et le corps de la réponse devient cet artefact. Sur les endpoints URL V1 avec une clé privée, il renvoie les octets du fichier, et il ne peut pas être combiné avec async_mode ni avec plusieurs URL.