---
seo_title: API da URL a PDF | Converti Pagine Web in PDF | EnConvert
meta_desc: Converti qualsiasi URL in PDF con POST /v1/convert/url-to-pdf. Gestisce lazy loading, banner cookie e auth: sync o async restituiscono URL presigned o byte grezzi.
keywords: api per convertire url in pdf, convertire pagina web in pdf con api, api sito web in pdf, api rest html in pdf, alternativa a puppeteer per pdf, salvare pagina web come pdf tramite api, pdf a pagina intera api, conversione batch url in pdf api
---

# API da URL a PDF

L'endpoint `POST /v1/convert/url-to-pdf` converte qualsiasi URL pubblicamente accessibile in un documento PDF ad alta fedeltà. Supporta il rendering continuo a pagina singola o l'output paginato con dimensioni di pagina personalizzate, oltre alla gestione del lazy loading, alla chiusura dei banner cookie, all'HTTP Basic Auth, all'iniezione di cookie e agli header personalizzati. Esegui le conversioni in modo sincrono per ottenere un URL di download presigned o i byte grezzi del PDF, oppure usa la modalità asincrona per convertire in batch più URL con notifiche webhook ed email.

---

## Endpoint

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

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

---

## Autenticazione

Questo endpoint supporta l'autenticazione sia con chiave privata che con chiave pubblica.

### Chiave privata

Includi la tua chiave segreta nell'header `X-API-Key`. Usala per le chiamate server-to-server, dove la chiave non viene mai esposta al client.

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

### Chiave pubblica con JWT

Per l'uso lato client, genera prima un token JWT usando la tua chiave pubblica, poi passalo come token Bearer.

**Passo 1 -- Ottieni un token:**

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

**Passo 2 -- Usa il token:**

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

<div class="alert alert-info">
<strong>Nota:</strong> Le richieste con chiave pubblica sono limitate a un singolo URL, alla modalità sincrona e al download diretto. La modalità asincrona, l'elaborazione in batch, i webhook e le email di notifica non sono disponibili con le chiavi pubbliche.
</div>

---

## Parametri della richiesta

### Parametri di primo livello

| Parametro | Tipo | Obbligatorio | Predefinito | Descrizione | Limitazioni per piano |
|-----------|------|----------|---------|-------------|-------------|
| `url` | `string` o `string[]` | Sì | -- | Una singola stringa URL o un array di URL da convertire. Più URL richiedono la modalità asincrona. | -- |
| `async_mode` | `boolean` | No | `false` | Esegue la conversione in modo asincrono. Restituisce immediatamente un `batch_id` per il polling. Obbligatorio per l'elaborazione in batch (più URL). | Richiede accesso alla modalità asincrona |
| `direct_download` | `boolean` | No | `false` | Restituisce i byte grezzi del PDF nel corpo della risposta invece di una risposta JSON con un URL presigned. Forzato a `true` per le chiavi pubbliche. Incompatibile con `async_mode` e con più URL. | -- |
| `output_format` | `boolean` | No | `false` | Quando è `true` con più URL, raggruppa tutti i PDF di output in un unico archivio ZIP. Richiede più URL. | Richiede accesso all'output ZIP |
| `output_filename` | `string` | No | Generato automaticamente | Nome file personalizzato per il file di output. L'estensione `.pdf` viene aggiunta automaticamente. Formato predefinito: `{domain}_{timestamp}.pdf`. | -- |
| `job_id` | `string` | No | -- | ID del job fornito dal client per il recupero in caso di timeout. **Solo chiavi pubbliche.** Quando una conversione sincrona supera i limiti di timeout del reverse proxy (60-120s su siti pesanti), il client può interrogare `GET /v1/convert/status/{job_id}` per recuperare il risultato a posteriori. Ignorato per le chiavi private. | -- |
| `notification_email` | `string` | No | Email del proprietario del progetto | Indirizzo email da notificare al completamento di un job asincrono. Solo chiavi private. | -- |
| `callback_url` | `string` | No | -- | URL webhook che riceve una richiesta POST al completamento della conversione. Solo chiavi private. | Richiede accesso ai webhook |

### Parametri del browser e del rendering

| Parametro | Tipo | Obbligatorio | Predefinito | Descrizione | Limitazioni per piano |
|-----------|------|----------|---------|-------------|-------------|
| `viewport_width` | `integer` | No | `1920` | Larghezza del viewport del browser in pixel. | -- |
| `viewport_height` | `integer` | No | `1080` | Altezza del viewport del browser in pixel. | -- |
| `single_page` | `boolean` | No | `true` | `true` esegue il rendering dell'intera pagina come un'unica pagina PDF continua. `false` produce un output paginato usando le dimensioni di pagina di `pdf_options`. | -- |
| `load_media` | `boolean` | No | `true` | Attende il caricamento completo di tutte le immagini e i video prima della conversione. Quando è `false`, la conversione è più veloce ma i contenuti multimediali possono apparire come segnaposto. | -- |
| `enable_scroll` | `boolean` | No | `true` | Scorre la pagina dall'alto verso il basso per attivare i contenuti a caricamento differito (loader basati su IntersectionObserver). | -- |
| `handle_sticky_header` | `boolean` | No | `true` | Rileva header fissi/sticky e scorre in cima alla pagina prima della cattura, in modo che l'header venga renderizzato correttamente all'inizio del PDF. | -- |
| `handle_cookies` | `boolean` | No | `true` | Chiude automaticamente i banner di consenso cookie (OneTrust, Cookiebot, Didomi, Usercentrics e implementazioni generiche). | -- |
| `wait_for_images` | `boolean` | No | `true` | Attende il caricamento completo di tutti gli elementi `<img>` (timeout di 5 secondi per immagine). | -- |
| `wait_for_selector` | `string` | No | `null` | Selettore CSS da attendere prima della cattura. Restituisce `422` se non compare mai entro `wait_for_selector_timeout`. Utile per le SPA che idratano il contenuto dopo il caricamento. | -- |
| `wait_for_selector_timeout` | `integer` | No | `10000` | Millisecondi di attesa per `wait_for_selector` (massimo `60000`). | -- |
| `block_ads` | `boolean` | No | `false` | Interrompe le richieste ai domini noti di pubblicità/tracker in modo che non vengano mai caricati, renderizzati o rallentino la cattura. | -- |
| `block_media` | `boolean` | No | `false` | Interrompe completamente le richieste di immagini e audio/video per un rendering più veloce e leggero. A differenza di `load_media` (che controlla solo l'attesa), questo impedisce del tutto il download dei media. | -- |

### Autenticazione e richieste personalizzate

| Parametro | Tipo | Obbligatorio | Predefinito | Descrizione | Limitazioni per piano |
|-----------|------|----------|---------|-------------|-------------|
| `auth` | `object` | No | `null` | Credenziali HTTP Basic Auth per l'URL di destinazione. Formato: `{"username": "...", "password": "..."}`. Non può essere usato insieme a un header personalizzato `Authorization`. | Richiede accesso a basic auth |
| `cookies` | `array` | No | `null` | Array di oggetti cookie da iniettare prima della navigazione. Massimo 50 cookie. Ogni cookie deve avere `name`, `value` e `domain` oppure `url`. | Richiede accesso a basic auth |
| `headers` | `object` | No | `null` | Dizionario di header HTTP personalizzati inviati con ogni richiesta all'URL di destinazione. Massimo 20 header. Header bloccati: `host`, `content-length`, `transfer-encoding`, `connection`, `upgrade`, `te`, `trailer`. | Richiede accesso a basic auth |

### Opzioni PDF

Passa questi parametri all'interno di un oggetto `pdf_options` nel corpo della richiesta.

| Parametro | Tipo | Predefinito | Descrizione |
|-----------|------|---------|-------------|
| `page_size` | `string` | `"A4"` | Dimensione di pagina con nome. Ignorato quando sono impostati sia `page_width` che `page_height`. |
| `page_width` | `float` | `null` | Larghezza personalizzata della pagina in millimetri. Deve essere positiva. `page_width` e `page_height` devono essere impostati insieme. |
| `page_height` | `float` | `null` | Altezza personalizzata della pagina in millimetri. Deve essere positiva. Devono essere impostati insieme. |
| `orientation` | `string` | `"portrait"` | `"portrait"` oppure `"landscape"`. Scambia larghezza e altezza quando impostato su landscape. |
| `margins` | `object` | `{"top": 10, "bottom": 10, "left": 10, "right": 10}` | Margini di pagina in millimetri. Tutti i valori devono essere non negativi. |
| `scale` | `float` | `1.0` | Fattore di scala del contenuto. Intervallo: da `0.1` a `2.0`. Applicato solo in modalità paginata (`single_page=false`). |
| `grayscale` | `boolean` | `false` | Converte l'output PDF in scala di grigi tramite post-elaborazione. |
| `header` | `object` | `null` | Header di pagina per la modalità paginata. Formato: `{"content": "<html>", "height": 15}`. Contenuto massimo 2000 caratteri. Altezza in mm. |
| `footer` | `object` | `null` | Footer di pagina per la modalità paginata. Stesso formato dell'header. |

**Dimensioni di pagina supportate:** `A0`, `A1`, `A2`, `A3`, `A4`, `A5`, `A6`, `B0`, `B1`, `B2`, `B3`, `B4`, `B5`, `Letter`, `Legal`, `Tabloid`, `Ledger`

**Variabili template per header/footer:** `{{page}}`, `{{total_pages}}`, `{{date}}`, `{{title}}`, `{{url}}`

<div class="alert alert-info">
<strong>Nota:</strong> Header e footer vengono renderizzati solo in modalità paginata (<code>single_page=false</code>). Non hanno alcun effetto in modalità continua a pagina singola.
</div>

---

## Schema dell'oggetto cookie

Ogni elemento dell'array `cookies` deve seguire questa struttura:

| Campo | Tipo | Obbligatorio | Predefinito | Descrizione |
|-------|------|----------|---------|-------------|
| `name` | `string` | Sì | -- | Nome del cookie. |
| `value` | `string` | Sì | -- | Valore del cookie. |
| `domain` | `string` | Condizionale | -- | Dominio del cookie. È necessario fornire `domain` oppure `url`. |
| `url` | `string` | Condizionale | -- | URL a cui associare il cookie. È necessario fornire `domain` oppure `url`. |
| `path` | `string` | No | `"/"` | Percorso del cookie. Predefinito `"/"` quando `domain` è impostato. |

---

## Risposta

### Sincrona con download diretto (`direct_download=true`)

**Chiave privata** -- restituisce i byte grezzi del PDF:

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

**Chiave pubblica** -- restituisce JSON con un URL presigned:

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

### Sincrona senza download diretto (`direct_download=false`)

Disponibile solo con chiavi private.

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

### Modalità asincrona

Restituisce immediatamente un `batch_id` per il polling.

```
HTTP 202 Accepted
```

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

Quando `output_format=true` (raggruppamento ZIP):

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

### Polling dello stato del job (solo chiavi pubbliche)

L'endpoint di polling dello stato è progettato per il **recupero da timeout con chiave pubblica**. Quando una conversione sincrona richiede più tempo del timeout del reverse proxy (in genere 60s), il client può recuperare il risultato eseguendo il polling con il `job_id` fornito nella richiesta originale.

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

| Stato | Risposta |
|--------|----------|
| In elaborazione | `{"status": "processing"}` |
| Successo | `{"status": "success", "presigned_url": "...", "object_key": "..."}` |
| Fallito | `{"status": "failed", "error": "..."}` |

### Polling dello stato batch (solo chiavi private) {: #batch-status-polling-private-keys-only }

Per i job batch asincroni, esegui il polling dell'endpoint di stato batch con il `batch_id` restituito nella risposta 202.

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

**Risposta:**

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

**Valori di stato del batch:**

| Stato | Significato |
|--------|---------|
| `processing` | Almeno un URL è ancora in fase di conversione |
| `completed` | Tutti gli URL sono stati convertiti con successo |
| `partial` | Tutti gli URL sono stati completati, ma alcuni sono falliti |
| `failed` | Tutti gli URL sono falliti |

Quando `output_mode` è `"zip"`, viene fornito un unico `zip_download_url` invece dei valori `download_url` per singolo elemento.

### Payload del callback webhook

Quando viene fornito un `callback_url`, EnConvert invia una richiesta POST a quell'URL al completamento.

**Job con singolo URL:**

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

**Job batch:**

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

---

## Funzionalità

### Modalità Clear Capture

EnConvert gestisce automaticamente gli ostacoli più comuni delle pagine web per produrre PDF puliti:

- **Banner di consenso cookie** -- Chiude automaticamente i banner di OneTrust, Cookiebot, Didomi, Usercentrics e implementazioni generiche. Opera sia sulla pagina principale che sugli iframe. Usa una strategia "prima chiudi, poi accetta".
- **Chiusura di modali e popup** -- Chiude gli overlay usando più strategie: tasto Escape, pulsanti di chiusura ARIA, pulsanti di chiusura basati su classe e pulsanti di dialogo basati su ruolo.
- **Rivelazione delle animazioni da scroll** -- Forza la visibilità degli elementi nascosti da librerie di animazione attivate dallo scroll, tra cui WOW.js, AOS, ScrollReveal e GSAP ScrollTrigger.
- **Pulizia dei dropdown** -- Chiude tutti i dropdown aperti e converte gli elementi pulsante di navigazione in veri link di ancoraggio, in modo che restino cliccabili nel PDF.

### Dimensionamento e dimensioni della pagina

- **Modalità a pagina singola** (predefinita): l'intera pagina web viene renderizzata come un'unica pagina PDF continua. L'altezza viene calcolata dinamicamente in base al contenuto effettivo tramite l'attraversamento del DOM.
- **Modalità paginata** (`single_page=false`): l'output usa `page_size`, `orientation` e `margins` configurati. Supporta 18 dimensioni con nome, da A0 a Ledger, oppure dimensioni personalizzate in millimetri.

### HTTP Basic Auth

Passa `auth` con `username` e `password` per convertire pagine protette da HTTP Basic Authentication. Le credenziali vengono inviate come credenziali HTTP con ogni richiesta alla pagina di destinazione.

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

### Iniezione di cookie

Inietta fino a 50 cookie prima del caricamento della pagina. Utile per convertire pagine che richiedono una sessione attiva o preferenze utente specifiche.

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

### Header personalizzati

Invia fino a 20 header HTTP personalizzati con ogni richiesta alla pagina di destinazione. Utile per passare token API, user agent personalizzati o altri metadati della richiesta.

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

### Caricamento differito delle immagini

Quando `load_media` ed `enable_scroll` sono abilitati (entrambi predefiniti a `true`), il convertitore:

1. Scorre l'intera pagina lentamente (120px ogni 90ms) per attivare i loader a caricamento differito basati su IntersectionObserver
2. Attende che tutti gli elementi `<img>` generino il proprio evento `onload` (timeout di 5 secondi per immagine)
3. Attende 500ms per la stabilizzazione del layout dopo il caricamento di tutte le immagini

Imposta `load_media=false` per una conversione più veloce se la fedeltà dei contenuti multimediali non è critica -- il convertitore userà uno scroll rapido (300px ogni 30ms) e aggiungerà stili segnaposto per le immagini non caricate.

### Gestione dell'header sticky

Quando `handle_sticky_header` è abilitato (predefinito `true`), il convertitore rileva gli elementi posizionati come fixed e sticky che sembrano essere header (usando tag semantici, ruoli ARIA e pattern comuni nei nomi delle classi), quindi scorre in cima alla pagina prima della cattura in modo che l'header venga renderizzato correttamente all'inizio del PDF.

### Header e footer

Aggiungi header e footer ripetuti in modalità paginata con contenuto HTML e variabili template:

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

### Output in scala di grigi

Imposta `pdf_options.grayscale` su `true` per convertire il PDF finale in scala di grigi tramite post-elaborazione con Ghostscript.

### Funzionalità di rendering aggiuntive

- **Normalizzazione delle unità di viewport** -- Converte le unità CSS `vh`, `svh`, `lvh`, `dvh` in valori fissi in pixel per evitare problemi di layout nel rendering di stampa.
- **Modalità stealth** -- Usa il mascheramento del fingerprint del browser per evitare il rilevamento dei bot su pagine protette.
- **Intercettazione dei popup** -- Chiude automaticamente eventuali nuove schede del browser o popup attivati dalla pagina.
- **Bypass CSP** -- Gestisce le restrizioni di Content Security Policy e Trusted Types che altrimenti bloccherebbero la conversione.

---

## Limitazioni per piano di abbonamento

| Funzionalità | Founding | Indie | Studio | Enterprise |
|---------|------|---------|-----|------------|
| Conversione di base (singolo URL, sincrona) | Sì | Sì | Sì | Sì |
| `pdf_options` personalizzati | Sì | Sì | Sì | Sì |
| Opzioni di viewport e rendering | Sì | Sì | Sì | Sì |
| Modalità asincrona | No | Sì | Sì | Sì |
| Elaborazione batch (più URL) | No | Sì | Sì | Sì |
| Raggruppamento output in ZIP | No | No | Sì | Sì |
| Callback webhook | No | No | Sì | Sì |
| HTTP Basic Auth | No | Sì | Sì | Sì |
| Iniezione di cookie | No | Sì | Sì | Sì |
| Header personalizzati | No | Sì | Sì | Sì |
| Conversioni mensili | 100 | In base al piano | In base al piano | Illimitate |
| Limite dimensione batch | 0 | In base al piano | In base al piano | Illimitato |
| Conservazione dei file | 1 ora | In base al piano | In base al piano | In base al piano |

---

## Modalità asincrona

La modalità asincrona è utile per conversioni di lunga durata o quando si convertono più URL.

### Come funziona

1. Invia una richiesta con `async_mode=true` (oppure passa più URL, che abilita automaticamente la modalità asincrona).
2. L'API restituisce immediatamente HTTP 202 con un `batch_id` e un `url_count`.
3. Ogni URL viene convertito in background, caricato nello storage e monitorato individualmente.
4. Monitora il completamento tramite **notifica email** o **callback webhook**.

### Notifica email

Per impostazione predefinita, al termine del job asincrono viene inviata un'email di completamento all'indirizzo email del proprietario del progetto. Puoi sovrascriverlo con `notification_email`:

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

### Callback webhook

Fornisci un `callback_url` nella richiesta per ricevere una notifica POST automatica al completamento del job:

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

Il webhook viene inviato con un timeout di 30 secondi e considera HTTP 200, 201, 202 e 204 come consegna riuscita.

---

## Elaborazione batch e di massa

Converti più URL in un'unica richiesta. Richiede la modalità asincrona e una chiave privata.

### Output individuale (predefinito)

Ogni URL produce un file PDF separato:

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

### Output in bundle ZIP

Raggruppa tutti i PDF in un unico archivio 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"
}
```

Il file ZIP viene nominato `{output_filename}_{timestamp}.zip` oppure `batch_{timestamp}.zip` se non viene fornito un nome personalizzato.

---

## Esempi di codice

### Python (chiave privata)

```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 (chiave privata)

```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 (chiave privata)

```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 (chiave privata)

```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 -- Browser (chiave pubblica)

```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 (chiave pubblica)

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

---

## Risposte di errore

| Stato | Condizione |
|--------|-----------|
| `400 Bad Request` | Parametro `url` mancante o vuoto |
| `400 Bad Request` | `output_format=true` con un singolo URL (richiede più URL) |
| `400 Bad Request` | `direct_download=true` con più URL |
| `400 Bad Request` | `direct_download=true` con `async_mode=true` |
| `400 Bad Request` | Oggetto `auth` non valido (`username` o `password` mancanti) |
| `400 Bad Request` | `cookies` non valido (non è un array, supera 50 voci, campi obbligatori mancanti) |
| `400 Bad Request` | `headers` non valido (non è un oggetto, supera 20 voci, nomi di header bloccati, valori non stringa) |
| `400 Bad Request` | Conflitto tra `auth` e l'header personalizzato `Authorization` |
| `400 Bad Request` | Chiave pubblica che tenta di usare più URL |
| `400 Bad Request` | `pdf_options` non valido (dimensione di pagina non riconosciuta, scala fuori dall'intervallo 0.1-2.0, margini negativi, contenuto di header/footer superiore a 2000 caratteri) |
| `401 Unauthorized` | Chiave API / token JWT mancante o non valido |
| `402 Payment Required` | Quota mensile di ops esaurita |
| `402 Payment Required` | Il batch supererebbe la quota mensile di ops residua |
| `402 Payment Required` | Limite di storage raggiunto |
| `403 Forbidden` | Endpoint non incluso tra quelli consentiti per la chiave API |
| `403 Forbidden` | Funzionalità non disponibile nel piano attuale (async, webhook, ZIP, basic auth) |
| `403 Forbidden` | La dimensione del batch supera il limite del piano |
| `404 Not Found` | ID job non trovato (durante il polling dello stato) |
| `500 Internal Server Error` | Conversione non riuscita (crash del browser, errore di rendering, errore di post-elaborazione) |

---

## Limiti

| Limite | Valore |
|-------|-------|
| Timeout di navigazione della pagina | 60 secondi |
| Timeout di caricamento per immagine | 5 secondi |
| Timeout di chiusura banner cookie | 3 secondi |
| Numero massimo di cookie per richiesta | 50 |
| Numero massimo di header personalizzati per richiesta | 20 |
| Lunghezza del contenuto di header/footer | 2000 caratteri |
| Intervallo di scala del PDF | 0.1 -- 2.0 |
| Operazioni mensili | Dipende dal piano (Founding: 500) |
| Dimensione batch | Dipende dal piano (Founding: disabilitato) |
| Conservazione dei file | Dipende dal piano (Founding: 1 ora) |
| Timeout di consegna webhook | 30 secondi |

---

## Domande frequenti

### Come converto una pagina web in PDF con un'API REST?

Invia una richiesta `POST` a `/v1/convert/url-to-pdf` con un corpo JSON contenente l'`url` da convertire, autenticandoti con la tua chiave privata nell'header `X-API-Key` (oppure con un token Bearer JWT ottenuto da una chiave pubblica). La risposta sincrona restituisce un `presigned_url` per scaricare il PDF, oppure i byte grezzi del PDF quando `direct_download=true`.

### Posso convertire più URL in PDF in un'unica richiesta API?

Sì. Passa un array di URL nel parametro `url` con `async_mode=true` (è richiesta una chiave privata); l'API restituisce `HTTP 202` con un `batch_id` che interroghi tramite `GET /v1/convert/batch/{batch_id}`. Imposta `output_format=true` per raggruppare tutti i PDF in un unico archivio ZIP.

### Come catturo un'intera pagina web come un'unica pagina PDF continua?

La modalità a pagina singola è quella predefinita (`single_page=true`): l'intera pagina viene renderizzata come un'unica pagina PDF continua, la cui altezza è calcolata in base al contenuto effettivo. Imposta `single_page=false` per un output paginato con `page_size`, `orientation`, `margins` e header/footer opzionali tramite `pdf_options`.

### Perché il mio PDF mostra banner cookie o immagini mancanti?

La chiusura dei banner cookie (`handle_cookies`) e la gestione del lazy-load (`enable_scroll`, `load_media`, `wait_for_images`) sono tutte predefinite a `true` e coprono OneTrust, Cookiebot, Didomi, Usercentrics e i banner generici. Se imposti `load_media=false`, la conversione è più veloce ma i contenuti multimediali possono apparire come segnaposto.

### Posso convertire in PDF una pagina protetta da login?

Sì. Usa il parametro `auth` per l'HTTP Basic Auth, inietta fino a 50 cookie di sessione con `cookies`, oppure invia fino a 20 header HTTP personalizzati con `headers`. Queste opzioni richiedono l'accesso a basic auth nel tuo piano, e `auth` non può essere combinato con un header personalizzato `Authorization`.
