---
seo_title: URLs de téléchargement signées et rétention | EnConvert
meta_desc: Chaque sortie EnConvert est livrée sous forme d'URL signée à durée limitée. Ce que l'URL contient, combien de temps elle vit, et comment recevoir les octets.
keywords: expiration url présignée, url de téléchargement signée api, paramètre direct_download, fenêtre de rétention des fichiers, url présignée s3 15 minutes, re-signer une url de téléchargement, livraison des fichiers convertis api, erreur 410 gone artefact expiré
---

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

<div class="alert alert-warning">
<strong>Le lien est un identifiant au porteur.</strong> Quiconque possède l'URL peut télécharger ce fichier jusqu'à son expiration. Il n'y a pas de seconde vérification de la clé API. Traitez une URL signée comme un mot de passe d'une durée de vie de 15 minutes : ne la journalisez pas, ne la mettez pas dans un gestionnaire de tickets public et ne la collez pas dans un canal partagé.
</div>

---

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

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.

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

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](/fr/docs/endpoints/perceive.md). 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=markdown` diffuse un artefact d'une opération passée. `output` est obligatoire lorsque l'opération a produit plus d'un artefact, et un nom inconnu renvoie `404`.
- `GET /v2/perceive/batch/{job_id}?direct_download=true` diffuse le ZIP du lot pour les lots en `output_mode: "zip"` dont l'archive est prête, et répond `400` sinon.

`POST /v2/perceive/batch` rejette `direct_download` avec `422`. Définissez `output_mode` sur `"zip"` et téléchargez l'archive à la place.

<div class="alert alert-info">
<strong>direct_download ne contourne pas le stockage.</strong> L'artefact est d'abord téléversé, puis relu et diffusé vers vous. C'est pourquoi un artefact au-delà de sa fenêtre de rétention répond <code>410 Gone</code> 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.
</div>

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

<div class="alert alert-info">
<strong>Comportement avec une clé publique :</strong> Pour les clés publiques et dashboard, <code>direct_download</code> est forcé à <code>true</code>, mais la réponse est un objet JSON contenant une <code>presigned_url</code> (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.
</div>

Sur un endpoint URL, une clé privée avec `direct_download=true` renvoie les octets bruts du fichier :

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

Restrictions :

- `direct_download` ne peut pas être utilisé avec `async_mode: true` (renvoie `400`)
- `direct_download` ne peut pas être utilisé avec plusieurs URL (renvoie `400`)

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](/fr/docs/concepts/sync-and-async.md).

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

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