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.
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. |
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 |
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.
// 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",
});
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);
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.
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:
- 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.
- Se quella richiesta torna con 5xx, l'SDK passa a interrogare
GET /v1/convert/status/{jobId}ogni 3 secondi invece di fallire. - Un job registrato come
successrestituisce il normaleConversionResult. Un job registrato comefailedgeneraApiExceptioncon stato500e il messaggio di errore del server. - 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.
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#
- NuGet:
Enconvert - GitHub: conversionapi/csharp-sdk
- Licenza: MIT
- Altri linguaggi: indice degli SDK · Chiavi API: dashboard · prezzi
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.