---
seo_title: API URL vers PDF | Convertir des pages web en PDF | EnConvert
meta_desc: Convertissez une URL en PDF via POST /v1/convert/url-to-pdf. Gère lazy loading, bannières de cookies, auth ; sync ou async renvoie URLs présignées ou octets bruts.
keywords: convertir url en pdf api, api pour convertir une page web en pdf, convertir un site web en pdf api, api rest html vers pdf, alternative à puppeteer api, enregistrer une page web en pdf api, api pdf pleine page, conversion url vers pdf en masse api
---

# API URL vers PDF

L'endpoint `POST /v1/convert/url-to-pdf` convertit toute URL publiquement accessible en un document PDF haute fidélité. Il prend en charge le rendu continu sur une seule page ou une sortie paginée avec des tailles de page personnalisées, ainsi que la gestion du lazy loading, la fermeture des bannières de cookies, l'authentification HTTP Basic, l'injection de cookies et les en-têtes personnalisés. Exécutez les conversions de façon synchrone pour obtenir une URL de téléchargement présignée ou des octets PDF bruts, ou utilisez le mode asynchrone pour convertir plusieurs URLs en lot avec des notifications par webhook et par e-mail.

---

## Endpoint

```
POST /v1/convert/url-to-pdf
```

**Content-Type :** `application/json`

---

## Authentification

Cet endpoint prend en charge l'authentification par clé privée et par clé publique.

### Clé privée

Incluez votre clé secrète dans l'en-tête `X-API-Key`. Utilisez cette méthode pour les appels serveur à serveur où la clé n'est jamais exposée au client.

```
X-API-Key: sk_your_private_key
```

### Clé publique avec JWT

Pour une utilisation côté client, générez d'abord un token JWT à l'aide de votre clé publique, puis transmettez-le comme token Bearer.

**Étape 1 -- Obtenir un token :**

```
POST /v1/auth/token
X-API-Key: pk_your_public_key
```

**Étape 2 -- Utiliser le token :**

```
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```

<div class="alert alert-info">
<strong>Remarque :</strong> Les requêtes par clé publique sont limitées à une seule URL, au mode synchrone et au téléchargement direct. Le mode asynchrone, le traitement par lot, les webhooks et les notifications par e-mail ne sont pas disponibles avec les clés publiques.
</div>

---

## Paramètres de la requête

### Paramètres de premier niveau

| Parameter | Type | Required | Default | Description | Plan Gating |
|-----------|------|----------|---------|-------------|-------------|
| `url` | `string` ou `string[]` | Oui | -- | Une chaîne URL unique ou un tableau d'URLs à convertir. Plusieurs URLs nécessitent le mode asynchrone. | -- |
| `async_mode` | `boolean` | Non | `false` | Exécute la conversion de façon asynchrone. Renvoie immédiatement un `batch_id` pour l'interrogation (polling). Obligatoire pour un traitement par lot (plusieurs URLs). | Nécessite l'accès asynchrone |
| `direct_download` | `boolean` | Non | `false` | Renvoie les octets PDF bruts dans le corps de la réponse au lieu d'une réponse JSON contenant une URL présignée. Forcé à `true` pour les clés publiques. Incompatible avec `async_mode` et plusieurs URLs. | -- |
| `output_format` | `boolean` | Non | `false` | Lorsque `true` avec plusieurs URLs, regroupe tous les PDF de sortie dans une seule archive ZIP. Nécessite plusieurs URLs. | Nécessite l'accès à la sortie ZIP |
| `output_filename` | `string` | Non | Généré automatiquement | Nom de fichier personnalisé pour le fichier de sortie. L'extension `.pdf` est ajoutée automatiquement. Format par défaut : `{domain}_{timestamp}.pdf`. | -- |
| `job_id` | `string` | Non | -- | ID de job fourni par le client pour la récupération après timeout. **Clés publiques uniquement.** Lorsqu'une conversion synchrone dépasse les limites de timeout du reverse proxy (60-120s sur les sites lourds), le client peut interroger `GET /v1/convert/status/{job_id}` pour récupérer le résultat après coup. Ignoré pour les clés privées. | -- |
| `notification_email` | `string` | Non | E-mail du propriétaire du projet | Adresse e-mail à notifier lorsqu'un job asynchrone se termine. Clés privées uniquement. | -- |
| `callback_url` | `string` | Non | -- | URL du webhook qui reçoit une requête POST lorsque la conversion se termine. Clés privées uniquement. | Nécessite l'accès aux webhooks |

### Paramètres du navigateur et du rendu

| Parameter | Type | Required | Default | Description | Plan Gating |
|-----------|------|----------|---------|-------------|-------------|
| `viewport_width` | `integer` | Non | `1920` | Largeur de la fenêtre d'affichage (viewport) du navigateur, en pixels. | -- |
| `viewport_height` | `integer` | Non | `1080` | Hauteur de la fenêtre d'affichage (viewport) du navigateur, en pixels. | -- |
| `single_page` | `boolean` | Non | `true` | `true` restitue la page entière comme une seule page PDF continue. `false` produit une sortie paginée utilisant la taille de page de `pdf_options`. | -- |
| `load_media` | `boolean` | Non | `true` | Attend le chargement complet de toutes les images et vidéos avant la conversion. Lorsque `false`, la conversion est plus rapide mais les médias peuvent apparaître comme des espaces réservés. | -- |
| `enable_scroll` | `boolean` | Non | `true` | Fait défiler la page de haut en bas pour déclencher le contenu à chargement différé (lazy loading) (chargeurs basés sur IntersectionObserver). | -- |
| `handle_sticky_header` | `boolean` | Non | `true` | Détecte les en-têtes collants/fixes et fait défiler vers le haut avant la capture afin que l'en-tête s'affiche correctement au début du PDF. | -- |
| `handle_cookies` | `boolean` | Non | `true` | Ferme automatiquement les bannières de consentement aux cookies (OneTrust, Cookiebot, Didomi, Usercentrics, et bannières génériques). | -- |
| `wait_for_images` | `boolean` | Non | `true` | Attend que tous les éléments `<img>` aient fini de charger (timeout de 5 secondes par image). | -- |
| `wait_for_selector` | `string` | Non | `null` | Sélecteur CSS à attendre avant la capture. Renvoie `422` s'il n'apparaît jamais dans le délai `wait_for_selector_timeout`. Utile pour les SPA qui hydratent le contenu après le chargement. | -- |
| `wait_for_selector_timeout` | `integer` | Non | `10000` | Millisecondes d'attente pour `wait_for_selector` (maximum `60000`). | -- |
| `block_ads` | `boolean` | Non | `false` | Interrompt les requêtes vers les domaines connus de publicité/traçage afin qu'ils ne se chargent jamais, ne s'affichent jamais et ne ralentissent pas la capture. | -- |
| `block_media` | `boolean` | Non | `false` | Interrompt entièrement les requêtes d'images et audio/vidéo pour un rendu plus rapide et plus léger. Contrairement à `load_media` (qui contrôle uniquement l'attente), cela empêche totalement le téléchargement des médias. | -- |

### Authentification et requêtes personnalisées

| Parameter | Type | Required | Default | Description | Plan Gating |
|-----------|------|----------|---------|-------------|-------------|
| `auth` | `object` | Non | `null` | Identifiants HTTP Basic Auth pour l'URL cible. Format : `{"username": "...", "password": "..."}`. Ne peut pas être utilisé en même temps qu'un en-tête personnalisé `Authorization`. | Nécessite l'accès à l'authentification basique |
| `cookies` | `array` | Non | `null` | Tableau d'objets cookie à injecter avant la navigation. Maximum 50 cookies. Chaque cookie doit avoir `name`, `value`, et soit `domain` soit `url`. | Nécessite l'accès à l'authentification basique |
| `headers` | `object` | Non | `null` | Dictionnaire d'en-têtes HTTP personnalisés envoyés avec chaque requête vers l'URL cible. Maximum 20 en-têtes. En-têtes bloqués : `host`, `content-length`, `transfer-encoding`, `connection`, `upgrade`, `te`, `trailer`. | Nécessite l'accès à l'authentification basique |

### Options PDF

Transmettez ces éléments dans un objet `pdf_options` du corps de la requête.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page_size` | `string` | `"A4"` | Taille de page nommée. Ignorée lorsque `page_width` et `page_height` sont tous deux définis. |
| `page_width` | `float` | `null` | Largeur de page personnalisée en millimètres. Doit être positive. `page_width` et `page_height` doivent être définis ensemble. |
| `page_height` | `float` | `null` | Hauteur de page personnalisée en millimètres. Doit être positive. Les deux doivent être définis ensemble. |
| `orientation` | `string` | `"portrait"` | `"portrait"` ou `"landscape"`. Inverse la largeur et la hauteur lorsque défini sur landscape. |
| `margins` | `object` | `{"top": 10, "bottom": 10, "left": 10, "right": 10}` | Marges de page en millimètres. Toutes les valeurs doivent être non négatives. |
| `scale` | `float` | `1.0` | Facteur d'échelle du contenu. Plage : `0.1` à `2.0`. Appliqué uniquement en mode paginé (`single_page=false`). |
| `grayscale` | `boolean` | `false` | Convertit le PDF de sortie en niveaux de gris via post-traitement. |
| `header` | `object` | `null` | En-tête de page pour le mode paginé. Format : `{"content": "<html>", "height": 15}`. Contenu limité à 2000 caractères max. Hauteur en mm. |
| `footer` | `object` | `null` | Pied de page pour le mode paginé. Même format que l'en-tête. |

**Tailles de page prises en charge :** `A0`, `A1`, `A2`, `A3`, `A4`, `A5`, `A6`, `B0`, `B1`, `B2`, `B3`, `B4`, `B5`, `Letter`, `Legal`, `Tabloid`, `Ledger`

**Variables de modèle pour en-tête/pied de page :** `{{page}}`, `{{total_pages}}`, `{{date}}`, `{{title}}`, `{{url}}`

<div class="alert alert-info">
<strong>Remarque :</strong> Les en-têtes et pieds de page ne sont restitués qu'en mode paginé (<code>single_page=false</code>). Ils n'ont aucun effet en mode continu sur une seule page.
</div>

---

## Schéma de l'objet cookie

Chaque élément du tableau `cookies` doit respecter cette structure :

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `name` | `string` | Oui | -- | Nom du cookie. |
| `value` | `string` | Oui | -- | Valeur du cookie. |
| `domain` | `string` | Conditionnel | -- | Domaine du cookie. `domain` ou `url` doit être fourni. |
| `url` | `string` | Conditionnel | -- | URL à associer au cookie. `domain` ou `url` doit être fourni. |
| `path` | `string` | Non | `"/"` | Chemin du cookie. Par défaut `"/"` lorsque `domain` est défini. |

---

## Réponse

### Synchrone avec téléchargement direct (`direct_download=true`)

**Clé privée** -- renvoie les octets PDF bruts :

```
HTTP 200 OK
Content-Type: application/pdf
Content-Disposition: inline; filename="example_20260404_123456789.pdf"
X-Object-Key: env/files/{project_id}/url-to-pdf/example_20260404_123456789.pdf
X-File-Size: 123456
X-Conversion-Time: 12.5
X-Filename: example_20260404_123456789.pdf

(binary PDF data)
```

**Clé publique** -- renvoie du JSON avec une URL présignée :

```json
{
    "presigned_url": "https://spaces.example.com/...",
    "object_key": "env/files/{project_id}/url-to-pdf/example_20260404_123456789.pdf",
    "filename": "example_20260404_123456789.pdf",
    "file_size": 123456,
    "conversion_time_seconds": 12.5,
    "job_id": "client-provided-id"
}
```

### Synchrone sans téléchargement direct (`direct_download=false`)

Disponible uniquement avec les clés privées.

```json
{
    "presigned_url": "https://spaces.example.com/...",
    "object_key": "env/files/{project_id}/url-to-pdf/example_20260404_123456789.pdf",
    "filename": "example_20260404_123456789.pdf",
    "file_size": 123456,
    "conversion_time_seconds": 12.5
}
```

### Mode asynchrone

Renvoie immédiatement un `batch_id` pour l'interrogation (polling).

```
HTTP 202 Accepted
```

```json
{
    "status": "processing",
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "url_count": 5,
    "output_format": "individual"
}
```

Lorsque `output_format=true` (regroupement ZIP) :

```json
{
    "status": "processing",
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "url_count": 5,
    "output_format": "zip"
}
```

### Interrogation du statut du job (clés publiques uniquement)

L'endpoint d'interrogation du statut est conçu pour la **récupération après timeout des clés publiques**. Lorsqu'une conversion synchrone prend plus de temps que le timeout du reverse proxy (généralement 60s), le client peut récupérer le résultat en interrogeant avec le `job_id` fourni dans la requête d'origine.

```
GET /v1/convert/status/{job_id}
Authorization: Bearer <jwt_token>
```

| Status | Response |
|--------|----------|
| En cours | `{"status": "processing"}` |
| Succès | `{"status": "success", "presigned_url": "...", "object_key": "..."}` |
| Échec | `{"status": "failed", "error": "..."}` |

### Interrogation du statut de lot (clés privées uniquement) {: #batch-status-polling-private-keys-only }

Pour les jobs de lot asynchrones, interrogez l'endpoint de statut de lot avec le `batch_id` renvoyé dans la réponse 202.

```
GET /v1/convert/batch/{batch_id}
X-API-Key: sk_your_private_key
```

**Réponse :**

```json
{
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "processing",
    "total": 5,
    "completed": 3,
    "failed": 1,
    "in_progress": 1,
    "output_mode": "individual",
    "zip_download_url": null,
    "items": [
        {
            "source_url": "https://example.com/page1",
            "status": "Success",
            "download_url": "https://spaces.example.com/...",
            "output_file_size": 102400,
            "duration": "2.34"
        },
        {
            "source_url": "https://example.com/page2",
            "status": "Failed",
            "download_url": null,
            "output_file_size": null,
            "duration": "0.87"
        },
        {
            "source_url": "https://example.com/page3",
            "status": "In Progress",
            "download_url": null,
            "output_file_size": null,
            "duration": null
        }
    ]
}
```

**Valeurs de statut de lot :**

| Status | Meaning |
|--------|---------|
| `processing` | Au moins une URL est encore en cours de conversion |
| `completed` | Toutes les URLs ont été converties avec succès |
| `partial` | Toutes les URLs sont terminées, mais certaines ont échoué |
| `failed` | Toutes les URLs ont échoué |

Lorsque `output_mode` vaut `"zip"`, une seule `zip_download_url` est fournie à la place des valeurs `download_url` par élément.

### Payload du callback webhook

Lorsqu'un `callback_url` est fourni, EnConvert envoie une requête POST à cette URL une fois la conversion terminée.

**Job avec une seule URL :**

```json
{
    "job_id": "activity_id",
    "status": "success",
    "batch_id": "...",
    "gcs_uri": "object_key",
    "filename": "example_20260404_123456789.pdf",
    "file_size": 123456
}
```

**Job de lot :**

```json
{
    "job_id": "activity_id",
    "status": "success",
    "batch_id": "...",
    "total_tasks": 10,
    "successful_tasks": 8,
    "failed_tasks": 2,
    "tasks": [
        {"url": "https://example.com/page1", "status": "success", "filename": "page1.pdf"},
        {"url": "https://example.com/page2", "status": "failed", "filename": null}
    ]
}
```

---

## Fonctionnalités

### Mode de capture nette

EnConvert gère automatiquement les obstacles courants des pages web pour produire des PDF propres :

- **Bannières de consentement aux cookies** -- Ferme automatiquement les bannières d'OneTrust, Cookiebot, Didomi, Usercentrics, et des implémentations génériques. Opère sur la page principale et les iframes. Utilise une stratégie de fermeture d'abord, puis d'acceptation.
- **Fermeture des modales et popups** -- Ferme les overlays via plusieurs stratégies : touche Échap, boutons de fermeture ARIA, boutons de fermeture basés sur des classes, et boutons de dialogue basés sur des rôles.
- **Révélation des animations au scroll** -- Force la visibilité des éléments masqués par des bibliothèques d'animation déclenchées au scroll, notamment WOW.js, AOS, ScrollReveal et GSAP ScrollTrigger.
- **Nettoyage des menus déroulants** -- Ferme tous les menus déroulants ouverts et convertit les éléments de bouton de navigation en véritables liens ancre afin qu'ils restent cliquables dans le PDF.

### Taille et dimensions de page

- **Mode page unique** (par défaut) : la page web entière est restituée comme une seule page PDF continue. La hauteur est calculée dynamiquement à partir du contenu réel via un parcours du DOM.
- **Mode paginé** (`single_page=false`) : la sortie utilise les `page_size`, `orientation` et `margins` configurés. Prend en charge 18 tailles nommées, de A0 à Ledger, ou des dimensions personnalisées en millimètres.

### Authentification HTTP Basic

Transmettez `auth` avec `username` et `password` pour convertir des pages protégées par une authentification HTTP Basic. Les identifiants sont envoyés comme identifiants HTTP avec chaque requête vers la page cible.

```json
{
    "url": "https://staging.example.com/report",
    "auth": {
        "username": "admin",
        "password": "secret"
    }
}
```

### Injection de cookies

Injectez jusqu'à 50 cookies avant le chargement de la page. Utile pour convertir des pages nécessitant une session active ou des préférences utilisateur spécifiques.

```json
{
    "url": "https://example.com/dashboard",
    "cookies": [
        {"name": "session_id", "value": "abc123", "domain": "example.com"},
        {"name": "locale", "value": "en-US", "domain": "example.com"}
    ]
}
```

### En-têtes personnalisés

Envoyez jusqu'à 20 en-têtes HTTP personnalisés avec chaque requête vers la page cible. Utile pour transmettre des tokens API, des user agents personnalisés, ou d'autres métadonnées de requête.

```json
{
    "url": "https://example.com/report",
    "headers": {
        "X-Custom-Token": "my-token-value",
        "Accept-Language": "en-US"
    }
}
```

### Chargement différé des images (lazy loading)

Lorsque `load_media` et `enable_scroll` sont activés (tous deux à `true` par défaut), le convertisseur :

1. Fait défiler toute la page lentement (120px toutes les 90ms) pour déclencher les chargeurs différés basés sur IntersectionObserver
2. Attend que tous les éléments `<img>` déclenchent leur événement `onload` (timeout de 5 secondes par image)
3. Attend 500ms pour la stabilisation de la mise en page après le chargement de toutes les images

Définissez `load_media=false` pour une conversion plus rapide si la fidélité des médias n'est pas critique -- le convertisseur utilisera un défilement rapide (300px toutes les 30ms) et ajoutera des styles d'espace réservé pour les images non chargées.

### Gestion des en-têtes collants

Lorsque `handle_sticky_header` est activé (`true` par défaut), le convertisseur détecte les éléments en position fixe et collante qui semblent être des en-têtes (via des balises sémantiques, des rôles ARIA et des motifs de noms de classe courants), puis fait défiler vers le haut de la page avant la capture afin que l'en-tête s'affiche correctement au début du PDF.

### En-têtes et pieds de page

Ajoutez des en-têtes et pieds de page répétitifs en mode paginé avec du contenu HTML et des variables de modèle :

```json
{
    "url": "https://example.com/report",
    "single_page": false,
    "pdf_options": {
        "page_size": "A4",
        "header": {
            "content": "<div style='font-size:10px;text-align:center;width:100%'>Confidential Report</div>",
            "height": 15
        },
        "footer": {
            "content": "<div style='font-size:9px;text-align:center;width:100%'>Page {{page}} of {{total_pages}}</div>",
            "height": 10
        }
    }
}
```

### Sortie en niveaux de gris

Définissez `pdf_options.grayscale` sur `true` pour convertir le PDF final en niveaux de gris via un post-traitement Ghostscript.

### Fonctionnalités de rendu supplémentaires

- **Normalisation des unités de viewport** -- Convertit les unités CSS `vh`, `svh`, `lvh`, `dvh` en valeurs de pixels fixes pour éviter les problèmes de mise en page lors du rendu d'impression.
- **Mode furtif** -- Utilise le masquage d'empreinte de navigateur pour éviter la détection de bots sur les pages protégées.
- **Interception des popups** -- Ferme automatiquement tout nouvel onglet de navigateur ou popup déclenché par la page.
- **Contournement CSP** -- Gère les restrictions de Content Security Policy et Trusted Types qui bloqueraient sinon la conversion.

---

## Restrictions selon le plan d'abonnement

| Feature | Founding | Indie | Studio | Enterprise |
|---------|------|---------|-----|------------|
| Conversion de base (URL unique, synchrone) | Oui | Oui | Oui | Oui |
| `pdf_options` personnalisés | Oui | Oui | Oui | Oui |
| Options de viewport et de rendu | Oui | Oui | Oui | Oui |
| Mode asynchrone | Non | Oui | Oui | Oui |
| Traitement par lot (plusieurs URLs) | Non | Oui | Oui | Oui |
| Regroupement de sortie ZIP | Non | Non | Oui | Oui |
| Callbacks webhook | Non | Non | Oui | Oui |
| HTTP Basic Auth | Non | Oui | Oui | Oui |
| Injection de cookies | Non | Oui | Oui | Oui |
| En-têtes personnalisés | Non | Oui | Oui | Oui |
| Conversions mensuelles | 100 | Selon le plan | Selon le plan | Illimité |
| Limite de taille de lot | 0 | Selon le plan | Selon le plan | Illimité |
| Rétention des fichiers | 1 heure | Selon le plan | Selon le plan | Selon le plan |

---

## Mode asynchrone

Le mode asynchrone est utile pour les conversions longues ou lors de la conversion de plusieurs URLs.

### Fonctionnement

1. Envoyez une requête avec `async_mode=true` (ou transmettez plusieurs URLs, ce qui active automatiquement le mode asynchrone).
2. L'API renvoie immédiatement HTTP 202 avec un `batch_id` et un `url_count`.
3. Chaque URL est convertie en arrière-plan, téléversée vers le stockage, et suivie individuellement.
4. Suivez l'achèvement via une **notification par e-mail** ou un **callback webhook**.

### Notification par e-mail

Par défaut, un e-mail d'achèvement est envoyé à l'adresse e-mail du propriétaire du projet lorsque le job asynchrone se termine. Vous pouvez remplacer cela avec `notification_email` :

```json
{
    "url": ["https://example.com/page1", "https://example.com/page2"],
    "async_mode": true,
    "notification_email": "team@example.com"
}
```

### Callback webhook

Fournissez un `callback_url` dans la requête pour recevoir une notification POST automatique lorsque le job se termine :

```json
{
    "url": ["https://example.com/page1", "https://example.com/page2"],
    "async_mode": true,
    "callback_url": "https://your-server.com/webhook/enconvert"
}
```

Le webhook est envoyé avec un timeout de 30 secondes et considère les codes HTTP 200, 201, 202 et 204 comme une livraison réussie.

---

## Traitement par lot et en masse

Convertissez plusieurs URLs en une seule requête. Nécessite le mode asynchrone et une clé privée.

### Sortie individuelle (par défaut)

Chaque URL produit un fichier PDF distinct :

```json
{
    "url": [
        "https://example.com/page1",
        "https://example.com/page2",
        "https://example.com/page3"
    ],
    "async_mode": true
}
```

### Sortie en archive ZIP

Regroupez tous les PDF dans une seule archive ZIP :

```json
{
    "url": [
        "https://example.com/page1",
        "https://example.com/page2",
        "https://example.com/page3"
    ],
    "async_mode": true,
    "output_format": true,
    "output_filename": "monthly-reports"
}
```

Le fichier ZIP est nommé `{output_filename}_{timestamp}.zip`, ou `batch_{timestamp}.zip` si aucun nom personnalisé n'est fourni.

---

## Exemples de code

### Python (clé privée)

```python
import requests

response = requests.post(
    "https://api.enconvert.com/v1/convert/url-to-pdf",
    headers={"X-API-Key": "sk_your_private_key"},
    json={
        "url": "https://example.com",
        "single_page": True,
        "pdf_options": {
            "page_size": "A4",
            "margins": {"top": 15, "bottom": 15, "left": 10, "right": 10}
        }
    }
)

data = response.json()
print(data["presigned_url"])
```

### PHP (clé privée)

```php
$ch = curl_init("https://api.enconvert.com/v1/convert/url-to-pdf");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Content-Type: application/json",
        "X-API-Key: sk_your_private_key"
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "url" => "https://example.com",
        "single_page" => true,
        "pdf_options" => [
            "page_size" => "A4",
            "margins" => ["top" => 15, "bottom" => 15, "left" => 10, "right" => 10]
        ]
    ])
]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
echo $data["presigned_url"];
```

### Node.js (clé privée)

```javascript
const response = await fetch("https://api.enconvert.com/v1/convert/url-to-pdf", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-API-Key": "sk_your_private_key"
    },
    body: JSON.stringify({
        url: "https://example.com",
        single_page: true,
        pdf_options: {
            page_size: "A4",
            margins: { top: 15, bottom: 15, left: 10, right: 10 }
        }
    })
});

const data = await response.json();
console.log(data.presigned_url);
```

### Go (clé privée)

```go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "io"
)

func main() {
    body, _ := json.Marshal(map[string]interface{}{
        "url":         "https://example.com",
        "single_page": true,
        "pdf_options": map[string]interface{}{
            "page_size": "A4",
            "margins":   map[string]int{"top": 15, "bottom": 15, "left": 10, "right": 10},
        },
    })

    req, _ := http.NewRequest("POST", "https://api.enconvert.com/v1/convert/url-to-pdf", bytes.NewBuffer(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-API-Key", "sk_your_private_key")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    respBody, _ := io.ReadAll(resp.Body)
    fmt.Println(string(respBody))
}
```

### JavaScript -- Navigateur (clé publique)

```javascript
// Step 1: Get a JWT token
const tokenRes = await fetch("https://api.enconvert.com/v1/auth/token", {
    method: "POST",
    headers: { "X-API-Key": "pk_your_public_key" }
});
const { token } = await tokenRes.json();

// Step 2: Convert URL to PDF
const convertRes = await fetch("https://api.enconvert.com/v1/convert/url-to-pdf", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${token}`
    },
    body: JSON.stringify({
        url: "https://example.com"
    })
});

const data = await convertRes.json();
// Open the PDF in a new tab
window.open(data.presigned_url, "_blank");
```

### React (clé publique)

```jsx
import { useState } from "react";

function UrlToPdf() {
    const [loading, setLoading] = useState(false);
    const [pdfUrl, setPdfUrl] = useState(null);

    async function convertUrl() {
        setLoading(true);
        try {
            // Get JWT token
            const tokenRes = await fetch("https://api.enconvert.com/v1/auth/token", {
                method: "POST",
                headers: { "X-API-Key": "pk_your_public_key" }
            });
            const { token } = await tokenRes.json();

            // Convert
            const convertRes = await fetch("https://api.enconvert.com/v1/convert/url-to-pdf", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${token}`
                },
                body: JSON.stringify({ url: "https://example.com" })
            });

            const data = await convertRes.json();
            setPdfUrl(data.presigned_url);
        } finally {
            setLoading(false);
        }
    }

    return (
        <div>
            <button onClick={convertUrl} disabled={loading}>
                {loading ? "Converting..." : "Convert to PDF"}
            </button>
            {pdfUrl && <a href={pdfUrl} target="_blank" rel="noreferrer">Download PDF</a>}
        </div>
    );
}

export default UrlToPdf;
```

---

## Réponses d'erreur

| Status | Condition |
|--------|-----------|
| `400 Bad Request` | Paramètre `url` manquant ou vide |
| `400 Bad Request` | `output_format=true` avec une seule URL (plusieurs URLs requises) |
| `400 Bad Request` | `direct_download=true` avec plusieurs URLs |
| `400 Bad Request` | `direct_download=true` avec `async_mode=true` |
| `400 Bad Request` | Objet `auth` invalide (`username` ou `password` manquant) |
| `400 Bad Request` | `cookies` invalide (n'est pas un tableau, dépasse 50 entrées, champs obligatoires manquants) |
| `400 Bad Request` | `headers` invalide (n'est pas un objet, dépasse 20 entrées, noms d'en-tête bloqués, valeurs non-string) |
| `400 Bad Request` | Conflit entre `auth` et l'en-tête personnalisé `Authorization` |
| `400 Bad Request` | Clé publique tentant d'utiliser plusieurs URLs |
| `400 Bad Request` | `pdf_options` invalide (taille de page non reconnue, échelle hors de la plage 0.1-2.0, marges négatives, contenu d'en-tête/pied de page dépassant 2000 caractères) |
| `401 Unauthorized` | Clé API / token JWT manquant ou invalide |
| `402 Payment Required` | Quota mensuel d'ops épuisé |
| `402 Payment Required` | Le lot dépasserait le quota mensuel d'ops restant |
| `402 Payment Required` | Limite de stockage atteinte |
| `403 Forbidden` | L'endpoint ne figure pas parmi les endpoints autorisés de la clé API |
| `403 Forbidden` | Fonctionnalité non disponible sur le plan actuel (asynchrone, webhook, ZIP, authentification basique) |
| `403 Forbidden` | La taille du lot dépasse la limite de lot du plan |
| `404 Not Found` | ID de job introuvable (lors de l'interrogation du statut) |
| `500 Internal Server Error` | Échec de la conversion (crash du navigateur, erreur de rendu, échec du post-traitement) |

---

## Limites

| Limit | Value |
|-------|-------|
| Timeout de navigation de page | 60 secondes |
| Timeout de chargement par image | 5 secondes |
| Timeout de fermeture des bannières de cookies | 3 secondes |
| Nombre maximum de cookies par requête | 50 |
| Nombre maximum d'en-têtes personnalisés par requête | 20 |
| Longueur du contenu d'en-tête/pied de page | 2000 caractères |
| Plage d'échelle PDF | 0.1 -- 2.0 |
| Opérations mensuelles | Selon le plan (Founding : 500) |
| Taille de lot | Selon le plan (Founding : désactivé) |
| Rétention des fichiers | Selon le plan (Founding : 1 heure) |
| Timeout de livraison webhook | 30 secondes |

---

## Questions fréquentes

### Comment convertir une page web en PDF avec une API REST ?

Envoyez une requête `POST` à `/v1/convert/url-to-pdf` avec un corps JSON contenant l'`url` à convertir, en vous authentifiant avec votre clé privée dans l'en-tête `X-API-Key` (ou un token Bearer JWT issu d'une clé publique). La réponse synchrone renvoie une `presigned_url` pour télécharger le PDF, ou des octets PDF bruts lorsque `direct_download=true`.

### Puis-je convertir plusieurs URLs en PDF en une seule requête API ?

Oui. Transmettez un tableau d'URLs dans le paramètre `url` avec `async_mode=true` (une clé privée est requise) ; l'API renvoie `HTTP 202` avec un `batch_id` que vous interrogez via `GET /v1/convert/batch/{batch_id}`. Définissez `output_format=true` pour regrouper tous les PDF dans une seule archive ZIP.

### Comment capturer une page web entière comme une seule page PDF continue ?

Le mode page unique est le mode par défaut (`single_page=true`) : la page entière est restituée comme une seule page PDF continue dont la hauteur est calculée à partir du contenu réel. Définissez `single_page=false` pour une sortie paginée avec `page_size`, `orientation`, `margins`, et des en-têtes et pieds de page optionnels via `pdf_options`.

### Pourquoi mon PDF affiche-t-il des bannières de cookies ou des images manquantes ?

La fermeture des bannières de cookies (`handle_cookies`) et la gestion du lazy loading (`enable_scroll`, `load_media`, `wait_for_images`) sont toutes définies sur `true` par défaut, couvrant OneTrust, Cookiebot, Didomi, Usercentrics, et les bannières génériques. Si vous définissez `load_media=false`, la conversion est plus rapide mais les médias peuvent apparaître comme des espaces réservés.

### Puis-je convertir en PDF une page protégée par une connexion ?

Oui. Utilisez le paramètre `auth` pour l'authentification HTTP Basic, injectez jusqu'à 50 cookies de session avec `cookies`, ou envoyez jusqu'à 20 en-têtes HTTP personnalisés avec `headers`. Ces options nécessitent l'accès à l'authentification basique sur votre plan, et `auth` ne peut pas être combiné avec un en-tête `Authorization` personnalisé.
