SDK de Conversión de Archivos para C# y .NET#
Enconvert es el cliente oficial de C# y .NET para la API de EnConvert, publicado en NuGet y dirigido a .NET 8 o posterior. Convierte archivos desde C#: URL a PDF, DOCX a PDF, HEIC a WebP, JSON a YAML, cualquier documento a Markdown y sitios web completos en un único ZIP. El mismo cliente lleva un espacio de nombres V2 para web scraping e inteligencia web, así que una sola clave cubre perceive, discover, lookup, distill, ingest y watch. Cada llamada es asíncrona y cancelable, cada respuesta es un record tipado, y el paquete no arrastra ninguna dependencia de terceros.
Enconvert · Fuente: conversionapi/csharp-sdk · Runtime: .NET 8+ · Dependencias: ninguna más allá de la BCL
Instalación#
dotnet add package Enconvert
El ensamblado apunta a net8.0 con los tipos de referencia anulables habilitados y está construido únicamente sobre System.Net.Http y System.Text.Json.
Inicio rápido#
using Enconvert;
using var client = new EnconvertClient(Environment.GetEnvironmentVariable("ENCONVERT_API_KEY")!);
// Convierte una página en vivo a PDF y transmítela a disco.
var pdf = await client.ConvertUrlToPdfAsync("https://example.com", new UrlToPdfOptions { SaveTo = "page.pdf" });
Console.WriteLine(pdf.PresignedUrl);
// Lee una página como debería hacerlo tu agente, con una puntuación de calidad adjunta.
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, así que decláralo con using o regístralo como singleton. Se autentica con una clave de API privada, lo que significa que su sitio es el servidor: nunca envíes la clave dentro de una build de escritorio, móvil o Blazor WebAssembly. La configuración de claves se trata en Autenticación.
Qué expone el cliente#
| Miembro | Endpoint | Devuelve |
|---|---|---|
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} (sondeado) |
BatchStatus |
V2 |
la superficie /v2/* |
EnconvertV2 |
client.V2 agrupa 23 métodos repartidos en seis capacidades:
| Capacidad | Métodos | Referencia |
|---|---|---|
| Perceive | PerceiveAsync, GetPerceiveOperationAsync, PerceiveBatchAsync, GetPerceiveBatchAsync, PerceiveDirectAsync, DownloadPerceiveArtifactAsync |
/es/docs/v2-perceive |
| Discover | DiscoverAsync |
/es/docs/v2-discover |
| Lookup | LookupAsync |
/es/docs/v2-lookup |
| Distill | DistillAsync |
/es/docs/v2-distill |
| Ingest | IngestAsync, IngestFilesAsync, ListIngestJobsAsync, GetIngestJobAsync, CancelIngestJobAsync, RetryIngestWebhookAsync, GetWebhookSecretAsync, RotateWebhookSecretAsync |
/es/docs/v2-ingest |
| Watch | CreateWatcherAsync, ListWatchersAsync, GetWatcherAsync, GetWatcherSnapshotsAsync, UpdateWatcherAsync, DeleteWatcherAsync |
/es/docs/v2-watch |
Cada método admite un CancellationToken opcional al final. Las opciones son records con propiedades init, así que constrúyelas con un inicializador de objeto y reutilízalas con with. Los clientes de otros lenguajes están en el índice de SDK; la superficie REST en crudo está en Visión general de endpoints.
Conversión de archivos#
ConvertUrlToPdfAsync#
Renderiza a PDF cualquier URL accesible.
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)");
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
SaveTo |
string? |
-- | Ruta local a la que transmitir el PDF. Los directorios padre que falten se crean. |
SinglePage |
bool? |
true |
true genera una única página continua. false pagina usando PdfOptions.PageSize. |
PdfOptions |
PdfOptions? |
-- | Tamaño de página, orientación, márgenes, escala, escala de grises, encabezado, pie. Ver Opciones de PDF. |
ViewportWidth, ViewportHeight |
int? |
1920, 1080 |
Viewport del navegador en píxeles. |
LoadMedia, EnableScroll |
bool? |
true |
Espera a las imágenes y el vídeo antes de capturar; desplaza de arriba abajo para que se disparen los cargadores diferidos. |
OutputFilename |
string? |
auto | Sustituye el nombre de archivo generado. |
Auth |
HttpBasicAuth? |
-- | Credenciales HTTP Basic para una página tras un inicio de sesión. |
Cookies, Headers |
IReadOnlyList<BrowserCookie>?, IReadOnlyDictionary<string, string>? |
-- | Hasta 50 cookies inyectadas antes de renderizar, y hasta 20 encabezados de solicitud extra. Los encabezados salto a salto se rechazan. |
Auth con tu propio encabezado Authorization. La API rechaza ese conflicto en lugar de adivinar qué credencial gana. Elige una.
ConvertUrlToScreenshotAsync#
Captura un PNG de cualquier URL.
await client.ConvertUrlToScreenshotAsync("https://example.com", new UrlToScreenshotOptions
{
ViewportWidth = 1440,
SaveTo = "screenshot.png",
});
UrlToScreenshotOptions acepta las mismas opciones de viewport, medios, desplazamiento, nombre de archivo y acceso que ConvertUrlToPdfAsync, menos SinglePage y PdfOptions.
ConvertUrlToMarkdownAsync#
Extrae Markdown limpio con sabor GitHub desde una URL. Se eliminan la navegación, los pies de página, los anuncios y los scripts, se conserva el cuerpo principal del artículo y se antepone un frontmatter YAML (title, description, url, links, images).
await client.ConvertUrlToMarkdownAsync("https://example.com/article", new UrlToMarkdownOptions { SaveTo = "article.md" });
UrlToMarkdownOptions comparte las mismas opciones de renderizado y acceso. Para lecturas orientadas a agentes que además necesiten una puntuación de calidad y extracción estructurada, usa Perceive en su lugar.
ConvertImageAsync#
Convierte entre jpeg, png, svg, heic y webp en cualquier dirección, y rasteriza un PDF a JPEG.
// Desde una ruta.
await client.ConvertImageAsync("photo.heic", new ConvertImageOptions { OutputFormat = "webp", SaveTo = "photo.webp" });
// Desde bytes, con un nombre de archivo explícito para poder detectar el formato de entrada.
var bytes = await File.ReadAllBytesAsync("scan.pdf");
await client.ConvertImageAsync(new FileInput(bytes, "scan.pdf"), new ConvertImageOptions
{
OutputFormat = "jpeg",
SaveTo = "scan.jpeg",
});
Tres sobrecargas aceptan una ruta string, un byte[] en crudo o un record FileInput. El formato de entrada procede de la extensión del nombre de archivo, así que prefiere FileInput cuando tengas bytes: la sobrecarga de byte[] pelado envía upload.bin, que solo funciona en los endpoints con detección automática.
| Opción | Tipo | Obligatorio | Descripción |
|---|---|---|---|
OutputFormat |
string |
Sí | Formato de destino. jpg, yml, htm y md se normalizan a sus nombres canónicos. |
SaveTo |
string? |
-- | Ruta local a la que transmitir el resultado. |
OutputFilename |
string? |
-- | Sustituye el nombre de archivo generado. |
Los pares no admitidos lanzan ArgumentException antes de realizar ninguna solicitud HTTP, así que una errata no cuesta nada.
ConvertDocumentAsync#
Convierte documentos y formatos de datos. OutputFormat es "pdf" por defecto.
// docx a pdf
await client.ConvertDocumentAsync("report.docx", new ConvertDocumentOptions { SaveTo = "report.pdf" });
// json a yaml
await client.ConvertDocumentAsync("data.json", new ConvertDocumentOptions { OutputFormat = "yaml", SaveTo = "data.yaml" });
// markdown a pdf con geometría de página
await client.ConvertDocumentAsync("README.md", new ConvertDocumentOptions
{
PdfOptions = new PdfOptions { PageSize = "A4", Margins = new PdfMargins { Top = 20, Bottom = 20 } },
SaveTo = "readme.pdf",
});
Entradas admitidas: .doc, .docx, .xls, .xlsx, .ppt, .pptx, .html, .htm, .odt, .ods, .odp, .ots, .pages, .numbers, .md, .markdown, .csv, .json, .xml, .yaml, .yml, .toml. EPUB no tiene un par de documento propio, así que envía .epub a través de ConvertToPdfAsync o ConvertToMarkdownAsync.
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
OutputFormat |
string? |
"pdf" |
Formato de destino. |
SaveTo |
string? |
-- | Ruta local a la que transmitir el resultado. |
OutputFilename |
string? |
-- | Sustituye el nombre de archivo generado. |
PdfOptions |
PdfOptions? |
-- | Configuración de página. Solo tiene sentido cuando la salida es PDF. |
Igual que ConvertImageAsync, este método tiene sobrecargas de ruta, byte[] y FileInput.
ConvertToMarkdownAsync#
Envía casi cualquier documento al endpoint de Markdown con detección automática: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT y MD, además de formatos ofimáticos heredados y ODF. Aquí las imágenes no están admitidas.
await client.ConvertToMarkdownAsync("handbook.docx", new ConvertToMarkdownOptions { SaveTo = "handbook.md" });
El formato se detecta en el servidor, así que no hay comprobación de extensión en el cliente ni opciones de PDF en este endpoint. ConvertToMarkdownOptions lleva únicamente SaveTo y OutputFilename. La salida es un único archivo .md consciente de los encabezados, lo que lo convierte en una primera etapa sólida de un pipeline de RAG: un troceador semántico puede dividir por la propia jerarquía de encabezados del documento en lugar de por recuentos arbitrarios de caracteres.
ConvertToPdfAsync#
El otro endpoint con detección automática: formatos ofimáticos, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, texto plano, imágenes rasterizadas, SVG, EPUB o un PDF existente que se pasa directamente para normalizarlo.
// Diapositivas a PDF.
await client.ConvertToPdfAsync("slides.pptx", new ConvertToPdfOptions { SaveTo = "slides.pdf" });
// PDF de paso directo, convertido a escala de grises.
await client.ConvertToPdfAsync("scan.pdf", new ConvertToPdfOptions
{
PdfOptions = new PdfOptions { Grayscale = true },
SaveTo = "gray.pdf",
});
Grayscale. El endpoint anything-to-pdf ignora el resto de PdfOptions. Cuando necesites tamaño de página, orientación, márgenes o un encabezado y un pie, enruta el HTML y el Markdown por ConvertDocumentAsync, o renderiza la página con ConvertUrlToPdfAsync.
ConvertToPdfOptions lleva SaveTo, OutputFilename y PdfOptions, y tiene las mismas tres sobrecargas de entrada que los métodos anteriores.
Lotes de sitios completos#
ConvertWebsiteToPdfAsync y ConvertWebsiteToScreenshotAsync descubren todas las páginas de un sitio, convierten cada una en segundo plano y empaquetan los resultados en un único ZIP. Ambos son únicamente asíncronos y requieren una clave de API privada.
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}");
// Bloquea hasta que el ZIP esté listo, luego guárdalo.
var status = await client.WaitForBatchAsync(batch.BatchId, new WaitForBatchOptions { SaveTo = "site.zip" });
Console.WriteLine($"{status.Completed} of {status.Total} converted, {status.Failed} failed");
Para sondear según tu propio calendario, llama a GetBatchStatusAsync(batchId) y lee ZipDownloadUrl en cuanto Status salga de "processing".
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
CrawlMode |
string? |
"auto" |
"auto", "sitemap" (solo sitemap.xml) o "full" (sitemap más un rastreo en anchura). |
IncludePatterns, ExcludePatterns |
IReadOnlyList<string>? |
-- | Rastrea solo, u omite, las URL que coincidan con estos patrones. Modo de rastreo completo. |
NotificationEmail, CallbackUrl |
string? |
propietario del proyecto, -- | Dirección a la que se avisa cuando termina el lote, y webhook al que se hace POST al finalizar. |
SinglePage, PdfOptions |
bool?, PdfOptions? |
-- | Solo para lotes de PDF. Las opciones de viewport, medios, desplazamiento y acceso se comparten con los métodos de URL individual. |
WaitForBatchOptions toma IntervalMs (por defecto 5_000), TimeoutMs (por defecto 1_800_000, es decir 30 minutos) y SaveTo. Reventar el plazo lanza ApiException con estado 504.
GetJobStatusAsync#
Sondea un único job de conversión por 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);
Pares de conversión admitidos#
El SDK refleja el mapa de conversores del gateway e implementa 43 pares {input}-to-{output} tipados.
| Entrada | Salidas |
|---|---|
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 |
entre sí, los 20 pares |
pdf |
jpeg |
La clase estática Formats expone la misma tabla, así que puedes validar antes de construir una interfaz o una cola de jobs:
Formats.ValidOutputsFor("json"); // ["csv", "toml", "xml", "yaml"]
Formats.ValidOutputsFor("pdf"); // ["jpeg"]
Formats.ImplementedConversions; // el conjunto completo de los 43 nombres de endpoint
Formats.NormalizeOutputFormat(".JPG"); // "jpeg"
Cualquier cosa fuera de esta tabla pasa por ConvertToPdfAsync o ConvertToMarkdownAsync, que detectan el formato en el servidor. La referencia completa de parámetros vive en Parámetros y opciones.
Inteligencia web (V2)#
Cada lectura V2 lleva RenderQuality, una puntuación de 0.0 a 1.0 que indica con qué honestidad se renderizó la página. Un desafío antibots, un muro de cookies o de inicio de sesión, un error HTTP o el armazón vacío de una aplicación de una sola página vuelven con una puntuación baja, un mapa Deductions relleno que nombra qué comprobaciones saltaron y una lista Warnings, mientras que el contenido se sigue devolviendo. De eso se trata: una mala lectura queda marcada en lugar de entrar en silencio en el contexto de tu agente. La misma puntuación aparece en los resultados de perceive, en los elementos de distill, en los resultados de lookup percibidos automáticamente y en los snapshots de los watchers. Los conceptos se tratan en la visión general de V2. Los endpoints V2 requieren una clave de API privada; las claves públicas se rechazan.
Perceive#
Renderiza una URL en artefactos listos para agentes.
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); // de 0.0 a 1.0
Console.WriteLine(op.StatusCode); // estado HTTP del origen
Console.WriteLine(op.Outputs["markdown"].Url); // firmada durante 15 minutos
Console.WriteLine(op.Structured); // JsonObject, la forma es tuya
foreach (var (check, penalty) in op.Deductions) Console.WriteLine($"deduction {check}: {penalty}");
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
Outputs |
IReadOnlyList<string>? |
["markdown", "structured"] |
Cualquiera de markdown, html_cleaned, html_raw, screenshot, screenshot_full_page, pdf, links, images, structured. |
Extract |
IReadOnlyList<string>? |
-- | Objetivos heurísticos: tables, prices, contacts, metadata, main_content, headings, structured_data, technologies, all. |
Schema |
JsonObject? |
-- | Schema JSON que guía la extracción estructurada del nivel del LLM. |
WaitFor, WaitTimeoutMs |
string?, int? |
--, 30000 |
Un selector CSS (opcionalmente css:...) o js:<expr> que esperar antes de capturar, acotado entre 0 y 60000 ms. |
JsCode |
string? |
-- | JavaScript ejecutado tras la navegación, máximo 20000 caracteres. |
Viewport |
PerceiveViewport? |
1920 x 1080 | Width de 320 a 3840, Height de 240 a 2160. |
Headers, Cookies, Auth |
ver arriba | -- | Encabezados extra, cookies inyectadas, credenciales HTTP Basic. |
CacheMode |
string? |
"enabled" |
"enabled" reutiliza una caché de 1 hora, "bypass" la omite, "refresh" vuelve a renderizar. |
PdfOptions |
PdfOptions? |
-- | Solo tiene sentido cuando Outputs contiene pdf. |
BlockResources |
IReadOnlyList<string>? |
-- | Tipos de recurso que omitir: image, media, font, stylesheet, script. |
RespectRobots, Mobile |
bool? |
-- | Respeta robots.txt; emula un dispositivo móvil. |
OnlyMainContent |
bool? |
true |
Elimina navegación, encabezado, pie y banners de cookies del artefacto markdown y del extracto main_content. |
DirectDownload |
bool? |
-- | Transmite los bytes del artefacto en lugar del envoltorio JSON. Es preferible PerceiveDirectAsync. |
ProxyUrl, Geolocation y ActionChain existen en PerceiveOptions pero todavía no están disponibles en el servidor y actualmente vuelven como 422.
Vuelve a firmar las URL de artefacto más adelante, agrupa hasta 1000 URL o transmite bytes sin la ida y vuelta de la URL firmada:
// Las URL de artefacto se vuelven a firmar en cada obtención de la operación.
var again = await client.V2.GetPerceiveOperationAsync(op.OperationId);
// Lotes: los pequeños se ejecutan en línea, los más grandes devuelven "queued" y los sondeas.
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}");
// Descarga directa: se requiere exactamente una salida que produzca artefacto.
var direct = await client.V2.PerceiveDirectAsync("https://example.com", new PerceiveOptions { Outputs = new[] { "pdf" } });
await File.WriteAllBytesAsync(direct.Filename ?? "page.pdf", direct.Content);
// Vuelve a descargar un artefacto almacenado de una operación anterior.
var artifact = await client.V2.DownloadPerceiveArtifactAsync(op.OperationId, "markdown");
PerceiveDirectAsync lanza ArgumentException localmente a menos que se pida exactamente uno de markdown, html_cleaned, html_raw, screenshot, screenshot_full_page, pdf, links o images, ya que structured es JSON en línea y no un archivo almacenado. Ambos métodos directos devuelven un PerceiveDirectResult que lleva Content, ContentType, Filename, OperationId, ObjectKey, CacheHit, RenderQuality, SourceStatusCode, ContentHash y WarningsCount. DownloadPerceiveArtifactAsync toma el nombre de la salida solo cuando la operación produjo más de un artefacto, y devuelve 410 una vez que el artefacto supera su ventana de retención.
Discover#
Enumera las URL de un sitio sin renderizado en navegador, lo que lo hace mucho más barato que un rastreo.
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);
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
Mode |
string? |
"hybrid" |
"sitemap", "crawl" o "hybrid" (sitemap más rastreo HTTP). |
MaxUrls |
int? |
100 |
De 1 a 1000. |
MaxDepth |
int? |
2 |
Profundidad de rastreo, de 1 a 5. |
IncludePatterns, ExcludePatterns |
IReadOnlyList<string>? |
-- | Lista de permitidos y lista de bloqueados con expresiones regulares, máximo 50 entradas cada una. La lista de bloqueados se aplica en segundo lugar. |
SameDomainOnly, RespectRobots |
bool? |
true, -- |
Permanece en el dominio semilla; respeta robots.txt durante el descubrimiento. |
DiscoverResult informa de Total, Urls, PagesCrawled, Truncated, RobotsRespected, un mapa de recuentos por origen en Sources y Warnings.
Lookup#
Ejecuta una búsqueda web por categorías y, opcionalmente, renderiza los primeros resultados en la misma llamada.
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}");
}
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
Category |
string? |
"web" |
"web", "news", "images", "scholar", "patents", "maps". |
Country, Locale |
string? |
-- | Código de país gl de Google ("us", "in") y idioma de interfaz hl ("en"). |
TimeFilter |
string? |
-- | "hour", "day", "week", "month", "year". |
NumResults, Page |
int? |
10, 1 |
De 1 a 100 resultados; página de 1 a 10. |
Location, Autocorrect |
string?, bool? |
--, true |
Ubicación en texto libre como "Austin, Texas"; deja que el proveedor corrija erratas evidentes. |
PerceiveTop |
int? |
0 |
De 0 a 10. Percibe automáticamente las N primeras URL de resultado con un renderizado completo en navegador. |
LookupResult también lleva AnswerBox, KnowledgeGraph, PerceiveOperationIds y Warnings.
Distill#
Extracción estructurada guiada por schema. Cuando proporcionas uno, se ejecuta primero una pasada CSS gratuita, y solo los campos que se le escapan escalan al nivel del 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}");
// O descubre primero un sitio y luego destila cada página que encuentre.
await client.V2.DistillAsync(new DistillOptions
{
DiscoverFrom = new DistillDiscoverFrom { Url = "https://example.com", Mode = "sitemap", MaxPages = 10 },
Schema = new JsonObject { ["title"] = "page title" },
});
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
Schema |
JsonObject |
obligatorio | Un objeto JSON-Schema o un mapa plano {field: description}. El Data de la respuesta se ajusta a esta forma. |
Urls |
IReadOnlyList<string>? |
-- | URL explícitas, máximo 50. Exactamente uno de Urls o DiscoverFrom. |
DiscoverFrom |
DistillDiscoverFrom? |
-- | Url, Mode ("sitemap", "crawl", "hybrid") y MaxPages de 1 a 50, por defecto 10. |
CssSchema |
CssSchema? |
-- | BaseSelector más Fields, con Name y TargetField opcionales. |
WaitFor, WaitTimeoutMs |
string?, int? |
--, 30000 |
Condición de espera antes de la extracción. |
Headers, Cookies, RespectRobots |
-- | -- | Las mismas formas que las opciones de renderizado anteriores. |
CssField.Type es uno de text, attribute, html, regex, nested, list o nested_list, con anidamiento de hasta cinco niveles de profundidad. Pasar Urls y DiscoverFrom a la vez, o ninguno de los dos, lanza ArgumentException antes de que salga ninguna solicitud.
Ingest#
Convierte un sitio entero, o un montón de documentos subidos, en JSONL troceado que un almacén vectorial pueda leer. Ingest siempre es asíncrono.
// Desde un sitio.
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",
});
// Desde archivos subidos: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT y MD, ofimática heredada y ODF.
var fileJob = await client.V2.IngestFilesAsync(
new[] { new FileInput(await File.ReadAllBytesAsync("handbook.pdf"), "handbook.pdf") },
new IngestFilesOptions { Chunk = new IngestChunkOptions { MaxWords = 512 } });
// Sondea hasta que el JSONL esté listo.
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
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
Mode |
string? |
"urls" |
"urls", "sitemap" o "crawl". |
Url |
string? |
-- | URL semilla. Obligatoria para sitemap y crawl, rechazada para urls. |
Urls |
IReadOnlyList<string>? |
-- | URL explícitas, máximo 1000. Obligatoria para urls, rechazada en el resto de casos. |
MaxPages, MaxDepth, SameDomainOnly |
int?, int?, bool? |
50, 2, true |
Límite de descubrimiento de 1 a 1000 para sitemap y crawl, profundidad de rastreo de 1 a 5, permanecer en el dominio semilla. |
IncludePatterns, ExcludePatterns |
IReadOnlyList<string>? |
-- | Lista de permitidos y lista de bloqueados con expresiones regulares. |
RespectRobots, WaitFor, WaitTimeoutMs |
-- | -- | Controles de renderizado por página. |
Chunk |
IngestChunkOptions? |
-- | MaxWords de 32 a 4000, por defecto 512. SentenceOverlap de 0 a 10, por defecto 1. |
WebhookUrl |
string? |
-- | Webhook de finalización, firmado con HMAC. |
Las reglas de modo se imponen en el cliente, así que un job urls que además establece Url lanza ArgumentException de inmediato. Un job pasa por queued, discovering, processing y luego completed, failed o canceled. La entrega del webhook se puede verificar de extremo a extremo:
var secret = await client.V2.GetWebhookSecretAsync();
Console.WriteLine($"{secret.SignatureHeader} using {secret.SignatureScheme}, replay tolerance {secret.ReplayToleranceSeconds}s");
await client.V2.RotateWebhookSecretAsync(); // las firmas antiguas dejan de verificarse
var retry = await client.V2.RetryIngestWebhookAsync(job.JobId);
Console.WriteLine($"delivered: {retry.Delivered} after {retry.Attempts} attempts");
Watch#
Vuelve a renderizar una página según un calendario y te avisa cuando 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 borra
await client.V2.DeleteWatcherAsync(watcher.WatcherId); // borrado lógico, idempotente
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
FrequencyMinutes |
int? |
60 |
De 60 a 43200. El mínimo de una hora es estricto. |
DiffMode |
string? |
"auto" |
"auto", "text", "structured", "tables", "metadata". |
TrackFields |
JsonObject? |
-- | Subconjunto de campos o selectores para el motor de diff. |
WebhookUrl |
string? |
-- | Webhook de cambios, firmado con HMAC. Establécelo a "" en una actualización para borrarlo. |
NotifyEmail |
bool? |
true |
Envía un correo al propietario del proyecto cuando hay cambios. |
WatcherUpdate también acepta Status ("active" o "paused") y lanza ArgumentException si le pasas una actualización sin ningún campo establecido.
WatcherSnapshot.Changes es JSON en crudo tomado de la página vigilada. Escápalo antes de renderizarlo en un panel o en un correo electrónico.
Opciones de PDF#
PdfOptions es compartido por ConvertUrlToPdfAsync, ConvertDocumentAsync, ConvertToPdfAsync (solo escala de grises) y PerceiveOptions cuando pdf está entre las salidas.
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 | Descripción |
|---|---|---|
PageSize |
string? |
"A4", "A3", "Letter", "Legal" y compañía. |
PageWidth, PageHeight |
double? |
Dimensiones personalizadas. Establecidas juntas, prevalecen sobre PageSize. |
Orientation |
string? |
"portrait" o "landscape". |
Margins |
PdfMargins? |
Top, Bottom, Left, Right, todos opcionales. |
Scale |
double? |
Escala de renderizado, por ejemplo 0.9 para el 90 por ciento. |
Grayscale |
bool? |
Posprocesa el PDF a escala de grises. |
Header, Footer |
PdfHeaderFooter? |
Content de hasta 2000 caracteres, más Height. |
Manejo de errores#
Todo fallo aflora como una excepción tipada, así que los bloques catch se leen de lo más específico a lo menos específico.
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}"); }
| Clase | Se lanza en | Código de estado |
|---|---|---|
AuthenticationException |
Clave inválida, ausente o revocada | 401, 403 (informado como 401) |
QuotaException |
Se lanza ante un HTTP 402 | 402 |
RateLimitException |
Límite de tasa superado | 429 |
ApiException |
Cualquier otro 4xx o 5xx | el código real |
EnconvertException |
Clase base de todas las anteriores | -- |
La jerarquía va de EnconvertException a ApiException (que lleva StatusCode) y de ahí a las tres clases específicas, así que un único catch (EnconvertException) captura todo lo que lanza el SDK. Los mensajes se toman del campo detail o error del cuerpo de la respuesta cuando está presente. La validación que el SDK hace localmente, como un par de conversión no admitido o una solicitud de distill malformada, lanza ArgumentException en su lugar y nunca llega a la red. Los códigos de respuesta están catalogados en Códigos de error.
Recuperación de timeouts#
Los renderizados de URL largos y las conversiones de documentos grandes pueden sobrevivir a un timeout de proxy inverso de 60 a 120 segundos incluso cuando la conversión en sí tiene éxito. El cliente absorbe eso por ti:
- Antes de cada conversión de un solo archivo o de una sola URL, el SDK genera un id de job y lo envía con la solicitud.
- Si esa solicitud vuelve con 5xx, el SDK pasa a sondear
GET /v1/convert/status/{jobId}cada 3 segundos en lugar de fallar. - Un job registrado como
successdevuelve elConversionResultnormal. Un job registrado comofailedlanzaApiExceptioncon estado500y el mensaje de error del servidor. - El plazo de sondeo es de 5 minutos, tras lo cual el SDK lanza
ApiException(504, "Conversion timed out").
Esto cubre ConvertUrlToPdfAsync, ConvertUrlToScreenshotAsync, ConvertUrlToMarkdownAsync y los cuatro métodos de subida de archivos. Los envíos de lotes de sitios web quedan fuera deliberadamente, porque un envío fallido no tiene fila de job que sondear y debe aflorar de inmediato. Cuando una respuesta omite job_id, el SDK rellena el id que generó, de modo que ConversionResult.JobId siempre se puede usar con GetJobStatusAsync.
Configuración#
using var client = new EnconvertClient(
apiKey: Environment.GetEnvironmentVariable("ENCONVERT_API_KEY")!,
baseUrl: null, // por defecto https://api.enconvert.com
timeoutMs: 300_000);
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
apiKey |
string |
obligatorio | Clave de API privada. Un valor vacío lanza ArgumentException. |
baseUrl |
string? |
https://api.enconvert.com |
Sustituye la URL base de la API. Las barras finales se eliminan. |
timeoutMs |
int |
300_000 |
Timeout de solicitud en milisegundos, 5 minutos por defecto. |
La clave viaja en un encabezado X-API-Key en cada solicitud. Las descargas de artefactos van directamente a URL de almacenamiento firmadas mediante un segundo HttpClient que no envía clave ni aplica timeout, así que un ZIP grande puede transmitirse todo el tiempo que necesite. Libera el cliente una sola vez al final de tu proceso, o regístralo como singleton en lugar de construir uno por solicitud.
Forma del resultado#
Cada método de conversión devuelve un ConversionResult:
public sealed record ConversionResult
{
public required string PresignedUrl { get; init; } // URL de descarga firmada
public required string ObjectKey { get; init; } // clave del objeto en almacenamiento
public required string Filename { get; init; } // nombre de archivo del servidor
public int? FileSize { get; init; } // bytes
public double? ConversionTimeSeconds { get; init; }
public string? JobId { get; init; } // usable con GetJobStatusAsync
}
El trabajo asíncrono devuelve sus propios records: JobStatus (Status, PresignedUrl, ObjectKey, Error), BatchSubmission (BatchId, Status, UrlCount, TotalDiscovered, DiscoveryMethod, OutputFormat) y BatchStatus (recuentos agregados, OutputMode, ZipDownloadUrl y los Items por URL).
Los artefactos V2 llegan como valores V2OutputArtifact indexados por nombre de salida, cada uno con Url (firmada durante 15 minutos), ObjectKey, SizeBytes, ContentType y ExpiresIn (900 segundos por defecto). Las URL firmadas caducan, así que para acceso permanente descarga los bytes (pasa SaveTo, usa PerceiveDirectAsync o descarga la URL tú mismo) y guárdalos en tu propio bucket. Llamar de nuevo a GetPerceiveOperationAsync vuelve a firmar las URL de artefacto de una operación que siga dentro de su ventana de retención.
Código fuente e incidencias#
- NuGet:
Enconvert - GitHub: conversionapi/csharp-sdk
- Licencia: MIT
- Otros lenguajes: índice de SDK · Claves de API: panel de control · precios
Preguntas frecuentes#
¿Cómo convierto archivos en C# con un paquete de NuGet?#
Ejecuta dotnet add package Enconvert, crea un cliente con tu clave (new EnconvertClient(apiKey)) y llama a un método asíncrono como ConvertUrlToPdfAsync, ConvertImageAsync o ConvertDocumentAsync. Pasa SaveTo en el record de opciones para transmitir la salida directamente a una ruta local, o lee result.PresignedUrl para descargarla tú mismo.
¿Cómo convierto una URL a PDF en .NET?#
Llama a await client.ConvertUrlToPdfAsync("https://example.com", new UrlToPdfOptions { SaveTo = "page.pdf" }). Establece SinglePage = false para paginar, y pasa un record PdfOptions para el tamaño de página, la orientación, los márgenes, la escala, la escala de grises, el encabezado y el pie. El viewport es de 1920 x 1080 por defecto.
¿Cómo convierto DOCX a PDF en C#?#
Llama a ConvertDocumentAsync("report.docx", new ConvertDocumentOptions { SaveTo = "report.pdf" }). PDF es el formato de salida por defecto, así que OutputFormat es opcional aquí. El mismo método gestiona entradas XLSX, PPTX, ODF, Pages, Numbers, HTML, Markdown, CSV, JSON, XML, YAML y TOML.
¿Cómo extraigo el contenido de una página web con el SDK de C#?#
Usa el espacio de nombres V2: await client.V2.PerceiveAsync(url, new PerceiveOptions { Outputs = new[] { "markdown", "structured" } }). Obtienes Markdown, HTML limpio o en crudo, capturas de pantalla, PDF, enlaces, imágenes y extracción estructurada, cada uno como artefacto firmado, más una puntuación RenderQuality para la lectura. Para enumerar primero las URL de un sitio sin renderizar nada, llama a DiscoverAsync.
¿Qué significa la calidad de renderizado y por qué está en cada lectura?#
RenderQuality es una puntuación de honestidad de 0.0 a 1.0 adjunta a cada lectura V2. Una puntuación baja significa que la página no se renderizó limpiamente: un desafío antibots, un muro de cookies o de inicio de sesión, un error HTTP o el armazón vacío de una aplicación de una sola página. El contenido sigue llegando, junto con un mapa Deductions que nombra qué comprobaciones saltaron y una lista Warnings, para que tu agente pueda rechazar una mala lectura en lugar de tratarla como un hecho.
¿Gestiona el SDK las conversiones que superan el timeout del proxy?#
Sí. Envía un id de job generado con cada conversión de un solo archivo o de una sola URL y, si la solicitud devuelve 5xx, sondea GET /v1/convert/status/{jobId} cada 3 segundos durante hasta 5 minutos. El éxito devuelve el resultado normal, un fallo registrado lanza ApiException, y agotar el plazo lanza ApiException(504, "Conversion timed out"). Los envíos de lotes de sitios web quedan excluidos a propósito.
¿Puedo usar este SDK desde Blazor WebAssembly o desde una aplicación móvil?#
No. El cliente se autentica con una clave de API privada que nunca debe viajar en código que un usuario pueda leer. Ejecútalo desde ASP.NET Core, un worker service, una Azure Function o cualquier otro host .NET 8 del lado del servidor, y haz que tu front end llame a tu propio endpoint en su lugar.
¿Qué conversiones de imagen están admitidas?#
Todos los pares entre jpeg, png, svg, heic y webp, que son 20 combinaciones, más la rasterización de pdf a jpeg. El formato de entrada procede de la extensión del nombre de archivo, y los pares no admitidos lanzan ArgumentException antes de cualquier llamada de red. Consulta la tabla tú mismo con Formats.ValidOutputsFor("heic").
¿Cómo convierto un sitio de documentación en fragmentos listos para RAG?#
Llama a client.V2.IngestAsync(new IngestOptions { Mode = "sitemap", Url = "https://docs.example.com", MaxPages = 100 }), luego sondea GetIngestJobAsync hasta que Status sea "completed" y lee OutputUrl para obtener el JSONL. Para documentos locales, IngestFilesAsync pasa las subidas por el mismo troceador. Ajusta Chunk.MaxWords y Chunk.SentenceOverlap para que encajen con tu modelo de embeddings.
¿Cuánto tiempo son válidas las URL de descarga?#
Las URL de artefacto de V2 se firman durante 15 minutos (ExpiresIn es de 900 segundos) y se vuelven a firmar cada vez que obtienes la operación con GetPerceiveOperationAsync. Los resultados de conversión también devuelven una URL prefirmada. En ambos casos, si necesitas que el archivo sobreviva a la firma, descárgalo y guárdalo en tu propio bucket.