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.

NuGet: Enconvert · Sorgente: conversionapi/csharp-sdk · Runtime: .NET 8+ · Dipendenze: nessuna oltre la BCL

Installazione#

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#

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.


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
Discover DiscoverAsync /it/docs/v2-discover
Lookup LookupAsync /it/docs/v2-lookup
Distill DistillAsync /it/docs/v2-distill
Ingest IngestAsync, IngestFilesAsync, ListIngestJobsAsync, GetIngestJobAsync, CancelIngestJobAsync, RetryIngestWebhookAsync, GetWebhookSecretAsync, RotateWebhookSecretAsync /it/docs/v2-ingest
Watch CreateWatcherAsync, ListWatchersAsync, GetWatcherAsync, GetWatcherSnapshotsAsync, UpdateWatcherAsync, DeleteWatcherAsync /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; la superficie REST grezza è in Panoramica degli endpoint.


Conversione dei file#

ConvertUrlToPdfAsync#

Esegue il rendering in PDF di qualsiasi URL raggiungibile.

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.
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.
Non combinare Auth con un tuo header Authorization. L'API rifiuta quel conflitto invece di indovinare quale credenziale vince. Scegline una.

ConvertUrlToScreenshotAsync#

Cattura un PNG di qualsiasi URL.

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

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.

ConvertImageAsync#

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

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

// 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 oppure a 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.

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.

// 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",
});
Qui viene rispettato solo Grayscale. L'endpoint anything-to-pdf ignora il resto di PdfOptions. Quando ti servono dimensione pagina, orientamento, margini oppure intestazione e piè di pagina, instrada HTML e Markdown attraverso ConvertDocumentAsync, oppure renderizza la pagina con ConvertUrlToPdfAsync.

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.

var batch = await client.ConvertWebsiteToPdfAsync("https://example.com", new WebsiteToPdfOptions
{
    CrawlMode = "sitemap",
    ExcludePatterns = new[] { "/blog/tag/" },
    NotificationEmail = "[email protected]",
});
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.

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);
Raramente ti serve direttamente. Il client interroga già lo stato del job per te quando una richiesta sincrona muore sul proxy. Vedi Recupero dei timeout.

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:

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.


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

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

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

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.

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.

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.

// 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:

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.

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.

I diff degli snapshot contengono contenuto di pagina non attendibile. WatcherSnapshot.Changes è JSON grezzo prelevato dalla pagina monitorata. Effettuane l'escape prima di renderizzarlo in una dashboard o in un'email.

Opzioni PDF#

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

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.

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.

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#

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.

Non inserire mai la chiave direttamente nel codice. 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.

Struttura del risultato#

Ogni metodo di conversione restituisce un ConversionResult:

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#


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.