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.

NuGet: 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.
No combines 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 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",
});
Aquí solo se respeta 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);
Rara vez necesitas esto directamente. El cliente ya sondea el estado del job por ti cuando una solicitud síncrona muere en el proxy. Ver Recuperación de timeouts.

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.

Los diffs de snapshot contienen contenido de página no confiable. 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:

  1. 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.
  2. Si esa solicitud vuelve con 5xx, el SDK pasa a sondear GET /v1/convert/status/{jobId} cada 3 segundos en lugar de fallar.
  3. Un job registrado como success devuelve el ConversionResult normal. Un job registrado como failed lanza ApiException con estado 500 y el mensaje de error del servidor.
  4. 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.

Nunca incrustes la clave en el código. Léela desde una variable de entorno, desde los user secrets o desde tu gestor de secretos. Cualquiera que tenga tu clave privada puede ejecutar conversiones contra tu proyecto.

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#


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.