---
seo_title: SDK Go conversione file: client API senza dipendenze | EnConvert
meta_desc: SDK Go ufficiale di EnConvert per Go 1.21+. Solo libreria standard, nessuna dipendenza esterna, con conversione file tipizzata e il namespace web intelligence V2.
keywords: sdk conversione file go, convertire file con go, url in pdf go, api web scraping go, docx in pdf go, enconvert go sdk, libreria golang html in pdf, heic in webp golang, api estrazione markdown go, api screenshot sito web golang, pipeline rag ingestion go, estrazione dati strutturati golang
---

# SDK Go per la conversione file

`github.com/conversionapi/go-sdk` è il client EnConvert ufficiale per Go 1.21 e successivi. Si importa come package `enconvert` e non porta con sé alcuna dipendenza di terze parti: tutto gira su `net/http`, `encoding/json` e `mime/multipart`. Dodici metodi sul client coprono conversione file, rendering di URL e batch su interi siti (DOCX in PDF, HEIC in WebP, URL in PDF, URL in Markdown), e il namespace `client.V2` aggiunge ventitré metodi di web intelligence per percepire, scoprire, cercare, distillare, ingerire e sorvegliare pagine. Ogni chiamata accetta un `context.Context` come primo argomento, così cancellazioni e scadenze restano nelle tue mani.

<div class="alert alert-info">
<strong>Modulo:</strong> <code>github.com/conversionapi/go-sdk</code> · <strong>Package:</strong> <code>enconvert</code> · <strong>Sorgente:</strong> <a href="https://github.com/conversionapi/go-sdk">conversionapi/go-sdk</a> · <strong>Go:</strong> 1.21+ · <strong>Dipendenze:</strong> nessuna
</div>

---

## Installazione

```bash
go get github.com/conversionapi/go-sdk
```

Il percorso del modulo finisce in `go-sdk` ma il package si chiama `enconvert`, quindi importalo con un nome esplicito: `import enconvert "github.com/conversionapi/go-sdk"`.

---

## Guida rapida

```go
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    enconvert "github.com/conversionapi/go-sdk"
)

func main() {
    client, err := enconvert.New(os.Getenv("ENCONVERT_API_KEY"))
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()

    result, err := client.ConvertURLToPDF(ctx, "https://example.com", enconvert.URLToPDFOptions{SaveTo: "page.pdf"})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(result.Filename, result.PresignedURL)
}
```

`New` restituisce un errore solo quando la chiave API è vuota; vedi [Configurazione](#configurazione) per le opzioni funzionali. Leggere una pagina come dovrebbe farlo un agente, con un punteggio di qualità allegato, è una sola chiamata sul namespace V2:

```go
op, err := client.V2.Perceive(ctx, "https://example.com", enconvert.PerceiveOptions{
    Outputs: []enconvert.PerceiveOutputName{enconvert.PerceiveOutputMarkdown},
})
fmt.Println(op.Outputs["markdown"].URL, *op.RenderQuality) // ad es. 0.93
```

Ogni frammento qui sotto presuppone un `client` e un `ctx` costruiti esattamente così.

---

## Cosa espone il client

Dodici metodi pendono da `*enconvert.Client` e corrispondono 1:1 agli endpoint REST. Le opzioni si passano sempre come valore struct, mai come puntatore, quindi il valore zero (`enconvert.URLToPDFOptions{}`) significa "tutti i default", e numeri e booleani facoltativi sono ovunque campi puntatore: usa gli helper `enconvert.Int`, `enconvert.Bool`, `enconvert.Float64` ed `enconvert.String` invece di una variabile locale usa e getta.

| Metodo | Endpoint | Restituisce |
|--------|----------|---------|
| `ConvertURLToPDF(ctx, url, opts)` | `POST /v1/convert/url-to-pdf` | `ConversionResult` |
| `ConvertURLToScreenshot(ctx, url, opts)` | `POST /v1/convert/url-to-screenshot` | `ConversionResult` |
| `ConvertURLToMarkdown(ctx, url, opts)` | `POST /v1/convert/url-to-markdown` | `ConversionResult` |
| `ConvertImage(ctx, file, opts)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `ConvertDocument(ctx, file, opts)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `ConvertToMarkdown(ctx, file, opts)` | `POST /v1/convert/anything-to-markdown` | `ConversionResult` |
| `ConvertToPDF(ctx, file, opts)` | `POST /v1/convert/anything-to-pdf` | `ConversionResult` |
| `ConvertWebsiteToPDF(ctx, url, opts)` | `POST /v1/convert/website-to-pdf` | `BatchSubmission` |
| `ConvertWebsiteToScreenshot(ctx, url, opts)` | `POST /v1/convert/website-to-screenshot` | `BatchSubmission` |
| `GetJobStatus(ctx, jobID)` | `GET /v1/convert/status/{jobID}` | `JobStatus` |
| `GetBatchStatus(ctx, batchID)` | `GET /v1/convert/batch/{batchID}` | `BatchStatus` |
| `WaitForBatch(ctx, batchID, opts)` | `GET /v1/convert/batch/{batchID}` (con polling) | `BatchStatus` |

`client.V2` contiene altri ventitré metodi distribuiti su sei gruppi di capacità:

| Gruppo | Metodi | Percorso base |
|-------|---------|-----------|
| Perceive | `Perceive`, `PerceiveDirect`, `GetPerceiveOperation`, `DownloadPerceiveArtifact`, `PerceiveBatch`, `GetPerceiveBatch` | `/v2/perceive` |
| Discover | `Discover` | `/v2/discover` |
| Lookup | `Lookup` | `/v2/lookup` |
| Distill | `Distill` | `/v2/distill` |
| Ingest | `Ingest`, `IngestFiles`, `GetIngestJob`, `ListIngestJobs`, `CancelIngestJob`, `RetryIngestWebhook`, `GetWebhookSecret`, `RotateWebhookSecret` | `/v2/ingest` |
| Watch | `CreateWatcher`, `ListWatchers`, `GetWatcher`, `GetWatcherSnapshots`, `UpdateWatcher`, `DeleteWatcher` | `/v2/watch` |

---

## Conversione file

Gli upload accettano qualsiasi `FileSource`: `enconvert.FilePath("report.docx")` per un percorso su disco (il nome base determina il formato di input e il tipo MIME), `enconvert.FileBytes(buf)` per byte grezzi senza nome (caricati come `upload.bin`, `application/octet-stream`), oppure `enconvert.FileInput{Data: buf, Filename: "report.docx"}` per byte grezzi con un nome file esplicito e un `ContentType` facoltativo.

### ConvertURLToPDF

Esegue il rendering in PDF di qualsiasi URL raggiungibile.

```go
result, err := client.ConvertURLToPDF(ctx, "https://example.com", enconvert.URLToPDFOptions{
    SinglePage:       enconvert.Bool(false),
    PDFOptions:       &enconvert.PDFOptions{PageSize: "A4", Orientation: "landscape"},
    URLRenderOptions: enconvert.URLRenderOptions{ViewportWidth: enconvert.Int(1440)},
    SaveTo:           "report.pdf",
})
```

| Opzione | Tipo | Default | Descrizione |
|--------|------|---------|-------------|
| `SaveTo` | `string` | -- | Percorso locale su cui scrivere il PDF in streaming. Le directory superiori vengono create per te. |
| `SinglePage` | `*bool` | `true` | `true` produce una sola pagina continua. `false` impagina usando `PDFOptions.PageSize`. |
| `PDFOptions` | `*PDFOptions` | -- | Dimensione pagina, orientamento, margini, scala, scala di grigi, intestazione, piè di pagina. Vedi [Opzioni PDF](#opzioni-pdf). |
| `ViewportWidth`, `ViewportHeight` | `*int` | `1920`, `1080` | Dimensione del viewport del browser in pixel. |
| `LoadMedia` | `*bool` | `true` | Attende immagini e video prima della cattura. |
| `EnableScroll` | `*bool` | `true` | Scorre dall'alto in basso per far scattare i lazy loader. |
| `OutputFilename` | `string` | automatico | Sovrascrive il nome file generato. |
| `Auth` | `*HTTPBasicAuth` | -- | Credenziali HTTP Basic per pagine dietro un login. |
| `Cookies`, `Headers` | `[]BrowserCookie`, `map[string]string` | -- | Cookie iniettati prima del rendering (massimo 50) e header di richiesta aggiuntivi (massimo 20, quelli hop-by-hop vengono rifiutati). |

Tutto quello che va da `ViewportWidth` in giù vive sulla struct `URLRenderOptions` incorporata, condivisa da ogni metodo basato su URL.

<div class="alert alert-warning">
<strong>Non combinare <code>Auth</code> con un header <code>Authorization</code>.</strong> L'API rifiuta il conflitto invece di indovinare quale dei due intendevi.
</div>

### ConvertURLToScreenshot e ConvertURLToMarkdown

Cattura un PNG di qualsiasi URL, oppure estrae Markdown GitHub-Flavored pulito con frontmatter YAML (titolo, descrizione, url, link, immagini). Il convertitore Markdown rimuove navigazione, piè di pagina, pubblicità e script, e mantiene il corpo principale dell'articolo.

```go
shot, err := client.ConvertURLToScreenshot(ctx, "https://example.com", enconvert.URLToScreenshotOptions{
    URLRenderOptions: enconvert.URLRenderOptions{ViewportWidth: enconvert.Int(1440)},
    SaveTo:           "shot.png",
})

article, err := client.ConvertURLToMarkdown(ctx, "https://example.com/article",
    enconvert.URLToMarkdownOptions{SaveTo: "article.md"})
```

Entrambi accettano le stesse `URLRenderOptions` di `ConvertURLToPDF`, più `SaveTo`. Nessuno dei due accetta `SinglePage` o `PDFOptions`.

### ConvertImage

Converte tra `jpeg`, `png`, `svg`, `heic` e `webp` in qualsiasi direzione, oppure rasterizza un PDF in JPEG.

```go
// Da un percorso
result, err := client.ConvertImage(ctx, enconvert.FilePath("photo.heic"),
    enconvert.ConvertImageOptions{OutputFormat: "webp", SaveTo: "photo.webp"})

// Da byte già in memoria
buf, _ := os.ReadFile("photo.heic")
result, err = client.ConvertImage(ctx, enconvert.FileInput{Data: buf, Filename: "photo.heic"},
    enconvert.ConvertImageOptions{OutputFormat: "webp", SaveTo: "photo.webp"})
```

| Opzione | Tipo | Obbligatoria | Descrizione |
|--------|------|----------|-------------|
| `OutputFormat` | `string` | Sì | `jpeg`, `png`, `svg`, `heic` o `webp`. `jpg` è accettato come alias di `jpeg`. |
| `SaveTo` | `string` | -- | Percorso locale su cui scrivere il risultato in streaming. |
| `OutputFilename` | `string` | -- | Sovrascrive il nome file generato. |

Il formato di input viene ricavato dall'estensione del nome file (`.jpg`, `.jpeg`, `.png`, `.svg`, `.heic`, `.webp`, `.pdf`). Le coppie non supportate falliscono in locale, prima di qualsiasi chiamata di rete, con un errore che elenca cosa è disponibile:

```go
enconvert.ValidOutputsFor("pdf")  // []string{"jpeg"}
enconvert.ValidOutputsFor("json") // []string{"csv", "toml", "xml", "yaml"}
```

### ConvertDocument

Converte documenti e formati dati. `OutputFormat` vale `pdf` quando lo lasci vuoto.

```go
// docx in pdf
_, err := client.ConvertDocument(ctx, enconvert.FilePath("report.docx"),
    enconvert.ConvertDocumentOptions{SaveTo: "report.pdf"})

// json in yaml
_, err = client.ConvertDocument(ctx, enconvert.FilePath("data.json"),
    enconvert.ConvertDocumentOptions{OutputFormat: "yaml", SaveTo: "data.yaml"})
```

**Input supportati:** `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.html`, `.htm`, `.odt`, `.ods`, `.odp`, `.ots`, `.pages`, `.numbers`, `.md`, `.markdown`, `.csv`, `.json`, `.xml`, `.yaml`, `.yml`, `.toml`.

| Opzione | Tipo | Default | Descrizione |
|--------|------|---------|-------------|
| `OutputFormat` | `string` | `"pdf"` | Formato di destinazione. `yml`, `htm`, `md` e `jpg` vengono normalizzati ai rispettivi nomi canonici. |
| `SaveTo` | `string` | -- | Percorso locale su cui scrivere il risultato in streaming. |
| `OutputFilename` | `string` | -- | Sovrascrive il nome file generato. |
| `PDFOptions` | `*PDFOptions` | -- | Impostazioni di pagina. Hanno senso solo quando l'output è un PDF. |

Le 43 coppie implementate, esposte come mappa `enconvert.ImplementedConversions`, sono: `json` verso `csv`, `toml`, `xml`, `yaml`; `xml` verso `csv`, `json`; `yaml` verso `json`; `csv` verso `json`, `xml`; `toml` verso `json`; `markdown` verso `html`, `pdf`; `html` verso `pdf`; `doc`, `excel`, `ppt`, `odt`, `ods`, `odp`, `ots`, `pages` e `numbers` verso `pdf`; tutte e 20 le coppie ordinate tra `jpeg`, `png`, `svg`, `heic`, `webp`; e `pdf` verso `jpeg`.

EPUB non ha una coppia documenti dedicata. Passa i file `.epub` attraverso `ConvertToPDF` o `ConvertToMarkdown`.

### ConvertToMarkdown

Rileva automaticamente l'input lato server e restituisce Markdown pulito. È il mattone di base per l'ingestione RAG: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT e MD, più i formati office legacy e ODF.

```go
_, err := client.ConvertToMarkdown(ctx, enconvert.FilePath("handbook.pdf"),
    enconvert.ConvertToMarkdownOptions{SaveTo: "handbook.md"})
```

`ConvertToMarkdownOptions` ha due campi, `SaveTo` e `OutputFilename`. Le immagini non sono supportate da questo endpoint, e su di esso non ci sono opzioni PDF. L'output conserva la gerarchia di intestazioni del documento, così un chunker semantico può dividere sulle intestazioni invece che su conteggi arbitrari di caratteri.

### ConvertToPDF

Rileva automaticamente l'input lato server e restituisce un PDF: office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, testo semplice, immagini raster, SVG, EPUB, oppure un PDF esistente restituito così com'è per la normalizzazione.

```go
_, err := client.ConvertToPDF(ctx, enconvert.FilePath("slides.pptx"),
    enconvert.ConvertToPDFOptions{SaveTo: "slides.pdf"})

// Passthrough PDF, convertito in scala di grigi
_, err = client.ConvertToPDF(ctx, enconvert.FilePath("scan.pdf"), enconvert.ConvertToPDFOptions{
    PDFOptions: &enconvert.PDFOptions{Grayscale: enconvert.Bool(true)},
    SaveTo:     "scan-gray.pdf",
})
```

<div class="alert alert-warning">
<strong>Qui viene rispettato solo <code>PDFOptions.Grayscale</code>.</strong> Ogni altro campo di geometria viene ignorato su <code>anything-to-pdf</code>. Usa <code>ConvertDocument</code> o <code>ConvertURLToPDF</code> quando ti servono dimensione pagina, orientamento, margini, scala, intestazioni o piè di pagina.
</div>

`ConvertToPDFOptions` ha tre campi: `SaveTo`, `OutputFilename` e `PDFOptions`.

### ConvertWebsiteToPDF e ConvertWebsiteToScreenshot

Individua ogni pagina di un sito, converte ciascuna in background e raccoglie tutto in un unico ZIP. Entrambi sono solo asincroni: restituiscono un `BatchSubmission`, e tu interroghi con `GetBatchStatus` oppure ti blocchi con `WaitForBatch`. Serve una chiave API privata con accesso al crawl.

```go
batch, err := client.ConvertWebsiteToPDF(ctx, "https://example.com", enconvert.WebsiteToPDFOptions{
    WebsiteConversionOptions: enconvert.WebsiteConversionOptions{CrawlMode: enconvert.CrawlModeSitemap},
})

status, err := client.WaitForBatch(ctx, batch.BatchID, enconvert.WaitForBatchOptions{SaveTo: "site.zip"})
fmt.Println(status.Completed, "of", status.Total, "pages converted")
```

| Opzione | Tipo | Default | Descrizione |
|--------|------|---------|-------------|
| `CrawlMode` | `CrawlMode` | `auto` | `CrawlModeAuto`, `CrawlModeSitemap` (solo sitemap.xml) o `CrawlModeFull` (sitemap più crawl BFS). |
| `IncludePatterns` | `[]string` | -- | Fa il crawl solo degli URL che corrispondono a questi pattern, in modalità full crawl. |
| `ExcludePatterns` | `[]string` | -- | Salta gli URL che corrispondono a questi pattern, in modalità full crawl. |
| `NotificationEmail` | `string` | proprietario del progetto | Indirizzo avvisato quando il batch finisce. |
| `CallbackURL` | `string` | -- | Webhook chiamato in POST quando il batch finisce. |
| `SinglePage` | `*bool` | default del server | Solo PDF. Pagina continua o impaginata. |
| `PDFOptions` | `*PDFOptions` | -- | Solo PDF. Applicate a ogni pagina. |

Anche tutto quello che sta su `URLRenderOptions` è accettato qui, e viene inviato per singola pagina solo quando lo imposti. `WaitForBatchOptions` accetta `Interval` (default 5 secondi), `Timeout` (default 30 minuti) e `SaveTo`. Sforare il timeout restituisce un `*APIError` con stato `504`; un batch finito senza ZIP ne restituisce uno con stato `500`. `ConvertWebsiteToScreenshot` è identico tolti `SinglePage` e `PDFOptions`, e produce uno ZIP di PNG.

---

## Web intelligence (V2)

Ogni lettura V2 porta con sé un punteggio `RenderQuality` da 0.0 a 1.0, esposto come `*float64` su `PerceiveResult`, `DistillItem`, `WatcherSnapshot` e sul risultato del download diretto. Un punteggio basso significa che la pagina non si è renderizzata in modo pulito: una pagina di sfida, un muro dei cookie, uno shell SPA vuoto, un errore HTTP. Il contenuto torna comunque, segnalato, insieme a una mappa `Deductions` che nomina ogni penalità scattata e a uno slice `Warnings`, così una lettura sbagliata non entra mai di nascosto nel contesto del tuo agente. Tutti i metodi V2 richiedono una chiave API privata; le chiavi pubbliche vengono rifiutate. Usa il punteggio come filtro prima di fidarti di qualsiasi cosa:

```go
if op.RenderQuality != nil && *op.RenderQuality < 0.6 {
    log.Printf("low quality read of %s: %v", op.URL, op.Deductions)
}
```

### Perceive

Esegue il rendering di un URL negli artefatti che richiedi. Sincrono, con URL degli artefatti firmati per 15 minuti.

```go
op, err := client.V2.Perceive(ctx, "https://example.com", enconvert.PerceiveOptions{
    Outputs:  []enconvert.PerceiveOutputName{enconvert.PerceiveOutputMarkdown, enconvert.PerceiveOutputStructured},
    Extract:  []enconvert.PerceiveExtractName{enconvert.PerceiveExtractTables, enconvert.PerceiveExtractMetadata},
    Viewport: &enconvert.PerceiveViewport{Width: enconvert.Int(1440)},
})
fmt.Println(op.OperationID, op.Outputs["markdown"].URL, op.Structured, op.ExtractionTier)

// Rifirma gli URL degli artefatti più tardi
again, err := client.V2.GetPerceiveOperation(ctx, op.OperationID)
```

| Opzione | Tipo | Default | Descrizione |
|--------|------|---------|-------------|
| `Outputs` | `[]PerceiveOutputName` | `markdown`, `structured` | `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. |
| `Extract` | `[]PerceiveExtractName` | -- | `tables`, `prices`, `contacts`, `metadata`, `main_content`, `headings`, `structured_data`, `technologies`, `all`. |
| `Schema` | `map[string]any` | -- | Schema JSON per l'estrazione strutturata tramite il tier LLM. |
| `WaitFor`, `WaitTimeoutMs` | `string`, `*int` | `30000` ms | Un selettore CSS, facoltativamente con prefisso `css:`, oppure `js:<expr>`, e il relativo budget (da 0 a 60000). |
| `JSCode` | `string` | -- | JavaScript eseguito dopo la navigazione, massimo 20000 caratteri. |
| `Viewport` | `*PerceiveViewport` | 1920 x 1080 | `Width` da 320 a 3840, `Height` da 240 a 2160. |
| `Headers`, `Cookies`, `Auth` | `map[string]string`, `[]BrowserCookie`, `*HTTPBasicAuth` | -- | Header di richiesta, cookie iniettati, credenziali HTTP Basic. |
| `CacheMode` | `PerceiveCacheMode` | `enabled` | `enabled` (cache di 1 ora), `bypass`, `refresh`. |
| `PDFOptions` | `*PDFOptions` | -- | Ha senso solo quando `Outputs` include `pdf`. |
| `BlockResources` | `[]PerceiveResourceType` | -- | `image`, `media`, `font`, `stylesheet`, `script`, `xhr`, `fetch`, `websocket`, `manifest`, `other`. |
| `RespectRobots`, `Mobile` | `*bool` | default del server | Rispetta `robots.txt`; emula un dispositivo mobile. |
| `OnlyMainContent` | `*bool` | `true` | Rimuove navigazione, header, footer e banner dei cookie dall'artefatto markdown e dall'estratto `main_content`. Imposta `false` per la pagina intera. |
| `DirectDownload` | `*bool` | `false` | Trasmette byte grezzi invece di una busta JSON. Preferisci `PerceiveDirect`. |

<div class="alert alert-warning">
<strong>Tre opzioni sono dichiarate ma non ancora attive.</strong> <code>ProxyURL</code>, <code>Geolocation</code> e <code>ActionChain</code> sono accettate dalla struct Go e serializzate, ma al momento il server risponde <code>422</code> per tutte e tre. Lasciale non impostate.
</div>

Trasmettere un singolo artefatto direttamente su disco salta la busta JSON. `PerceiveDirect` verifica in locale che tu abbia chiesto esattamente un output che produca un artefatto:

```go
direct, err := client.V2.PerceiveDirect(ctx, "https://example.com", enconvert.PerceiveOptions{
    Outputs: []enconvert.PerceiveOutputName{enconvert.PerceiveOutputPDF},
})
os.WriteFile(direct.Filename, direct.Content, 0o644)

// Riscarica più tardi un artefatto salvato. Passa "" quando l'operazione ne ha prodotto uno solo.
saved, err := client.V2.DownloadPerceiveArtifact(ctx, direct.OperationID, enconvert.PerceiveOutputPDF)
```

`PerceiveDirectResult` porta con sé `Content`, `ContentType`, `Filename`, `OperationID`, `ObjectKey`, `CacheHit`, `RenderQuality`, `SourceStatusCode`, `ContentHash` e `WarningsCount`, tutti letti dagli header della risposta. Un `*APIError` con `410` da `DownloadPerceiveArtifact` significa che l'artefatto è uscito dalla sua finestra di conservazione.

I batch accettano fino a 1000 URL con un unico blocco di opzioni condivise. I batch piccoli finiscono inline; quelli più grandi tornano `queued`, quindi interroga `GetPerceiveBatch` finché `Status` non è `PerceiveBatchStatusCompleted` e leggi `Zip` o `Items`:

```go
batch, err := client.V2.PerceiveBatch(ctx, []string{"https://a.example", "https://b.example"},
    enconvert.PerceiveBatchOptions{OutputMode: enconvert.PerceiveBatchOutputZip})
done, err := client.V2.GetPerceiveBatch(ctx, batch.JobID)
```

`OutputMode` è `PerceiveBatchOutputManifest` (predefinito) o `PerceiveBatchOutputZip`. `DirectDownload` viene rifiutato con `422` sui batch.

### Discover

Elenca gli URL di un sito a partire dalla sua sitemap, da un crawl HTTP o da entrambi. Non viene avviato alcun browser, quindi è veloce ed economico.

```go
found, err := client.V2.Discover(ctx, "https://example.com", enconvert.DiscoverOptions{
    Mode: enconvert.DiscoverModeHybrid, MaxURLs: enconvert.Int(200), ExcludePatterns: []string{"/tag/"},
})
fmt.Println(found.Total, found.Truncated, found.Sources)
```

| Opzione | Tipo | Default | Descrizione |
|--------|------|---------|-------------|
| `Mode` | `DiscoverMode` | `hybrid` | `DiscoverModeSitemap`, `DiscoverModeCrawl` o `DiscoverModeHybrid` (sitemap più crawl HTTP). |
| `MaxURLs`, `MaxDepth` | `*int` | `100`, `2` | Da 1 a 1000 URL, profondità del crawl da 1 a 5. |
| `IncludePatterns`, `ExcludePatterns` | `[]string` | -- | Prima allowlist e poi denylist di espressioni regolari, semantica `re.search`, massimo 50 ciascuna. |
| `SameDomainOnly` | `*bool` | `true` | Resta sul dominio dell'URL di partenza. |
| `RespectRobots` | `*bool` | default del server | Rispetta `robots.txt`. |

`DiscoverResult` ti dà `URL`, `Mode`, `Total`, `URLs`, `PagesCrawled`, `Truncated`, `RobotsRespected`, `Sources` (conteggi grezzi per sorgente prima della deduplicazione) e `Warnings`.

### Lookup

Ricerca web categorizzata, con la possibilità di renderizzare i primi risultati nello stesso round trip.

```go
search, err := client.V2.Lookup(ctx, "best static site generators", enconvert.LookupOptions{
    Category: enconvert.LookupCategoryWeb, NumResults: enconvert.Int(10), PerceiveTop: enconvert.Int(3),
})
for _, hit := range search.Results {
    fmt.Println(hit.Position, hit.Title, hit.URL)
    if hit.Perceive != nil {
        fmt.Println(hit.Perceive.Outputs["markdown"].URL)
    }
}
```

| Opzione | Tipo | Default | Descrizione |
|--------|------|---------|-------------|
| `Category` | `LookupCategory` | `web` | `web`, `news`, `images`, `scholar`, `patents`, `maps`. |
| `Country`, `Locale` | `string` | -- | Codice paese `gl` di Google (`us`, `in`) e lingua dell'interfaccia `hl` (`en`). |
| `TimeFilter` | `LookupTimeFilter` | -- | `hour`, `day`, `week`, `month`, `year`. |
| `NumResults`, `Page` | `*int` | `10`, `1` | Da 1 a 100 risultati, pagina da 1 a 10. |
| `Location` | `string` | -- | Località in testo libero, per esempio `Austin, Texas`. |
| `Autocorrect` | `*bool` | `true` | Lascia che il provider corregga i refusi nella query. |
| `PerceiveTop` | `*int` | `0` | Da 0 a 10. Esegue un render completo del browser sui primi N URL dei risultati e allega ciascun `PerceiveResult` inline. |

`LookupResult` porta con sé anche `AnswerBox`, `KnowledgeGraph`, `PerceiveOperationIDs` e `Warnings`.

### Distill

Estrazione strutturata guidata da uno schema. Fornisci esattamente uno tra `URLs` (massimo 50) e `DiscoverFrom`; `Schema` è sempre obbligatorio. Entrambe le regole vengono controllate in locale prima che parta qualsiasi richiesta.

```go
extraction, err := client.V2.Distill(ctx, enconvert.DistillOptions{
    URLs:   []string{"https://example.com/pricing"},
    Schema: map[string]any{"plans": "list of plan names with monthly prices"},
    CSSSchema: &enconvert.CSSSchema{
        BaseSelector: ".plan-card",
        Fields: []enconvert.CSSField{
            {Name: "name", Type: enconvert.CSSFieldText, Selector: "h3"},
            {Name: "price", Type: enconvert.CSSFieldText, Selector: ".price"},
        },
    },
})
first := extraction.Results[0]
fmt.Println(first.Data, first.ExtractionTier, first.FieldsFromCSS, first.FieldsFromLLM)
```

Il `CSSSchema` facoltativo gira per primo e risponde a tutto ciò che può con semplici selettori; solo i campi che mancano vengono escalati al tier LLM, ed `ExtractionTier` riporta quali tier hanno effettivamente risposto (`css`, `llm`, `mixed` o `none`). `CSSField.Type` è uno tra `text`, `attribute`, `html`, `regex`, `nested`, `list` o `nested_list`, annidabile fino a 5 livelli di profondità.

Scambia `URLs` con `DiscoverFrom` per scoprire e poi distillare in un'unica chiamata. `DistillDiscoverFrom` accetta `URL`, `Mode` (default `hybrid`) e `MaxPages` (da 1 a 50, default 10, che limita sia la scoperta sia la distillazione):

```go
_, err = client.V2.Distill(ctx, enconvert.DistillOptions{
    DiscoverFrom: &enconvert.DistillDiscoverFrom{URL: "https://example.com", MaxPages: enconvert.Int(10)},
    Schema:       map[string]any{"title": "page title", "summary": "one-line summary"},
})
```

### Ingest

Trasforma un sito, un elenco di URL o una pila di documenti caricati in JSONL suddiviso in chunk e pronto per RAG. Sempre asincrono.

```go
job, err := client.V2.Ingest(ctx, enconvert.IngestOptions{
    Mode:       enconvert.IngestModeSitemap,
    URL:        "https://docs.example.com",
    MaxPages:   enconvert.Int(100),
    Chunk:      &enconvert.IngestChunkOptions{MaxWords: enconvert.Int(512), SentenceOverlap: enconvert.Int(1)},
    WebhookURL: "https://my.app/hooks/enconvert",
})

status, err := client.V2.GetIngestJob(ctx, job.JobID)
if status.Status == enconvert.IngestStatusCompleted {
    fmt.Println(status.TotalChunks, status.OutputURL)
}
```

| Opzione | Tipo | Default | Descrizione |
|--------|------|---------|-------------|
| `Mode` | `IngestMode` | `urls` | `IngestModeURLs`, `IngestModeSitemap`, `IngestModeCrawl`, `IngestModeFiles`. |
| `URL` | `string` | -- | URL di partenza. Obbligatorio per `sitemap` e `crawl`, vietato per `urls`. |
| `URLs` | `[]string` | -- | URL espliciti, massimo 1000. Obbligatori per `urls`, vietati altrimenti. |
| `MaxPages`, `MaxDepth` | `*int` | `50`, `2` | Limite di scoperta da 1 a 1000 per `sitemap` e `crawl`, profondità da 1 a 5. |
| `SameDomainOnly` | `*bool` | `true` | Resta sul dominio dell'URL di partenza. |
| `IncludePatterns`, `ExcludePatterns` | `[]string` | -- | Allowlist di espressioni regolari, poi denylist. |
| `RespectRobots` | `*bool` | default del server | Rispetta `robots.txt`. |
| `WaitFor`, `WaitTimeoutMs` | `string`, `*int` | `30000` ms | Selettore o espressione `js:` attesa per ogni pagina, e il relativo budget (da 0 a 60000). |
| `Chunk` | `*IngestChunkOptions` | -- | `MaxWords` da 32 a 4000, default 512. `SentenceOverlap` da 0 a 10, default 1. |
| `WebhookURL` | `string` | -- | Webhook di completamento, firmato in HMAC. |

Le regole su modalità e URL qui sopra vengono applicate lato client: `Ingest` restituisce un semplice errore Go, non un round trip verso l'API, se invii `URLs` con `Mode: IngestModeSitemap`.

I file caricati percorrono la stessa pipeline e lo stesso ciclo di vita del job:

```go
fileJob, err := client.V2.IngestFiles(ctx,
    []enconvert.FileSource{enconvert.FilePath("handbook.pdf"), enconvert.FilePath("notes.docx")},
    enconvert.IngestFilesOptions{Chunk: &enconvert.IngestChunkOptions{MaxWords: enconvert.Int(512)}})
```

Sono accettati PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT e MD, e file office legacy o ODF; serve almeno un file. Gestione dei job e impianto dei webhook:

```go
list, err := client.V2.ListIngestJobs(ctx, enconvert.V2ListOptions{Limit: enconvert.Int(20)})
canceled, err := client.V2.CancelIngestJob(ctx, job.JobID) // idempotente
secret, err := client.V2.GetWebhookSecret(ctx)             // Secret, SignatureHeader, SignatureScheme, ...
rotated, err := client.V2.RotateWebhookSecret(ctx)         // le vecchie firme smettono subito di verificare
retry, err := client.V2.RetryIngestWebhook(ctx, job.JobID)
```

`RetryIngestWebhook` risponde `409` quando il job non è completato e `400` quando non ha alcun webhook configurato. `V2ListOptions` accetta `Skip` e `Limit` (da 1 a 100, default 20).

### Watch

Riesegue il rendering di una pagina a cadenza fissa e ti avvisa quando cambia.

```go
watcher, err := client.V2.CreateWatcher(ctx, "https://example.com/pricing", enconvert.WatchCreateOptions{
    FrequencyMinutes: enconvert.Int(60),
    DiffMode:         enconvert.WatchDiffAuto,
    WebhookURL:       "https://my.app/hooks/changes",
})

history, err := client.V2.GetWatcherSnapshots(ctx, watcher.WatcherID, enconvert.SnapshotListOptions{Limit: enconvert.Int(10)})
for _, snap := range history.Snapshots {
    fmt.Println(snap.CheckedAt, snap.HasChanges, snap.ChangeCount, snap.Changes)
}
```

| Opzione | Tipo | Default | Descrizione |
|--------|------|---------|-------------|
| `FrequencyMinutes` | `*int` | `60` | Da 60 a 43200. Il limite minimo di un'ora è rigido. |
| `DiffMode` | `WatchDiffMode` | `auto` | `WatchDiffAuto`, `WatchDiffText`, `WatchDiffStructured`, `WatchDiffTables`, `WatchDiffMetadata`. |
| `TrackFields` | `map[string]any` | -- | Sottoinsieme di campi o selettori passato al motore di diff. |
| `WebhookURL`, `NotifyEmail` | `string`, `*bool` | --, `true` | Webhook di cambiamento firmato in HMAC, e se inviare un'email al proprietario del progetto. |

```go
// Un puntatore alla stringa vuota azzera il webhook; nil lo lascia com'è.
_, err = client.V2.UpdateWatcher(ctx, watcher.WatcherID, enconvert.WatcherUpdate{
    Status: enconvert.WatcherStatusPaused, WebhookURL: enconvert.String(""),
})
_, err = client.V2.DeleteWatcher(ctx, watcher.WatcherID) // soft delete, idempotente
```

`UpdateWatcher` richiede almeno un campo e restituisce un semplice errore Go se gli passi una struct vuota. `Status` accetta solo `WatcherStatusActive` o `WatcherStatusPaused`; l'eliminazione passa da `DeleteWatcher`, che restituisce il watcher marcato come eliminato con stato `deleted`. `ListWatchers` e `GetWatcher` completano il gruppo.

<div class="alert alert-warning">
<strong>I diff degli snapshot contengono contenuto di pagina non attendibile.</strong> <code>WatcherSnapshot.Changes</code> è uno slice di mappe grezze prelevate dalla pagina sorvegliata. Applica l'escape ai valori prima di renderizzarli da qualsiasi parte.
</div>

---

## Opzioni PDF

`PDFOptions` è condivisa da `ConvertURLToPDF`, `ConvertDocument`, `ConvertWebsiteToPDF`, `PerceiveOptions` e (solo per `Grayscale`) `ConvertToPDF`. Vengono inviati solo i campi che imposti.

```go
opts := &enconvert.PDFOptions{
    PageSize:    "A4",
    Orientation: "landscape",
    Margins:     &enconvert.PDFMargins{Top: enconvert.Float64(10), Bottom: enconvert.Float64(10)},
    Scale:       enconvert.Float64(0.9),
    Footer:      &enconvert.PDFHeaderFooter{Content: "Confidential", Height: enconvert.Float64(20)},
}
```

| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `PageSize` | `string` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"` e simili. |
| `PageWidth`, `PageHeight` | `*float64` | Impostali entrambi insieme per prevalere su `PageSize`. |
| `Orientation` | `string` | `"portrait"` o `"landscape"`. Il valore predefinito è verticale. |
| `Margins` | `*PDFMargins` | `Top`, `Bottom`, `Left`, `Right`, ciascuno un `*float64` in millimetri. Tutti e quattro facoltativi. |
| `Scale` | `*float64` | Scala di rendering, per esempio `0.9` per il 90%. |
| `Grayscale` | `*bool` | Post-elabora il PDF convertendolo in scala di grigi. |
| `Header`, `Footer` | `*PDFHeaderFooter` | Ciascuno ha `Content` (massimo 2000 caratteri) e `Height`. |

---

## Gestione degli errori

Go non ha classi di eccezione, quindi ogni errore dell'API è un unico tipo concreto, `*enconvert.APIError`, più tre predicati. `Error()` viene reso come `"[<status>] <message>"`.

```go
result, err := client.ConvertURLToPDF(ctx, "https://example.com", enconvert.URLToPDFOptions{})
switch {
case err == nil:
    fmt.Println(result.PresignedURL)
case enconvert.IsAuthenticationError(err):
    log.Println("invalid or missing API key")
case enconvert.IsRateLimitError(err):
    log.Println("too many requests, back off and retry")
case enconvert.IsQuotaError(err):
    log.Println("request rejected with 402")
default:
    var apiErr *enconvert.APIError
    if errors.As(err, &apiErr) {
        log.Printf("api error [%d]: %s", apiErr.StatusCode, apiErr.Message)
    } else {
        log.Println(err) // errore di rete, cancellazione del context, validazione locale
    }
}
```

| Controllo | Sollevato su | Codice di stato |
|-------|-----------|-------------|
| `IsAuthenticationError(err)` | Chiave non valida, mancante o revocata | `401`, `403` (entrambi registrati come `401`) |
| `IsQuotaError(err)` | Qualsiasi risposta a cui l'API risponde con `402` | `402` |
| `IsRateLimitError(err)` | Limite di frequenza superato | `429` |
| `errors.As(err, &apiErr)` | Qualsiasi altro 4xx o 5xx | il codice effettivo |

Gli errori che non raggiungono mai la rete, come una coppia di conversione non supportata, una chiamata `Distill` con sia `URLs` sia `DiscoverFrom`, o una chiamata `PerceiveDirect` che chiede due artefatti, tornano come semplici valori `error` da `errors.New` o `fmt.Errorf`, non come `*APIError`. I codici di risposta sono catalogati nel [riferimento sui codici di errore](/it/docs/error-codes).

---

## Recupero dei timeout

I render lunghi di URL e le conversioni di documenti di grandi dimensioni possono superare il tetto di 60-120 secondi di un reverse proxy anche quando il job finisce senza problemi sul server. L'SDK ne esce con il polling, senza codice da parte tua:

1. Prima di ogni conversione di un singolo file e di un singolo URL, il client genera un UUIDv4 e lo invia come `job_id`.
2. Se quella richiesta torna con un codice `>= 500`, il client passa silenziosamente a `GET /v1/convert/status/{job_id}`, interrogandolo ogni 3 secondi.
3. Su `success` restituisce il risultato. Su `failed` restituisce un `*APIError` con stato `500` e il messaggio del server.
4. La scadenza del polling è di 5 minuti, dopodiché ottieni un `*APIError` con stato `504` e il messaggio `Conversion timed out`.

`ConversionResult.JobID` è sempre valorizzato, anche quando il percorso sincrono è andato a buon fine e la risposta lo ha omesso, così puoi passarlo tu stesso a `GetJobStatus`:

```go
status, err := client.GetJobStatus(ctx, result.JobID)
switch status.Status {
case enconvert.JobStatusSuccess:
    fmt.Println(status.PresignedURL)
case enconvert.JobStatusFailed:
    log.Println(status.Error)
}
```

<div class="alert alert-info">
<strong>I batch sui siti web non partecipano, di proposito.</strong> <code>ConvertWebsiteToPDF</code> e <code>ConvertWebsiteToScreenshot</code> non hanno una riga di job per singola pagina da interrogare, quindi un 5xx lì emerge subito invece di essere riprovato. Nemmeno i metodi V2 usano il fallback sui job. Il polling gira dentro il <code>context.Context</code> che passi, quindi cancellare il context interrompe subito l'attesa.
</div>

---

## Configurazione

```go
client, err := enconvert.New(os.Getenv("ENCONVERT_API_KEY"),
    enconvert.WithTimeout(300*time.Second),
    enconvert.WithBaseURL("https://api.enconvert.com"),
)
```

| Input del costruttore | Tipo | Default | Descrizione |
|-------------------|------|---------|-------------|
| `apiKey` (primo argomento) | `string` | obbligatorio | Chiave API privata. `New` restituisce un errore quando è vuota. |
| `WithTimeout(d)` | `time.Duration` | `300 * time.Second` | Imposta `Timeout` sull'`*http.Client` interno, coprendo anche i download. |
| `WithBaseURL(u)` | `string` | `https://api.enconvert.com` | Override per un gateway self-hosted. Le barre finali vengono rimosse. |

Le scadenze per singola chiamata si sovrappongono al timeout del client tramite il context: avvolgilo con `context.WithTimeout(context.Background(), 30*time.Second)` e passa quello come primo argomento.

<div class="alert alert-warning">
<strong>Non scrivere mai la chiave API hardcoded.</strong> Leggila da una variabile d'ambiente o dal tuo secret manager, e tienila sul server. Chiunque possieda la tua chiave privata può eseguire conversioni sul tuo progetto.
</div>

Il client si può condividere in sicurezza tra goroutine: contiene un `*http.Client` e nessuno stato mutabile per richiesta. Creane uno all'avvio e riusalo. Vedi [autenticazione](/it/docs/authentication) per i tipi di chiave e la rotazione.

---

## Forma del risultato

Le conversioni di un singolo file e di un singolo URL restituiscono un `ConversionResult`:

```go
type ConversionResult struct {
    PresignedURL          string   // URL di download firmato per l'output
    ObjectKey             string   // chiave dell'oggetto nello storage
    Filename              string   // nome file lato server
    FileSize              *int64   // byte, nil quando l'API lo omette
    ConversionTimeSeconds *float64 // nil quando l'API lo omette
    JobID                 string   // sempre impostato dal client
}
```

Gli URL pre-firmati sono di breve durata. Passa `SaveTo` per far scrivere i byte su disco in streaming dall'SDK, oppure recupera tu stesso l'URL e archivia il file nel tuo bucket se ti serve un accesso a lungo termine. Il download deliberatamente non porta con sé la tua chiave API, dato che un URL pre-firmato si autentica da solo e inoltrare la chiave a un host di storage la esporrebbe.

Gli artefatti V2 arrivano come valori `V2OutputArtifact` indicizzati per nome di output, ciascuno con `URL` (pre-firmato per 15 minuti e rifirmato a ogni GET di stato), `ObjectKey`, `SizeBytes`, `ContentType` ed `ExpiresIn` (900 secondi per impostazione predefinita). `PerceiveResult` li avvolge insieme ai metadati di affidabilità: `RenderQuality`, `StatusCode`, `Deductions`, `CacheHit`, `Warnings`, `ContentHash`, `URLFinal`, `Structured`, `ExtractionTier`, `Tokens`, `CostCents`, `DurationMs` e `OptionsEcho`, che restituisce l'eco delle opzioni effettivamente rispettate dal server, con i segreti ridotti a booleani.

---

## Sorgente e segnalazioni

- **Modulo e sorgente:** [github.com/conversionapi/go-sdk](https://github.com/conversionapi/go-sdk)
- **Versione:** esposta a runtime come costante `enconvert.Version`
- **Licenza:** MIT, nessuna dipendenza di terze parti

Letture correlate: [tutti gli SDK](/it/docs/sdks), [panoramica V2](/it/docs/v2-overview), [perceive](/it/docs/v2-perceive), [discover](/it/docs/v2-discover), [lookup](/it/docs/v2-lookup), [distill](/it/docs/v2-distill), [ingest](/it/docs/v2-ingest), [watch](/it/docs/v2-watch), [panoramica degli endpoint](/it/docs/endpoints-overview), [parametri e opzioni](/it/docs/parameters-options) e la tua [dashboard](/it/dashboard) per le chiavi.

---

## Domande frequenti

### Come converto file in Go?

Esegui `go get github.com/conversionapi/go-sdk`, costruisci un client con `enconvert.New(os.Getenv("ENCONVERT_API_KEY"))`, poi chiama un metodo tipizzato come `ConvertDocument`, `ConvertImage` o `ConvertURLToPDF`. Passa `SaveTo` nella struct delle opzioni e l'SDK scrive il file finito direttamente su quel percorso in streaming, creando le directory superiori quando serve.

### Come converto un URL in PDF con Go?

Chiama `client.ConvertURLToPDF(ctx, url, enconvert.URLToPDFOptions{SaveTo: "page.pdf"})`. Imposta `SinglePage: enconvert.Bool(false)` per impaginare invece di produrre una sola pagina continua, e passa `PDFOptions` per dimensione pagina, orientamento, margini, scala, scala di grigi, intestazioni e piè di pagina.

### Come converto DOCX in PDF con Go?

`client.ConvertDocument(ctx, enconvert.FilePath("report.docx"), enconvert.ConvertDocumentOptions{SaveTo: "report.pdf"})`. Il formato di output vale `pdf` per impostazione predefinita, quindi puoi lasciare `OutputFormat` vuoto. Lo stesso metodo gestisce input XLSX, PPTX, ODT, ODS, ODP, OTS, Pages, Numbers, HTML, Markdown, CSV, JSON, XML, YAML e TOML.

### Come converto HEIC in WebP con Go?

`client.ConvertImage(ctx, enconvert.FilePath("photo.heic"), enconvert.ConvertImageOptions{OutputFormat: "webp", SaveTo: "photo.webp"})`. Il formato di input viene letto dall'estensione del nome file, e tutte e 20 le coppie ordinate tra `jpeg`, `png`, `svg`, `heic` e `webp` funzionano allo stesso modo. Le coppie non supportate falliscono in locale prima che venga inviata qualsiasi richiesta.

### L'SDK Go tira dentro dipendenze di terze parti?

No. `go.mod` dichiara il modulo e un requisito minimo di Go 1.21, e nient'altro. Il client è costruito su `net/http`, `encoding/json`, `mime/multipart` e `crypto/rand` della libreria standard, quindi non aggiunge alcuna supply chain transitiva alla tua build.

### Come estraggo una pagina web in Markdown pulito con Go?

Due possibilità. `client.ConvertURLToMarkdown` restituisce Markdown GitHub-Flavored con frontmatter YAML ed è il percorso più semplice. `client.V2.Perceive` con `Outputs: []enconvert.PerceiveOutputName{enconvert.PerceiveOutputMarkdown}` ti dà lo stesso Markdown più un punteggio `RenderQuality`, `Deductions`, `Warnings` e la possibilità di aggiungere screenshot, link o estrazione strutturata nello stesso render.

### Cosa significa la qualità del render e perché conviene controllarla?

`RenderQuality` è un `*float64` da 0.0 a 1.0 allegato a ogni lettura V2. Scende quando la pagina non si è renderizzata in modo onesto: una sfida anti-bot, un muro di login, un banner dei cookie sopra uno shell vuoto, o uno stato HTTP di errore. Il contenuto viene comunque restituito invece di essere inghiottito, quindi controlla il punteggio (e la mappa `Deductions` che nomina ogni penalità) prima di dare il testo in pasto a un modello.

### Come imposto un timeout per singola richiesta o annullo una conversione in Go?

Ogni metodo accetta un `context.Context` come primo argomento. Avvolgilo con `context.WithTimeout` o `context.WithCancel` per il controllo sulla singola chiamata; `WithTimeout` sul costruttore imposta il timeout di base del client HTTP per tutte le chiamate, incluso il download di un file con `SaveTo`.

### Cosa succede quando una conversione lunga supera il timeout del proxy?

L'SDK invia un `job_id` generato dal client con ogni conversione di un singolo file e di un singolo URL. Se la richiesta restituisce un codice `>= 500`, interroga `GET /v1/convert/status/{job_id}` ogni 3 secondi per un massimo di 5 minuti, restituendo il risultato su `success` e un `*APIError` su `failed`. Superare la scadenza produce un `*APIError` con stato `504`. Gli invii di batch di interi siti saltano deliberatamente questo fallback.

### Posso distribuire l'SDK Go dentro un client desktop o mobile?

No. Si autentica con una chiave API privata, e gli endpoint V2 rifiutano senza appello le chiavi pubbliche. Tieni il client su un server che controlli e lascia che la tua app parli con quello. Vedi [autenticazione](/it/docs/authentication) per il modello delle chiavi.

### Il client Go si può usare in sicurezza da più goroutine?

Sì. `*enconvert.Client` avvolge un solo `*http.Client` e non mantiene alcuno stato mutabile per richiesta, quindi creane uno all'avvio e condividilo ovunque.
