---
seo_title: SDK C# e .NET Conversione File | Client API NuGet EnConvert
meta_desc: SDK C# e .NET ufficiale di EnConvert per .NET 8+. Metodi async per la conversione file e il namespace V2 con perceive, discover, distill, ingest e watch.
keywords: sdk conversione file c# .net, convertire file in c# .net, url in pdf c# .net, api web scraping c# .net, docx in pdf c# .net, sdk csharp enconvert, client api conversione file nuget, heic in webp c#, url in markdown dotnet, api screenshot sito web dotnet, pipeline ingestion rag c#
---

# SDK C# e .NET per la Conversione dei File

`Enconvert` è il client C# e .NET ufficiale per l'API EnConvert, pubblicato su NuGet e destinato a .NET 8 o versioni successive. Converte file da C#: da URL a PDF, da DOCX a PDF, da HEIC a WebP, da JSON a YAML, qualsiasi documento in Markdown e interi siti web in un unico ZIP. Lo stesso client porta un namespace `V2` per il web scraping e la web intelligence, quindi una sola chiave copre perceive, discover, lookup, distill, ingest e watch. Ogni chiamata è async e annullabile, ogni risposta è un record tipizzato, e il pacchetto non trascina con sé alcuna dipendenza di terze parti.

<div class="alert alert-info">
<strong>NuGet:</strong> <code>Enconvert</code> · <strong>Sorgente:</strong> <a href="https://github.com/conversionapi/csharp-sdk">conversionapi/csharp-sdk</a> · <strong>Runtime:</strong> .NET 8+ · <strong>Dipendenze:</strong> nessuna oltre la BCL
</div>

---

## Installazione

```bash
dotnet add package Enconvert
```

L'assembly ha come target `net8.0` con i nullable reference type abilitati ed è costruito solo su `System.Net.Http` e `System.Text.Json`.

## Avvio rapido

```csharp
using Enconvert;

using var client = new EnconvertClient(Environment.GetEnvironmentVariable("ENCONVERT_API_KEY")!);

// Converte una pagina live in PDF e la trasmette su disco.
var pdf = await client.ConvertUrlToPdfAsync("https://example.com", new UrlToPdfOptions { SaveTo = "page.pdf" });
Console.WriteLine(pdf.PresignedUrl);

// Legge una pagina come dovrebbe fare il tuo agente, con un punteggio di qualità allegato.
var op = await client.V2.PerceiveAsync("https://example.com", new PerceiveOptions { Outputs = new[] { "markdown", "structured" } });
Console.WriteLine($"{op.Outputs["markdown"].Url} scored {op.RenderQuality}");
```

`EnconvertClient` implementa `IDisposable`, quindi dichiaralo con `using` oppure registralo come singleton. Si autentica con una chiave API privata, il che significa che il suo posto è sul server: non spedire mai la chiave dentro una build desktop, mobile o Blazor WebAssembly. La configurazione delle chiavi è trattata in [Autenticazione](/it/docs/authentication).

---

## Cosa espone il client

| Membro | Endpoint | Restituisce |
|--------|----------|---------|
| `ConvertUrlToPdfAsync(url, opts?)` | `POST /v1/convert/url-to-pdf` | `ConversionResult` |
| `ConvertUrlToScreenshotAsync(url, opts?)` | `POST /v1/convert/url-to-screenshot` | `ConversionResult` |
| `ConvertUrlToMarkdownAsync(url, opts?)` | `POST /v1/convert/url-to-markdown` | `ConversionResult` |
| `ConvertImageAsync(file, opts)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `ConvertDocumentAsync(file, opts?)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `ConvertToMarkdownAsync(file, opts?)` | `POST /v1/convert/anything-to-markdown` | `ConversionResult` |
| `ConvertToPdfAsync(file, opts?)` | `POST /v1/convert/anything-to-pdf` | `ConversionResult` |
| `ConvertWebsiteToPdfAsync(url, opts?)` | `POST /v1/convert/website-to-pdf` | `BatchSubmission` |
| `ConvertWebsiteToScreenshotAsync(url, opts?)` | `POST /v1/convert/website-to-screenshot` | `BatchSubmission` |
| `GetJobStatusAsync(jobId)` | `GET /v1/convert/status/{jobId}` | `JobStatus` |
| `GetBatchStatusAsync(batchId)` | `GET /v1/convert/batch/{batchId}` | `BatchStatus` |
| `WaitForBatchAsync(batchId, opts?)` | `GET /v1/convert/batch/{batchId}` (con polling) | `BatchStatus` |
| `V2` | la superficie `/v2/*` | `EnconvertV2` |

`client.V2` raggruppa 23 metodi su sei funzionalità:

| Funzionalità | Metodi | Riferimento |
|------------|---------|-----------|
| Perceive | `PerceiveAsync`, `GetPerceiveOperationAsync`, `PerceiveBatchAsync`, `GetPerceiveBatchAsync`, `PerceiveDirectAsync`, `DownloadPerceiveArtifactAsync` | [/it/docs/v2-perceive](/it/docs/v2-perceive) |
| Discover | `DiscoverAsync` | [/it/docs/v2-discover](/it/docs/v2-discover) |
| Lookup | `LookupAsync` | [/it/docs/v2-lookup](/it/docs/v2-lookup) |
| Distill | `DistillAsync` | [/it/docs/v2-distill](/it/docs/v2-distill) |
| Ingest | `IngestAsync`, `IngestFilesAsync`, `ListIngestJobsAsync`, `GetIngestJobAsync`, `CancelIngestJobAsync`, `RetryIngestWebhookAsync`, `GetWebhookSecretAsync`, `RotateWebhookSecretAsync` | [/it/docs/v2-ingest](/it/docs/v2-ingest) |
| Watch | `CreateWatcherAsync`, `ListWatchersAsync`, `GetWatcherAsync`, `GetWatcherSnapshotsAsync`, `UpdateWatcherAsync`, `DeleteWatcherAsync` | [/it/docs/v2-watch](/it/docs/v2-watch) |

Ogni metodo accetta un `CancellationToken` opzionale in coda. Le opzioni sono record con proprietà `init`, quindi costruiscile con un object initializer e riusale con `with`. Gli altri client per linguaggio si trovano nell'[indice degli SDK](/it/docs/sdks); la superficie REST grezza è in [Panoramica degli endpoint](/it/docs/endpoints-overview).

---

## Conversione dei file

### ConvertUrlToPdfAsync

Esegue il rendering in PDF di qualsiasi URL raggiungibile.

```csharp
var result = await client.ConvertUrlToPdfAsync("https://example.com", new UrlToPdfOptions
{
    SinglePage = false,
    ViewportWidth = 1440,
    PdfOptions = new PdfOptions { PageSize = "A4", Orientation = "landscape" },
    SaveTo = "report.pdf",
});

Console.WriteLine($"{result.Filename} ({result.FileSize} bytes)");
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `SaveTo` | `string?` | -- | Percorso locale su cui trasmettere il PDF in streaming. Le directory padre mancanti vengono create. |
| `SinglePage` | `bool?` | `true` | `true` produce una singola pagina continua. `false` pagina utilizzando `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` | Viewport del browser in pixel. |
| `LoadMedia`, `EnableScroll` | `bool?` | `true` | Attende immagini e video prima della cattura; scorre dall'alto verso il basso perché scattino i caricamenti lazy. |
| `OutputFilename` | `string?` | auto | Sovrascrive il nome file generato. |
| `Auth` | `HttpBasicAuth?` | -- | Credenziali HTTP Basic per una pagina protetta da login. |
| `Cookies`, `Headers` | `IReadOnlyList<BrowserCookie>?`, `IReadOnlyDictionary<string, string>?` | -- | Fino a 50 cookie iniettati prima del rendering, e fino a 20 header di richiesta aggiuntivi. Gli header hop-by-hop vengono rifiutati. |

<div class="alert alert-warning">
<strong>Non combinare <code>Auth</code> con un tuo header <code>Authorization</code>.</strong> L'API rifiuta quel conflitto invece di indovinare quale credenziale vince. Scegline una.
</div>

### ConvertUrlToScreenshotAsync

Cattura un PNG di qualsiasi URL.

```csharp
await client.ConvertUrlToScreenshotAsync("https://example.com", new UrlToScreenshotOptions
{
    ViewportWidth = 1440,
    SaveTo = "screenshot.png",
});
```

`UrlToScreenshotOptions` accetta le stesse opzioni di viewport, media, scroll, nome file e accesso di `ConvertUrlToPdfAsync`, meno `SinglePage` e `PdfOptions`.

### ConvertUrlToMarkdownAsync

Estrae Markdown pulito in stile GitHub-Flavored da un URL. Navigazione, footer, pubblicità e script vengono rimossi, il corpo principale dell'articolo viene mantenuto e viene anteposto un frontmatter YAML (titolo, descrizione, url, link, immagini).

```csharp
await client.ConvertUrlToMarkdownAsync("https://example.com/article", new UrlToMarkdownOptions { SaveTo = "article.md" });
```

`UrlToMarkdownOptions` condivide le stesse opzioni di rendering e di accesso. Per letture destinate agli agenti che richiedono anche un punteggio di qualità ed estrazione strutturata, usa invece [Perceive](#perceive).

### ConvertImageAsync

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

```csharp
// Da un percorso.
await client.ConvertImageAsync("photo.heic", new ConvertImageOptions { OutputFormat = "webp", SaveTo = "photo.webp" });

// Da byte, con un nome file esplicito perché il formato di input possa essere rilevato.
var bytes = await File.ReadAllBytesAsync("scan.pdf");
await client.ConvertImageAsync(new FileInput(bytes, "scan.pdf"), new ConvertImageOptions
{
    OutputFormat = "jpeg",
    SaveTo = "scan.jpeg",
});
```

Tre overload accettano un `string` di percorso, un `byte[]` grezzo oppure un record `FileInput`. Il formato di input viene ricavato dall'estensione del nome file, quindi preferisci `FileInput` quando hai i byte: l'overload con il semplice `byte[]` invia `upload.bin`, che funziona solo sugli endpoint con rilevamento automatico.

| Opzione | Tipo | Obbligatorio | Descrizione |
|--------|------|----------|-------------|
| `OutputFormat` | `string` | Sì | Formato di destinazione. `jpg`, `yml`, `htm` e `md` vengono normalizzati ai loro nomi canonici. |
| `SaveTo` | `string?` | -- | Percorso locale su cui trasmettere il risultato in streaming. |
| `OutputFilename` | `string?` | -- | Sovrascrive il nome file generato. |

Le coppie non supportate generano `ArgumentException` prima che venga effettuata qualsiasi richiesta HTTP, quindi un refuso non costa nulla.

### ConvertDocumentAsync

Converte documenti e formati di dati. `OutputFormat` vale `"pdf"` per impostazione predefinita.

```csharp
// da docx a pdf
await client.ConvertDocumentAsync("report.docx", new ConvertDocumentOptions { SaveTo = "report.pdf" });

// da json a yaml
await client.ConvertDocumentAsync("data.json", new ConvertDocumentOptions { OutputFormat = "yaml", SaveTo = "data.yaml" });

// da markdown a pdf con geometria di pagina
await client.ConvertDocumentAsync("README.md", new ConvertDocumentOptions
{
    PdfOptions = new PdfOptions { PageSize = "A4", Margins = new PdfMargins { Top = 20, Bottom = 20 } },
    SaveTo = "readme.pdf",
});
```

**Input supportati:** `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.html`, `.htm`, `.odt`, `.ods`, `.odp`, `.ots`, `.pages`, `.numbers`, `.md`, `.markdown`, `.csv`, `.json`, `.xml`, `.yaml`, `.yml`, `.toml`. EPUB non ha una coppia documentale dedicata, quindi invia i file `.epub` a [`ConvertToPdfAsync`](#converttopdfasync) oppure a [`ConvertToMarkdownAsync`](#converttomarkdownasync).

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `OutputFormat` | `string?` | `"pdf"` | Formato di destinazione. |
| `SaveTo` | `string?` | -- | Percorso locale su cui trasmettere il risultato in streaming. |
| `OutputFilename` | `string?` | -- | Sovrascrive il nome file generato. |
| `PdfOptions` | `PdfOptions?` | -- | Impostazioni di pagina. Hanno effetto solo quando l'output è PDF. |

Come `ConvertImageAsync`, questo metodo ha overload per percorso, `byte[]` e `FileInput`.

### ConvertToMarkdownAsync

Invia quasi qualsiasi documento all'endpoint Markdown con rilevamento automatico: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT e MD, oltre ai formati office legacy e ODF. Qui le immagini non sono supportate.

```csharp
await client.ConvertToMarkdownAsync("handbook.docx", new ConvertToMarkdownOptions { SaveTo = "handbook.md" });
```

Il formato viene rilevato lato server, quindi non c'è alcun controllo dell'estensione lato client né alcuna opzione PDF su questo endpoint. `ConvertToMarkdownOptions` porta solo `SaveTo` e `OutputFilename`. L'output è un unico file `.md` strutturato per intestazioni, il che ne fa un solido primo stadio in una pipeline RAG: un chunker semantico può suddividere sulla gerarchia di intestazioni del documento invece che su conteggi arbitrari di caratteri.

### ConvertToPdfAsync

L'altro endpoint con rilevamento automatico: office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, testo semplice, immagini raster, SVG, EPUB, oppure un PDF esistente passato in passthrough per la normalizzazione.

```csharp
// Slide in PDF.
await client.ConvertToPdfAsync("slides.pptx", new ConvertToPdfOptions { SaveTo = "slides.pdf" });

// PDF in passthrough, convertito in scala di grigi.
await client.ConvertToPdfAsync("scan.pdf", new ConvertToPdfOptions
{
    PdfOptions = new PdfOptions { Grayscale = true },
    SaveTo = "gray.pdf",
});
```

<div class="alert alert-warning">
<strong>Qui viene rispettato solo <code>Grayscale</code>.</strong> L'endpoint anything-to-pdf ignora il resto di <code>PdfOptions</code>. Quando ti servono dimensione pagina, orientamento, margini oppure intestazione e piè di pagina, instrada HTML e Markdown attraverso <code>ConvertDocumentAsync</code>, oppure renderizza la pagina con <code>ConvertUrlToPdfAsync</code>.
</div>

`ConvertToPdfOptions` porta `SaveTo`, `OutputFilename` e `PdfOptions`, e ha gli stessi tre overload di input dei metodi qui sopra.

### Batch di interi siti

`ConvertWebsiteToPdfAsync` e `ConvertWebsiteToScreenshotAsync` individuano ogni pagina di un sito, convertono ciascuna in background e raccolgono i risultati in un unico ZIP. Entrambi sono solo async e richiedono una chiave API privata.

```csharp
var batch = await client.ConvertWebsiteToPdfAsync("https://example.com", new WebsiteToPdfOptions
{
    CrawlMode = "sitemap",
    ExcludePatterns = new[] { "/blog/tag/" },
    NotificationEmail = "ops@example.com",
});
Console.WriteLine($"{batch.BatchId}: {batch.UrlCount} pages via {batch.DiscoveryMethod}");

// Blocca finché lo ZIP non è pronto, poi salvalo.
var status = await client.WaitForBatchAsync(batch.BatchId, new WaitForBatchOptions { SaveTo = "site.zip" });
Console.WriteLine($"{status.Completed} of {status.Total} converted, {status.Failed} failed");
```

Per interrogarlo secondo i tuoi tempi, chiama invece `GetBatchStatusAsync(batchId)` e leggi `ZipDownloadUrl` una volta che `Status` esce da `"processing"`.

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `CrawlMode` | `string?` | `"auto"` | `"auto"`, `"sitemap"` (solo sitemap.xml) oppure `"full"` (sitemap più un crawl in ampiezza). |
| `IncludePatterns`, `ExcludePatterns` | `IReadOnlyList<string>?` | -- | Sottoponi al crawl, oppure salta, solo gli URL che corrispondono a questi pattern. Modalità full crawl. |
| `NotificationEmail`, `CallbackUrl` | `string?` | proprietario del progetto, -- | Indirizzo avvisato al termine del batch, e webhook chiamato in POST al completamento. |
| `SinglePage`, `PdfOptions` | `bool?`, `PdfOptions?` | -- | Solo per i batch PDF. Le opzioni di viewport, media, scroll e accesso sono condivise con i metodi su URL singolo. |

`WaitForBatchOptions` prende `IntervalMs` (predefinito `5_000`), `TimeoutMs` (predefinito `1_800_000`, cioè 30 minuti) e `SaveTo`. Sforare il limite genera `ApiException` con stato `504`.

### GetJobStatusAsync

Interroga un singolo job di conversione tramite id.

```csharp
var status = await client.GetJobStatusAsync("job_abc123");

if (status.Status == "success") Console.WriteLine(status.PresignedUrl);
else if (status.Status == "failed") Console.Error.WriteLine(status.Error);
```

<div class="alert alert-info">
<strong>Raramente ti serve direttamente.</strong> Il client interroga già lo stato del job per te quando una richiesta sincrona muore sul proxy. Vedi <a href="#timeout-recovery">Recupero dei timeout</a>.
</div>

### Coppie di conversione supportate

L'SDK rispecchia la mappa dei convertitori del gateway e implementa 43 coppie tipizzate `{input}-to-{output}`.

| Input | Output |
|-------|---------|
| `json` | `csv`, `toml`, `xml`, `yaml` |
| `xml` | `csv`, `json` |
| `yaml` | `json` |
| `csv` | `json`, `xml` |
| `toml` | `json` |
| `markdown` | `html`, `pdf` |
| `html` | `pdf` |
| `doc`, `excel`, `ppt`, `odt`, `ods`, `odp`, `ots`, `pages`, `numbers` | `pdf` |
| `jpeg`, `png`, `svg`, `heic`, `webp` | tra loro, tutte le 20 coppie |
| `pdf` | `jpeg` |

La classe statica `Formats` espone la stessa tabella, così puoi validare prima di costruire un'interfaccia o una coda di job:

```csharp
Formats.ValidOutputsFor("json");        // ["csv", "toml", "xml", "yaml"]
Formats.ValidOutputsFor("pdf");         // ["jpeg"]
Formats.ImplementedConversions;         // l'insieme completo dei 43 nomi di endpoint
Formats.NormalizeOutputFormat(".JPG");  // "jpeg"
```

Tutto ciò che è fuori da questa tabella passa da `ConvertToPdfAsync` oppure `ConvertToMarkdownAsync`, che rilevano il formato lato server. Il riferimento completo dei parametri si trova in [Parametri e opzioni](/it/docs/parameters-options).

---

## Web intelligence (V2)

Ogni lettura V2 porta con sé `RenderQuality`, un punteggio da 0.0 a 1.0 che dice quanto onestamente la pagina è stata renderizzata. Una challenge anti-bot, un cookie wall o un login wall, un errore HTTP oppure uno shell vuoto di una single-page app tornano con un punteggio basso, una mappa `Deductions` popolata che nomina i controlli scattati e un elenco `Warnings`, mentre il contenuto viene comunque restituito. È proprio questo il punto: una lettura difettosa viene segnalata invece di entrare silenziosamente nel contesto del tuo agente. Lo stesso punteggio compare sui risultati di perceive, sugli item di distill, sui risultati di lookup percepiti automaticamente e sugli snapshot dei watcher. I concetti sono trattati nella [panoramica V2](/it/docs/v2-overview). Gli endpoint V2 richiedono una chiave API privata; le chiavi pubbliche vengono rifiutate.

### Perceive

Esegue il rendering di un URL in artefatti pronti per gli agenti.

```csharp
var op = await client.V2.PerceiveAsync("https://example.com", new PerceiveOptions
{
    Outputs = new[] { "markdown", "screenshot", "structured" },
    Extract = new[] { "tables", "metadata" },
    OnlyMainContent = true,
});

Console.WriteLine(op.RenderQuality);            // da 0.0 a 1.0
Console.WriteLine(op.StatusCode);               // stato HTTP a monte
Console.WriteLine(op.Outputs["markdown"].Url);  // firmato per 15 minuti
Console.WriteLine(op.Structured);               // JsonObject, la forma è la tua

foreach (var (check, penalty) in op.Deductions) Console.WriteLine($"deduction {check}: {penalty}");
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `Outputs` | `IReadOnlyList<string>?` | `["markdown", "structured"]` | Uno o più tra `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. |
| `Extract` | `IReadOnlyList<string>?` | -- | Obiettivi euristici: `tables`, `prices`, `contacts`, `metadata`, `main_content`, `headings`, `structured_data`, `technologies`, `all`. |
| `Schema` | `JsonObject?` | -- | Schema JSON che guida l'estrazione strutturata di livello LLM. |
| `WaitFor`, `WaitTimeoutMs` | `string?`, `int?` | --, `30000` | Un selettore CSS (eventualmente `css:...`) oppure `js:<expr>` da attendere prima della cattura, con un limite da 0 a 60000 ms. |
| `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` | vedi [sopra](#converturltopdfasync) | -- | Header aggiuntivi, cookie iniettati, credenziali HTTP Basic. |
| `CacheMode` | `string?` | `"enabled"` | `"enabled"` riutilizza una cache di 1 ora, `"bypass"` la salta, `"refresh"` rifà il rendering. |
| `PdfOptions` | `PdfOptions?` | -- | Ha effetto solo quando `Outputs` contiene `pdf`. |
| `BlockResources` | `IReadOnlyList<string>?` | -- | Tipi di risorsa da saltare: `image`, `media`, `font`, `stylesheet`, `script`. |
| `RespectRobots`, `Mobile` | `bool?` | -- | Rispetta robots.txt; emula un dispositivo mobile. |
| `OnlyMainContent` | `bool?` | `true` | Rimuove navigazione, header, footer e banner dei cookie dall'artefatto markdown e dall'estrazione `main_content`. |
| `DirectDownload` | `bool?` | -- | Trasmette i byte dell'artefatto invece dell'envelope JSON. Preferisci `PerceiveDirectAsync`. |

`ProxyUrl`, `Geolocation` e `ActionChain` esistono su `PerceiveOptions` ma non sono ancora disponibili lato server e attualmente tornano come `422`.

Rifirma in seguito gli URL degli artefatti, raggruppa fino a 1000 URL, oppure trasmetti i byte senza il round trip dell'URL firmato:

```csharp
// Gli URL degli artefatti vengono rifirmati a ogni recupero dell'operazione.
var again = await client.V2.GetPerceiveOperationAsync(op.OperationId);

// Batch: i piccoli girano inline, quelli più grandi tornano "queued" quindi li interroghi.
var batch = await client.V2.PerceiveBatchAsync(
    new[] { "https://a.example.com", "https://b.example.com" },
    new PerceiveBatchOptions { Outputs = new[] { "markdown" }, OutputMode = "zip" });

var done = await client.V2.GetPerceiveBatchAsync(batch.JobId);
Console.WriteLine($"{done.Completed}/{done.Total} done, {done.Failed} failed, zip {done.Zip?.Url}");

// Download diretto: serve esattamente un output che produca artefatti.
var direct = await client.V2.PerceiveDirectAsync("https://example.com", new PerceiveOptions { Outputs = new[] { "pdf" } });
await File.WriteAllBytesAsync(direct.Filename ?? "page.pdf", direct.Content);

// Riscarica un artefatto archiviato di un'operazione precedente.
var artifact = await client.V2.DownloadPerceiveArtifactAsync(op.OperationId, "markdown");
```

`PerceiveDirectAsync` genera `ArgumentException` localmente a meno che non venga richiesto esattamente uno tra `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links` e `images`, dato che `structured` è JSON inline e non un file archiviato. Entrambi i metodi diretti restituiscono un `PerceiveDirectResult` che porta `Content`, `ContentType`, `Filename`, `OperationId`, `ObjectKey`, `CacheHit`, `RenderQuality`, `SourceStatusCode`, `ContentHash` e `WarningsCount`. `DownloadPerceiveArtifactAsync` richiede il nome dell'output solo quando l'operazione ha prodotto più di un artefatto, e restituisce `410` quando l'artefatto supera la sua finestra di conservazione.

### Discover

Enumera gli URL di un sito senza alcun rendering nel browser, il che lo rende molto più economico di un crawl.

```csharp
var found = await client.V2.DiscoverAsync("https://example.com", new DiscoverOptions
{
    Mode = "hybrid",
    MaxUrls = 200,
    ExcludePatterns = new[] { "/tag/" },
});

Console.WriteLine($"{found.Total} urls, truncated: {found.Truncated}");
foreach (var url in found.Urls) Console.WriteLine(url);
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `Mode` | `string?` | `"hybrid"` | `"sitemap"`, `"crawl"` oppure `"hybrid"` (sitemap più crawl HTTP). |
| `MaxUrls` | `int?` | `100` | Da 1 a 1000. |
| `MaxDepth` | `int?` | `2` | Profondità di crawl, da 1 a 5. |
| `IncludePatterns`, `ExcludePatterns` | `IReadOnlyList<string>?` | -- | Allowlist e denylist regex, massimo 50 voci ciascuna. La denylist viene applicata per seconda. |
| `SameDomainOnly`, `RespectRobots` | `bool?` | `true`, -- | Resta sul dominio seed; rispetta robots.txt durante la discovery. |

`DiscoverResult` riporta `Total`, `Urls`, `PagesCrawled`, `Truncated`, `RobotsRespected`, una mappa di conteggi per sorgente in `Sources` e `Warnings`.

### Lookup

Esegue una ricerca web categorizzata e, facoltativamente, renderizza i risultati migliori nella stessa chiamata.

```csharp
var search = await client.V2.LookupAsync("best static site generators", new LookupOptions
{
    Category = "web",
    NumResults = 10,
    TimeFilter = "month",
    PerceiveTop = 3,
});

foreach (var hit in search.Results)
{
    Console.WriteLine($"{hit.Position}. {hit.Title} {hit.Url}");
    if (hit.Perceive is { } page)
        Console.WriteLine($"   quality {page.RenderQuality}, markdown {page.Outputs["markdown"].Url}");
}
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `Category` | `string?` | `"web"` | `"web"`, `"news"`, `"images"`, `"scholar"`, `"patents"`, `"maps"`. |
| `Country`, `Locale` | `string?` | -- | Codice paese `gl` di Google (`"us"`, `"in"`) e lingua dell'interfaccia `hl` (`"en"`). |
| `TimeFilter` | `string?` | -- | `"hour"`, `"day"`, `"week"`, `"month"`, `"year"`. |
| `NumResults`, `Page` | `int?` | `10`, `1` | Da 1 a 100 risultati; pagina da 1 a 10. |
| `Location`, `Autocorrect` | `string?`, `bool?` | --, `true` | Località in testo libero come `"Austin, Texas"`; lascia che il provider corregga i refusi evidenti. |
| `PerceiveTop` | `int?` | `0` | Da 0 a 10. Percepisce automaticamente i primi N URL dei risultati con un rendering completo nel browser. |

`LookupResult` porta anche `AnswerBox`, `KnowledgeGraph`, `PerceiveOperationIds` e `Warnings`.

### Distill

Estrazione strutturata guidata da schema. Quando ne fornisci uno, viene eseguito prima un passaggio CSS gratuito, e solo i campi che non copre passano al livello LLM.

```csharp
using System.Text.Json.Nodes;

var extraction = await client.V2.DistillAsync(new DistillOptions
{
    Urls = new[] { "https://example.com/pricing" },
    Schema = new JsonObject { ["plans"] = "list of plan names with monthly prices" },
    CssSchema = new CssSchema
    {
        BaseSelector = ".plan-card",
        Fields = new[]
        {
            new CssField { Name = "name", Type = "text", Selector = "h3" },
            new CssField { Name = "price", Type = "text", Selector = ".price" },
        },
    },
});

var first = extraction.Results[0];
Console.WriteLine($"{first.Data} via {first.ExtractionTier}");
Console.WriteLine($"css fields {first.FieldsFromCss}, llm fields {first.FieldsFromLlm}");

// Oppure individua prima un sito, poi distilla ogni pagina che trova.
await client.V2.DistillAsync(new DistillOptions
{
    DiscoverFrom = new DistillDiscoverFrom { Url = "https://example.com", Mode = "sitemap", MaxPages = 10 },
    Schema = new JsonObject { ["title"] = "page title" },
});
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `Schema` | `JsonObject` | obbligatorio | Un oggetto JSON-Schema oppure una mappa piatta `{field: description}`. Il campo `Data` della risposta rispecchia questa forma. |
| `Urls` | `IReadOnlyList<string>?` | -- | URL espliciti, massimo 50. Esattamente uno tra `Urls` e `DiscoverFrom`. |
| `DiscoverFrom` | `DistillDiscoverFrom?` | -- | `Url`, `Mode` (`"sitemap"`, `"crawl"`, `"hybrid"`) e `MaxPages` da 1 a 50, predefinito 10. |
| `CssSchema` | `CssSchema?` | -- | `BaseSelector` più `Fields`, con `Name` e `TargetField` opzionali. |
| `WaitFor`, `WaitTimeoutMs` | `string?`, `int?` | --, `30000` | Condizione di attesa prima dell'estrazione. |
| `Headers`, `Cookies`, `RespectRobots` | -- | -- | Stesse forme delle opzioni di rendering qui sopra. |

`CssField.Type` è uno tra `text`, `attribute`, `html`, `regex`, `nested`, `list` e `nested_list`, con annidamento fino a cinque livelli. Passare sia `Urls` sia `DiscoverFrom`, oppure nessuno dei due, genera `ArgumentException` prima che parta qualsiasi richiesta.

### Ingest

Trasforma un intero sito, o una pila di documenti caricati, in JSONL suddiviso in chunk che un vector store può leggere. Ingest è sempre asincrono.

```csharp
// Da un sito.
var job = await client.V2.IngestAsync(new IngestOptions
{
    Mode = "sitemap",
    Url = "https://docs.example.com",
    MaxPages = 100,
    Chunk = new IngestChunkOptions { MaxWords = 512 },
    WebhookUrl = "https://my.app/hooks/enconvert",
});

// Da file caricati: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT e MD, office legacy e ODF.
var fileJob = await client.V2.IngestFilesAsync(
    new[] { new FileInput(await File.ReadAllBytesAsync("handbook.pdf"), "handbook.pdf") },
    new IngestFilesOptions { Chunk = new IngestChunkOptions { MaxWords = 512 } });

// Esegui il polling finché il JSONL non è pronto.
var status = await client.V2.GetIngestJobAsync(job.JobId);
Console.WriteLine($"{status.Status}: {status.PagesProcessed}/{status.PagesDiscovered} pages, {status.TotalChunks} chunks");
if (status.Status == "completed") Console.WriteLine(status.OutputUrl);

var recent = await client.V2.ListIngestJobsAsync(new V2ListOptions { Limit = 20 });
await client.V2.CancelIngestJobAsync(job.JobId); // idempotente
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `Mode` | `string?` | `"urls"` | `"urls"`, `"sitemap"` oppure `"crawl"`. |
| `Url` | `string?` | -- | URL seed. Obbligatorio per `sitemap` e `crawl`, rifiutato per `urls`. |
| `Urls` | `IReadOnlyList<string>?` | -- | URL espliciti, massimo 1000. Obbligatorio per `urls`, rifiutato altrimenti. |
| `MaxPages`, `MaxDepth`, `SameDomainOnly` | `int?`, `int?`, `bool?` | `50`, `2`, `true` | Limite di discovery da 1 a 1000 per `sitemap` e `crawl`, profondità di crawl da 1 a 5, resta sul dominio seed. |
| `IncludePatterns`, `ExcludePatterns` | `IReadOnlyList<string>?` | -- | Allowlist e denylist regex. |
| `RespectRobots`, `WaitFor`, `WaitTimeoutMs` | -- | -- | Controlli di rendering per pagina. |
| `Chunk` | `IngestChunkOptions?` | -- | `MaxWords` da 32 a 4000, predefinito 512. `SentenceOverlap` da 0 a 10, predefinito 1. |
| `WebhookUrl` | `string?` | -- | Webhook di completamento, firmato con HMAC. |

Le regole sulle modalità vengono applicate lato client, quindi un job `urls` che imposta anche `Url` genera subito `ArgumentException`. Un job attraversa `queued`, `discovering`, `processing` e poi `completed`, `failed` oppure `canceled`. La consegna dei webhook è verificabile da un capo all'altro:

```csharp
var secret = await client.V2.GetWebhookSecretAsync();
Console.WriteLine($"{secret.SignatureHeader} using {secret.SignatureScheme}, replay tolerance {secret.ReplayToleranceSeconds}s");

await client.V2.RotateWebhookSecretAsync();  // le vecchie firme smettono di essere valide
var retry = await client.V2.RetryIngestWebhookAsync(job.JobId);
Console.WriteLine($"delivered: {retry.Delivered} after {retry.Attempts} attempts");
```

### Watch

Rifà il rendering di una pagina secondo una pianificazione e ti avvisa quando cambia.

```csharp
var watcher = await client.V2.CreateWatcherAsync("https://example.com/pricing", new WatchCreateOptions
{
    FrequencyMinutes = 60,
    DiffMode = "auto",
    WebhookUrl = "https://my.app/hooks/changes",
});

var page = await client.V2.ListWatchersAsync(new V2ListOptions { Limit = 20 });
var one = await client.V2.GetWatcherAsync(watcher.WatcherId);
var history = await client.V2.GetWatcherSnapshotsAsync(watcher.WatcherId, new SnapshotListOptions { Limit = 10 });
foreach (var snap in history.Snapshots)
    Console.WriteLine($"{snap.CheckedAt} changed: {snap.HasChanges} similarity: {snap.Similarity}");

await client.V2.UpdateWatcherAsync(watcher.WatcherId, new WatcherUpdate { Status = "paused" });
await client.V2.UpdateWatcherAsync(watcher.WatcherId, new WatcherUpdate { WebhookUrl = "" }); // lo cancella
await client.V2.DeleteWatcherAsync(watcher.WatcherId); // soft delete, idempotente
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `FrequencyMinutes` | `int?` | `60` | Da 60 a 43200. Il minimo orario è rigido. |
| `DiffMode` | `string?` | `"auto"` | `"auto"`, `"text"`, `"structured"`, `"tables"`, `"metadata"`. |
| `TrackFields` | `JsonObject?` | -- | Sottoinsieme di campi o selettori per il motore di diff. |
| `WebhookUrl` | `string?` | -- | Webhook sulle modifiche, firmato con HMAC. Impostalo a `""` in un aggiornamento per cancellarlo. |
| `NotifyEmail` | `bool?` | `true` | Invia un'email al proprietario del progetto quando ci sono modifiche. |

`WatcherUpdate` accetta anche `Status` (`"active"` oppure `"paused"`) e genera `ArgumentException` se passi un aggiornamento senza alcun campo impostato.

<div class="alert alert-warning">
<strong>I diff degli snapshot contengono contenuto di pagina non attendibile.</strong> <code>WatcherSnapshot.Changes</code> è JSON grezzo prelevato dalla pagina monitorata. Effettuane l'escape prima di renderizzarlo in una dashboard o in un'email.
</div>

---

## Opzioni PDF

`PdfOptions` è condiviso da `ConvertUrlToPdfAsync`, `ConvertDocumentAsync`, `ConvertToPdfAsync` (solo grayscale) e `PerceiveOptions` quando `pdf` è tra gli output.

```csharp
await client.ConvertUrlToPdfAsync("https://internal.example.com/report", new UrlToPdfOptions
{
    PdfOptions = new PdfOptions
    {
        PageSize = "A4",
        Orientation = "landscape",
        Margins = new PdfMargins { Top = 10, Bottom = 10, Left = 15, Right = 15 },
        Scale = 0.9,
        Header = new PdfHeaderFooter { Content = "Quarterly Report", Height = 15 },
        Footer = new PdfHeaderFooter { Content = "Confidential", Height = 12 },
    },
    Auth = new HttpBasicAuth { Username = "user", Password = "pass" },
    SaveTo = "report.pdf",
});
```

| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `PageSize` | `string?` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"` e affini. |
| `PageWidth`, `PageHeight` | `double?` | Dimensioni personalizzate. Impostati insieme, hanno la precedenza su `PageSize`. |
| `Orientation` | `string?` | `"portrait"` oppure `"landscape"`. |
| `Margins` | `PdfMargins?` | `Top`, `Bottom`, `Left`, `Right`, tutti opzionali. |
| `Scale` | `double?` | Scala di rendering, per esempio `0.9` per il 90 percento. |
| `Grayscale` | `bool?` | Post-elabora il PDF in scala di grigi. |
| `Header`, `Footer` | `PdfHeaderFooter?` | `Content` fino a 2000 caratteri, più `Height`. |

## Gestione degli errori

Ogni errore emerge come eccezione tipizzata, quindi i blocchi `catch` si leggono dal più specifico al meno specifico.

```csharp
using Enconvert;

try
{
    await client.V2.PerceiveAsync("https://example.com");
}
catch (AuthenticationException) { Console.Error.WriteLine("Invalid or missing API key."); }
catch (QuotaException) { Console.Error.WriteLine("Request rejected with 402."); }
catch (RateLimitException) { Console.Error.WriteLine("Too many requests, back off and retry."); }
catch (ApiException e) { Console.Error.WriteLine($"API error [{e.StatusCode}]: {e.Message}"); }
```

| Classe | Generata per | Codice di stato |
|-------|-----------|-------------|
| `AuthenticationException` | Chiave non valida, mancante o revocata | `401`, `403` (riportato come `401`) |
| `QuotaException` | Generata su HTTP 402 | `402` |
| `RateLimitException` | Rate limit superato | `429` |
| `ApiException` | Qualsiasi altro 4xx o 5xx | il codice effettivo |
| `EnconvertException` | Classe base di tutte le precedenti | -- |

La gerarchia va da `EnconvertException` ad `ApiException` (che porta `StatusCode`) fino alle tre classi specifiche, quindi un solo `catch (EnconvertException)` cattura tutto ciò che l'SDK genera. I messaggi vengono presi dal campo `detail` oppure `error` del corpo della risposta quando è presente. La validazione che l'SDK effettua localmente, come una coppia di conversione non supportata o una richiesta distill malformata, genera invece `ArgumentException` e non arriva mai alla rete. I codici di risposta sono catalogati in [Codici di errore](/it/docs/error-codes).

## Recupero dei timeout

I rendering di URL lunghi e le conversioni di documenti di grandi dimensioni possono superare un timeout di reverse proxy da 60 a 120 secondi anche quando la conversione stessa riesce. Il client assorbe la cosa per te:

1. Prima di ogni conversione di un singolo file o di un singolo URL, l'SDK genera un job id e lo invia con la richiesta.
2. Se quella richiesta torna con 5xx, l'SDK passa a interrogare `GET /v1/convert/status/{jobId}` ogni 3 secondi invece di fallire.
3. Un job registrato come `success` restituisce il normale `ConversionResult`. Un job registrato come `failed` genera `ApiException` con stato `500` e il messaggio di errore del server.
4. Il limite di tempo per il polling è di 5 minuti, superato il quale l'SDK genera `ApiException(504, "Conversion timed out")`.

Questo copre `ConvertUrlToPdfAsync`, `ConvertUrlToScreenshotAsync`, `ConvertUrlToMarkdownAsync` e tutti e quattro i metodi di upload file. Gli invii batch dei siti web sono esclusi di proposito, perché un invio fallito non ha una riga di job da interrogare e deve emergere subito. Quando una risposta omette `job_id`, l'SDK reinserisce l'id che ha generato, così `ConversionResult.JobId` è sempre utilizzabile con `GetJobStatusAsync`.

## Configurazione

```csharp
using var client = new EnconvertClient(
    apiKey: Environment.GetEnvironmentVariable("ENCONVERT_API_KEY")!,
    baseUrl: null,       // per impostazione predefinita https://api.enconvert.com
    timeoutMs: 300_000);
```

| Parametro | Tipo | Predefinito | Descrizione |
|-----------|------|---------|-------------|
| `apiKey` | `string` | obbligatorio | Chiave API privata. Un valore vuoto genera `ArgumentException`. |
| `baseUrl` | `string?` | `https://api.enconvert.com` | Sovrascrive l'URL base dell'API. Gli slash finali vengono rimossi. |
| `timeoutMs` | `int` | `300_000` | Timeout della richiesta in millisecondi, 5 minuti per impostazione predefinita. |

La chiave viaggia in un header `X-API-Key` su ogni richiesta. I download degli artefatti vanno direttamente agli URL firmati dello storage su un secondo `HttpClient` che non invia alcuna chiave e non applica alcun timeout, così uno ZIP di grandi dimensioni può essere trasmesso per tutto il tempo necessario. Rilascia il client una sola volta alla fine del processo, oppure registralo come singleton invece di costruirne uno per richiesta.

<div class="alert alert-warning">
<strong>Non inserire mai la chiave direttamente nel codice.</strong> Leggila da una variabile d'ambiente, dagli user secrets o dal tuo secret manager. Chiunque abbia la tua chiave privata può eseguire conversioni sul tuo progetto.
</div>

## Struttura del risultato

Ogni metodo di conversione restituisce un `ConversionResult`:

```csharp
public sealed record ConversionResult
{
    public required string PresignedUrl { get; init; }  // URL di download firmato
    public required string ObjectKey { get; init; }     // chiave dell'oggetto nello storage
    public required string Filename { get; init; }      // nome file lato server
    public int? FileSize { get; init; }                 // byte
    public double? ConversionTimeSeconds { get; init; }
    public string? JobId { get; init; }                 // utilizzabile con GetJobStatusAsync
}
```

Il lavoro asincrono restituisce i propri record: `JobStatus` (`Status`, `PresignedUrl`, `ObjectKey`, `Error`), `BatchSubmission` (`BatchId`, `Status`, `UrlCount`, `TotalDiscovered`, `DiscoveryMethod`, `OutputFormat`) e `BatchStatus` (conteggi aggregati, `OutputMode`, `ZipDownloadUrl` e gli `Items` per singolo URL).

Gli artefatti V2 arrivano come valori `V2OutputArtifact` indicizzati per nome di output, ciascuno con `Url` (firmato per 15 minuti), `ObjectKey`, `SizeBytes`, `ContentType` ed `ExpiresIn` (900 secondi per impostazione predefinita). Gli URL firmati scadono, quindi per un accesso permanente scarica i byte (passa `SaveTo`, usa `PerceiveDirectAsync`, oppure recupera l'URL tu stesso) e archiviali nel tuo bucket. Chiamare di nuovo `GetPerceiveOperationAsync` rifirma gli URL degli artefatti di un'operazione ancora dentro la sua finestra di conservazione.

## Sorgente e problemi

- **NuGet:** `Enconvert`
- **GitHub:** [conversionapi/csharp-sdk](https://github.com/conversionapi/csharp-sdk)
- **Licenza:** MIT
- **Altri linguaggi:** [indice degli SDK](/it/docs/sdks) · **Chiavi API:** [dashboard](/it/dashboard) · [prezzi](/it/pricing)

---

## Domande frequenti

### Come converto i file in C# con un pacchetto NuGet?

Esegui `dotnet add package Enconvert`, crea un client con la tua chiave (`new EnconvertClient(apiKey)`) e chiama un metodo async come `ConvertUrlToPdfAsync`, `ConvertImageAsync` oppure `ConvertDocumentAsync`. Passa `SaveTo` sul record delle opzioni per trasmettere l'output direttamente su un percorso locale, oppure leggi `result.PresignedUrl` per scaricarlo tu stesso.

### Come converto un URL in PDF in .NET?

Chiama `await client.ConvertUrlToPdfAsync("https://example.com", new UrlToPdfOptions { SaveTo = "page.pdf" })`. Imposta `SinglePage = false` per paginare, e passa un record `PdfOptions` per dimensione pagina, orientamento, margini, scala, scala di grigi, intestazione e piè di pagina. Il viewport predefinito è 1920 x 1080.

### Come converto DOCX in PDF in C#?

Chiama `ConvertDocumentAsync("report.docx", new ConvertDocumentOptions { SaveTo = "report.pdf" })`. PDF è il formato di output predefinito, quindi qui `OutputFormat` è opzionale. Lo stesso metodo gestisce input XLSX, PPTX, ODF, Pages, Numbers, HTML, Markdown, CSV, JSON, XML, YAML e TOML.

### Come estraggo il contenuto di una pagina web con l'SDK C#?

Usa il namespace V2: `await client.V2.PerceiveAsync(url, new PerceiveOptions { Outputs = new[] { "markdown", "structured" } })`. Ottieni Markdown, HTML pulito o grezzo, screenshot, PDF, link, immagini ed estrazione strutturata, ciascuno come artefatto firmato, più un punteggio `RenderQuality` per quella lettura. Per enumerare prima gli URL di un sito senza renderizzare nulla, chiama `DiscoverAsync`.

### Che cosa significa render quality e perché è su ogni lettura?

`RenderQuality` è un punteggio di onestà da 0.0 a 1.0 associato a ogni lettura V2. Un punteggio basso significa che la pagina non è stata renderizzata in modo pulito: una challenge anti-bot, un cookie wall o un login wall, un errore HTTP oppure uno shell vuoto di una single-page app. Il contenuto torna comunque, insieme a una mappa `Deductions` che nomina i controlli scattati e a un elenco `Warnings`, così il tuo agente può rifiutare una lettura difettosa invece di trattarla come un dato di fatto.

### L'SDK gestisce le conversioni che superano il timeout del proxy?

Sì. Invia un job id generato con ogni conversione di un singolo file o di un singolo URL e, se la richiesta restituisce 5xx, interroga `GET /v1/convert/status/{jobId}` ogni 3 secondi per un massimo di 5 minuti. Il successo restituisce il risultato normale, un fallimento registrato genera `ApiException`, e lo scadere del limite genera `ApiException(504, "Conversion timed out")`. Gli invii batch dei siti web sono esclusi di proposito.

### Posso usare questo SDK da Blazor WebAssembly o da un'app mobile?

No. Il client si autentica con una chiave API privata che non deve mai finire in codice leggibile da un utente. Eseguilo da ASP.NET Core, da un worker service, da una Azure Function o da qualsiasi altro host .NET 8 lato server, e fai in modo che il tuo front end chiami un tuo endpoint.

### Quali conversioni di immagini sono supportate?

Ogni coppia tra `jpeg`, `png`, `svg`, `heic` e `webp`, cioè 20 combinazioni, più la rasterizzazione da `pdf` a `jpeg`. Il formato di input viene ricavato dall'estensione del nome file, e le coppie non supportate generano `ArgumentException` prima di qualsiasi chiamata di rete. Controlla tu stesso la tabella con `Formats.ValidOutputsFor("heic")`.

### Come trasformo un sito di documentazione in chunk pronti per il RAG?

Chiama `client.V2.IngestAsync(new IngestOptions { Mode = "sitemap", Url = "https://docs.example.com", MaxPages = 100 })`, poi interroga `GetIngestJobAsync` finché `Status` non è `"completed"` e leggi `OutputUrl` per il JSONL. Per documenti locali, `IngestFilesAsync` fa passare gli upload dallo stesso chunker. Regola `Chunk.MaxWords` e `Chunk.SentenceOverlap` per adattarli al tuo modello di embedding.

### Per quanto tempo sono validi gli URL di download?

Gli URL degli artefatti V2 sono firmati per 15 minuti (`ExpiresIn` è 900 secondi) e vengono rifirmati ogni volta che recuperi l'operazione con `GetPerceiveOperationAsync`. Anche i risultati di conversione restituiscono un URL presigned. In entrambi i casi, se ti serve che il file sopravviva alla firma, scaricalo e archivialo nel tuo bucket.
