SDK de Conversión de Archivos para Ruby#
enconvert es la gem oficial de Ruby para la API de EnConvert. Convierte URL, imágenes y documentos a través de 43 endpoints de conversión implementados, y expone un segundo espacio de nombres, client.v2, que lee páginas web en vivo y las transforma en Markdown, JSON, capturas de pantalla y PDF listos para agentes. Cada lectura V2 lleva una puntuación render_quality, así que una página bloqueada o el armazón vacío de una SPA nunca pasa por contenido real. La gem está dirigida a Ruby 3.0+ y se distribuye sin dependencias en tiempo de ejecución, construida sobre net/http, json y securerandom de la librería estándar. Las respuestas vuelven como Structs con lectores en snake_case.
enconvert · Fuente: conversionapi/ruby-sdk · Ruby: 3.0.0 o posterior · Dependencias en tiempo de ejecución: ninguna
Instalación#
gem install enconvert
O añádela a tu Gemfile:
gem "enconvert"
bundle install
Inicio rápido#
require "enconvert"
client = Enconvert::Client.new(api_key: ENV.fetch("ENCONVERT_API_KEY"))
# Convierte una página en vivo a PDF y transmítela a disco.
result = client.convert_url_to_pdf("https://example.com", save_to: "page.pdf")
puts result.presigned_url
# Lee la misma página como debería hacerlo un agente, con una puntuación de calidad adjunta.
op = client.v2.perceive("https://example.com", outputs: %w[markdown structured])
puts op.outputs["markdown"].url, op.render_quality # p. ej. 0.93
La gem es solo del lado del servidor. Tu clave de API privada debe quedarse en el servidor, así que léela desde una variable de entorno o un gestor de secretos y nunca la envíes a un navegador ni a un binario móvil.
Qué expone el cliente#
Enconvert::Client lleva la superficie de conversión de archivos directamente, y toda la superficie de inteligencia web cuelga de client.v2.
| Grupo | Métodos | Devuelve |
|---|---|---|
| URL individual | convert_url_to_pdf, convert_url_to_screenshot, convert_url_to_markdown |
ConversionResult |
| Archivo subido | convert_image, convert_document, convert_to_markdown, convert_to_pdf |
ConversionResult |
| Lote de sitio completo | convert_website_to_pdf, convert_website_to_screenshot |
BatchSubmission |
| Estado y sondeo | get_job_status, get_batch_status, wait_for_batch |
JobStatus, BatchStatus |
| Inteligencia web | client.v2.*, 23 métodos repartidos en seis capacidades |
Structs de V2 |
| Ayudas de formatos | Enconvert.valid_outputs_for, Enconvert::IMPLEMENTED_CONVERSIONS |
Array, Set |
Cada tipo de respuesta es un Struct creado con keyword_init: true, así que lees los campos como métodos corrientes: result.presigned_url, op.render_quality, job.total_chunks.
Conversión de archivos#
convert_url_to_pdf#
Renderiza cualquier URL pública a PDF.
result = client.convert_url_to_pdf(
"https://example.com",
single_page: false,
pdf_options: { page_size: "A4", orientation: "landscape" },
viewport_width: 1440,
save_to: "report.pdf"
)
puts result.filename, result.file_size
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
save_to |
String |
nil |
Ruta local donde descargar el PDF. Los directorios padre se crean automáticamente. |
single_page |
Boolean |
true |
true genera una única página continua. false pagina usando pdf_options[:page_size]. |
pdf_options |
Hash |
nil |
Geometría de página. Ver Opciones de PDF. |
viewport_width |
Integer |
1920 |
Ancho del viewport del navegador en píxeles. |
viewport_height |
Integer |
1080 |
Alto del viewport del navegador en píxeles. |
load_media |
Boolean |
true |
Espera a las imágenes y el vídeo antes de capturar. |
enable_scroll |
Boolean |
true |
Desplaza de arriba abajo para que se disparen los cargadores diferidos. |
output_filename |
String |
nil |
Sustituye el nombre de archivo generado. |
auth |
Hash |
nil |
Credenciales HTTP basic, por ejemplo { username: "user", password: "pass" }. |
cookies, headers |
Array, Hash |
nil |
Cookies que establecer y encabezados de solicitud extra. Los tres campos de acceso del navegador se pasan sin cambios. |
auth con un encabezado Authorization. La API rechaza el conflicto, así que elige uno u otro.
convert_url_to_screenshot#
Captura un PNG de página completa de cualquier URL.
client.convert_url_to_screenshot("https://example.com", viewport_width: 1440, save_to: "shot.png")
Acepta las mismas opciones de viewport, medios, desplazamiento, nombre de archivo y acceso del navegador que convert_url_to_pdf. No admite single_page ni pdf_options.
convert_url_to_markdown#
Extrae Markdown limpio con sabor GitHub desde cualquier URL. El conversor elimina la navegación, los pies de página, los anuncios y los scripts, conserva el cuerpo principal del artículo y antepone un frontmatter YAML con el título, la descripción, la url, los enlaces y las imágenes.
client.convert_url_to_markdown("https://example.com/article", save_to: "article.md")
Útil para alimentar un pipeline de RAG, importar contenido de terceros a un CMS o construir un corpus de entrenamiento.
convert_image#
Convierte entre jpeg, png, svg, heic y webp en cualquier dirección, o rasteriza un PDF a JPEG.
# Desde una ruta en disco.
client.convert_image("photo.heic", output_format: "webp", save_to: "photo.webp")
# Desde bytes que ya tienes.
bytes = File.binread("photo.heic")
client.convert_image({ data: bytes, filename: "photo.heic" }, output_format: "webp", save_to: "photo.webp")
# Rasteriza la primera página de un PDF.
client.convert_image("invoice.pdf", output_format: "jpeg", save_to: "invoice.jpg")
El formato de entrada se resuelve a partir de la extensión del nombre de archivo, así que aquí no se puede usar un IO en crudo sin nombre de archivo. Pasa una ruta o un Hash { data:, filename: } en su lugar.
| Opción | Tipo | Obligatorio | Descripción |
|---|---|---|---|
output_format |
String |
Sí | Formato de destino. Los alias jpg, yml, htm y md se normalizan por ti. |
save_to |
String |
No | Ruta local donde descargar el resultado. |
output_filename |
String |
No | Sustituye el nombre de archivo generado. |
convert_document#
Convierte documentos y formatos de datos. output_format es "pdf" por defecto.
# docx a pdf
client.convert_document("report.docx", save_to: "report.pdf")
# json a yaml
client.convert_document("data.json", output_format: "yaml", save_to: "data.yaml")
# markdown a pdf con configuración de página personalizada
client.convert_document(
"README.md",
output_format: "pdf",
pdf_options: { page_size: "A4", margins: { top: 20, bottom: 20, left: 25, right: 25 } },
save_to: "readme.pdf"
)
Extensiones de entrada reconocidas: .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. Envía los archivos .epub a través de convert_to_pdf o convert_to_markdown en su lugar.
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
output_format |
String |
"pdf" |
Formato de destino. |
save_to |
String |
nil |
Ruta local donde descargar el resultado. |
output_filename |
String |
nil |
Sustituye el nombre de archivo generado. |
pdf_options |
Hash |
nil |
Configuración de página, respetada cuando la salida es PDF. |
convert_to_markdown#
Convierte un archivo subido de casi cualquier formato de documento a Markdown limpio. El formato se detecta en el servidor.
client.convert_to_markdown("handbook.docx", save_to: "handbook.md")
Entradas aceptadas: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT y MD, además de formatos ofimáticos heredados y ODF. Las imágenes no están admitidas en este endpoint.
La salida es un único archivo .md consciente de los encabezados, lo que lo convierte en un buen bloque de construcción para la ingesta 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. Las únicas opciones son save_to: y output_filename:.
convert_to_pdf#
Convierte un archivo subido de casi cualquier formato a PDF.
# pptx a pdf
client.convert_to_pdf("slides.pptx", save_to: "slides.pdf")
# pdf de paso directo, normalizado a escala de grises
client.convert_to_pdf("scan.pdf", pdf_options: { grayscale: true }, save_to: "gray.pdf")
Entradas aceptadas: 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 y se normaliza.
pdf_options[:grayscale]. Los demás campos de geometría de página se ignoran en este endpoint. Cuando necesites control total sobre tamaño de página, orientación y márgenes, usa convert_document o convert_url_to_pdf.
Las opciones son save_to:, output_filename: y pdf_options: (solo escala de grises).
convert_website_to_pdf y convert_website_to_screenshot#
Descubren todas las páginas de un sitio web, convierten cada una en segundo plano y reúnen los resultados en un único ZIP. Ambos métodos son asíncronos y devuelven un BatchSubmission de inmediato.
batch = client.convert_website_to_pdf(
"https://example.com",
crawl_mode: "sitemap",
exclude_patterns: ["/tag/"],
notification_email: "[email protected]"
)
status = client.wait_for_batch(batch.batch_id, save_to: "site.zip")
puts "#{status.completed} of #{status.total} pages converted"
status.items.each { |item| puts "#{item.status}\t#{item.source_url}" }
convert_website_to_screenshot funciona igual y produce un ZIP de PNG.
| Opción | Tipo | Descripción |
|---|---|---|
crawl_mode |
String |
Cómo se descubren las páginas, por ejemplo "sitemap". |
include_patterns, exclude_patterns |
Array |
Rastrea solo, u omite, las URL que coincidan con estos patrones. |
notification_email |
String |
Dirección de correo a la que avisar cuando termine el lote. |
callback_url |
String |
Webhook al que se llama al finalizar. |
output_filename |
String |
Sustituye el nombre de archivo generado del ZIP. |
viewport_width, viewport_height, load_media, enable_scroll |
Integer, Boolean |
Comportamiento de renderizado por página. Se envían solo cuando los estableces; de lo contrario el gateway aplica sus propios valores por defecto. |
auth, cookies, headers |
Hash, Array, Hash |
Acceso del navegador para páginas protegidas. |
single_page, pdf_options |
Boolean, Hash |
Solo para lotes de PDF. |
wait_for_batch(batch_id, interval: 5, timeout: 1800, save_to: nil) sondea hasta que el lote sale de "processing" y luego devuelve el BatchStatus final. Con save_to también descarga el ZIP. Lanza Enconvert::APIError con estado 504 cuando se alcanza el timeout, y con estado 500 cuando un lote termina sin ZIP que guardar.
get_job_status#
Sondea un único job de conversión por id.
status = client.get_job_status("job_abc123")
case status.status
when "success" then puts status.presigned_url
when "failed" then warn status.error
else puts "still processing"
end
Devuelve un JobStatus con status, presigned_url, object_key y error. Rara vez necesitas llamarlo tú mismo, porque el SDK ya lo sondea en tu nombre. Ver Recuperación de timeouts.
Conversiones admitidas#
La gem lleva la tabla completa de endpoints {input}-to-{output} implementados y valida cada par localmente, así que un par no admitido lanza Enconvert::Error antes de que ninguna solicitud salga de tu proceso.
Enconvert.valid_outputs_for("json") # => ["csv", "toml", "xml", "yaml"]
Enconvert.valid_outputs_for("pdf") # => ["jpeg"]
Enconvert::IMPLEMENTED_CONVERSIONS.size # => 43
| 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 |
Eso son 43 endpoints: 13 de texto estructurado, 9 de documentos y 21 de imágenes.
Inteligencia web (V2)#
client.v2 convierte páginas web en vivo en datos listos para agentes: renderizar, enumerar, buscar, extraer, ingerir y monitorizar. Cada renderizado lleva render_quality, una puntuación de 0.0 a 1.0 adjunta a cada lectura. Una puntuación baja significa que la página no se renderizó limpiamente, normalmente por una página de desafío, un muro de cookies, una barrera de inicio de sesión o un armazón vacío de JavaScript. El contenido se sigue devolviendo, pero marcado, junto con un array warnings y un Hash deductions que indica qué comprobaciones saltaron. Lee la puntuación antes de poner el contenido en ningún sitio, y una mala lectura nunca entrará en silencio en el contexto de tu agente.
Todos los endpoints V2 requieren una clave de API privada. Consulta la visión general de V2 para la referencia a nivel de endpoint.
Perceive#
Renderiza una URL en los artefactos que pidas. perceive es síncrono y devuelve la operación completada con URL de artefacto recién firmadas.
op = client.v2.perceive(
"https://example.com",
outputs: %w[markdown screenshot structured],
extract: %w[tables metadata],
only_main_content: true
)
puts op.operation_id, op.render_quality # "per_...", luego de 0.0 a 1.0
puts op.outputs["markdown"].url # URL de artefacto firmada
puts op.outputs["markdown"].expires_in # segundos hasta que deje de funcionar
puts op.structured, op.warnings.inspect # Hash de Ruby corriente, luego Array de String
Vuelve a firmar las URL de artefacto de una operación anterior en cualquier momento:
again = client.v2.get_perceive_operation(op.operation_id)
Percibe hasta 1000 URL con un único bloque de opciones compartido. Los lotes pequeños se ejecutan en línea y vuelven completados. Los más grandes vuelven con estado "queued", así que sondéalos por job_id:
batch = client.v2.perceive_batch(
["https://a.example.com", "https://b.example.com"],
outputs: %w[markdown],
output_mode: "zip"
)
done = client.v2.get_perceive_batch(batch.job_id)
puts "#{done.completed}/#{done.total} complete, #{done.failed} failed"
puts done.zip&.url
Sáltate por completo la ida y vuelta de la URL firmada con perceive_direct, donde el cuerpo de la respuesta HTTP es el propio artefacto:
direct = client.v2.perceive_direct("https://example.com", outputs: %w[markdown])
puts direct.filename, direct.content_type, direct.content.bytesize
File.binwrite("page.md", direct.content)
# Vuelve a descargar un artefacto almacenado de una operación anterior.
raw = client.v2.download_perceive_artifact(op.operation_id, output: "markdown")
perceive_direct necesita exactamente una salida que produzca artefacto y, en caso contrario, lanza Enconvert::Error localmente. structured puede acompañar, porque se queda en línea en el servidor y nunca se transmite. download_perceive_artifact toma un output: opcional, que puedes omitir cuando la operación produjo exactamente un artefacto y debes nombrar cuando produjo más de uno, y devuelve 410 una vez que un artefacto supera su ventana de retención.
| Opción | Tipo | Descripción |
|---|---|---|
outputs |
Array |
Cualquiera de markdown, html_cleaned, html_raw, screenshot, screenshot_full_page, pdf, links, images, structured. En el servidor su valor por defecto es ["markdown", "structured"]. |
extract |
Array |
Cualquiera de tables, prices, contacts, metadata, main_content, headings, structured_data, technologies, all. |
schema |
Hash |
Descripciones de campos para la extracción estructurada. |
wait_for, wait_timeout_ms |
String, Integer |
Selector CSS que esperar antes de capturar, y el límite de esa espera. |
js_code |
String |
JavaScript que ejecutar en la página antes de capturar. |
viewport, mobile |
Hash, Boolean |
Dimensiones del viewport y si renderizar con un perfil móvil. |
headers, cookies, auth |
Hash, Array, Hash |
Acceso del navegador para páginas protegidas. |
proxy_url |
String |
Enruta el renderizado a través de tu propio proxy. |
geolocation |
Hash |
Geolocalización simulada para el contexto del navegador. |
action_chain |
Array |
Clics, desplazamientos y entradas guionizados antes de capturar. |
cache_mode |
String |
enabled, bypass o refresh. |
pdf_options |
Hash |
Geometría de página cuando pdf está entre las salidas. |
block_resources |
Array |
Tipos de recurso que bloquear, por ejemplo image, font, script. |
respect_robots |
Boolean |
Respeta robots.txt. |
only_main_content |
Boolean |
Elimina la navegación y el relleno de la salida Markdown. |
direct_download |
Boolean |
Solo lo acepta perceive. perceive_batch lo rechaza. |
La semántica completa de los parámetros vive en Perceive y Parámetros y opciones.
Discover#
Enumera las URL de un sitio sin renderizar nada. No interviene ningún navegador, así que es rápido y barato.
found = client.v2.discover(
"https://example.com",
mode: "hybrid", # "sitemap", "crawl" o "hybrid"
max_urls: 200,
max_depth: 3,
exclude_patterns: ["/tag/"],
same_domain_only: true,
respect_robots: true
)
puts found.total, found.urls.first(10)
puts found.truncated # true cuando max_urls limitó el resultado
puts found.sources.inspect # de dónde vino cada URL
Consulta Discover para el comportamiento modo a modo.
Lookup#
Ejecuta una búsqueda web por categorías y, opcionalmente, percibe los primeros resultados en la misma llamada.
search = client.v2.lookup(
"best static site generators",
category: "web", # web, news, images, scholar, patents, maps
num_results: 10,
time_filter: "month", # hour, day, week, month, year
perceive_top: 3
)
search.results.each do |hit|
puts hit.position, hit.title, hit.url
puts hit.perceive&.render_quality # presente en los resultados percibidos automáticamente
end
puts search.answer_box.inspect, search.knowledge_graph.inspect
Con perceive_top: 3, las URL de los tres primeros resultados se renderizan y llevan un PerceiveResult completo en línea en hit.perceive. Sus ids de operación también se recogen en search.perceive_operation_ids. Más en Lookup.
Distill#
Extracción estructurada guiada por schema. Proporciona exactamente uno de urls: o discover_from:, y schema: siempre es obligatorio. El SDK lanza Enconvert::Error localmente si te equivocas en eso.
extraction = client.v2.distill(
urls: ["https://example.com/pricing"],
schema: { plans: "list of plan names with monthly prices" },
css_schema: {
base_selector: ".plan-card",
fields: [
{ name: "name", type: "text", selector: "h3" },
{ name: "price", type: "text", selector: ".price" }
]
}
)
item = extraction.results.first
puts item.data.inspect
puts item.extraction_tier # "css", "llm", "mixed" o "none"
puts item.fields_from_css, item.fields_from_llm
El css_schema opcional se ejecuta primero y responde todo lo que puede solo con selectores. Únicamente los campos que se le escapan escalan al nivel del modelo de lenguaje, y por eso fields_from_css y fields_from_llm se informan por separado.
Descubrir y destilar en una sola llamada:
client.v2.distill(
discover_from: { url: "https://example.com", mode: "sitemap", max_pages: 10 },
schema: { title: "page title", summary: "one-line summary" }
)
Los tipos de campo CSS son text, attribute, html, regex, nested, list y nested_list. Las listas de campos anidados se serializan de forma recursiva. Consulta Distill.
Ingest#
Convierte un sitio entero, o un conjunto de documentos subidos, en JSONL troceado y listo para RAG mediante un único pipeline. Ingest siempre es asíncrono.
job = client.v2.ingest(
mode: "sitemap", # urls, sitemap, crawl
url: "https://docs.example.com",
max_pages: 100,
chunk: { max_words: 512, sentence_overlap: 1 },
webhook_url: "https://my.app/hooks/enconvert"
)
status = client.v2.get_ingest_job(job.job_id)
puts status.status # queued, discovering, processing, completed, failed, canceled
puts status.pages_processed, status.total_chunks
puts status.output_url if status.status == "completed" # JSONL
El modo "urls" toma una lista urls: y rechaza url:. Cualquier otro modo toma una url: semilla y rechaza urls:. Ambas reglas se imponen localmente antes de enviar la solicitud.
Sube archivos en lugar de rastrear:
file_job = client.v2.ingest_files(
["handbook.pdf", "notes.docx"],
chunk: { max_words: 512, sentence_overlap: 1 }
)
ingest_files acepta PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT y MD, además de formatos ofimáticos heredados y ODF. Cada entrada puede ser una ruta, un objeto similar a IO o un Hash { data:, filename: }.
Gestiona jobs y la entrega de webhooks:
list = client.v2.list_ingest_jobs(limit: 20)
list.jobs.each { |j| puts "#{j.job_id}\t#{j.status}\t#{j.total_chunks}" }
puts list.has_more
client.v2.cancel_ingest_job(job.job_id) # idempotente
secret = client.v2.get_webhook_secret
puts secret.secret, secret.signature_header, secret.timestamp_header,
secret.signature_scheme, secret.replay_tolerance_seconds
client.v2.rotate_webhook_secret # las firmas antiguas dejan de verificarse al instante
client.v2.retry_ingest_webhook(job.job_id) # reenvía el webhook de un job completado
retry_ingest_webhook devuelve un WebhookRetryResult con delivered, attempts, status_code y detail. Responde 409 cuando el job no está completado y 400 cuando el job no tiene webhook configurado. Consulta Ingest.
Watch#
Vuelve a renderizar una URL a una cadencia fija y te avisa cuando cambia.
watcher = client.v2.create_watcher(
"https://example.com/pricing",
frequency_minutes: 60, # mínimo de una hora
diff_mode: "auto", # auto, text, structured, tables, metadata
webhook_url: "https://my.app/hooks/changes",
notify_email: true
)
puts watcher.watcher_id, watcher.next_check_at
history = client.v2.get_watcher_snapshots(watcher.watcher_id, limit: 10)
history.snapshots.each do |snap|
puts snap.checked_at, snap.has_changes, snap.similarity, snap.change_count, snap.changes.inspect
end
client.v2.list_watchers(limit: 20)
client.v2.get_watcher(watcher.watcher_id)
client.v2.update_watcher(watcher.watcher_id, status: "paused")
client.v2.update_watcher(watcher.watcher_id, webhook_url: "") # borra el webhook
client.v2.delete_watcher(watcher.watcher_id) # borrado lógico, idempotente
update_watcher lanza Enconvert::Error si lo llamas sin campos que actualizar. delete_watcher devuelve el watcher marcado como eliminado con estado "deleted", y un watcher eliminado se lee de vuelta como 404. La semántica completa está en Watch.
Opciones de PDF#
pdf_options es un Hash corriente con claves de símbolo, aceptado por convert_url_to_pdf, convert_website_to_pdf, convert_document, convert_to_pdf (solo escala de grises) y client.v2.perceive cuando pdf está entre las salidas. Solo se envían las claves que estableces.
client.convert_url_to_pdf(
"https://example.com",
pdf_options: { page_size: "A4", orientation: "landscape", scale: 0.9,
margins: { top: 10, bottom: 10, left: 15, right: 15 } },
save_to: "report.pdf"
)
| Clave | Tipo | Descripción |
|---|---|---|
page_size |
String |
"A4", "A3", "Letter", "Legal" y similares. |
page_width |
Number |
Ancho de página explícito, como alternativa a page_size. |
page_height |
Number |
Alto de página explícito. |
orientation |
String |
"portrait" o "landscape". |
margins |
Hash |
{ top:, bottom:, left:, right: }, todos opcionales. |
scale |
Number |
Escala de renderizado, por ejemplo 0.9 para el 90 por ciento. |
grayscale |
Boolean |
Posprocesa el PDF a escala de grises. |
header |
Hash |
Texto del encabezado por región de página. |
footer |
Hash |
Texto del pie por región de página. |
Manejo de errores#
Todo fallo es un Enconvert::Error o una de sus subclases, así que las cláusulas rescue pueden ser tan amplias o tan estrechas como quieras. Los fallos de validación del lado del cliente, como un par de conversión no admitido o un schema ausente, lanzan el Enconvert::Error base antes de realizar ninguna solicitud HTTP.
begin
client.v2.perceive("https://example.com")
rescue Enconvert::AuthenticationError
warn "Invalid or missing API key"
rescue Enconvert::QuotaError
warn "Request was rejected with 402"
rescue Enconvert::RateLimitError
warn "Too many requests, back off and retry"
rescue Enconvert::APIError => e
warn "API error [#{e.status_code}]: #{e.message}"
rescue Enconvert::Error => e
warn "Client-side validation failed: #{e.message}"
end
| Clase | Se lanza en | Código de estado |
|---|---|---|
Enconvert::AuthenticationError |
Clave de API inválida, ausente o revocada | 401, 403 |
Enconvert::QuotaError |
Se lanza ante un HTTP 402 | 402 |
Enconvert::RateLimitError |
Demasiadas solicitudes | 429 |
Enconvert::APIError |
Cualquier otra respuesta de 400 o superior | el código real |
Enconvert::Error |
Clase base, más todos los fallos de validación local | ninguno |
APIError#status_code te da el estado HTTP, y el mensaje se toma del campo detail o error del cuerpo de la respuesta cuando el cuerpo es JSON. El mapa completo de mensajes vive en Códigos de error.
Recuperación de timeouts#
Un renderizado largo de URL a PDF o una conversión de documento grande pueden sobrevivir al timeout del proxy inverso incluso cuando la conversión en sí tiene éxito en el servidor. El SDK se recupera de eso de forma transparente, sin código por tu parte.
- Antes de cada solicitud de conversión, el cliente genera un id de job de 32 caracteres y lo envía como
job_iden el cuerpo o en el formulario multiparte. - Si la solicitud vuelve con un estado de 500 o superior, el cliente pasa a sondear
GET /v1/convert/status/{job_id}cada 3 segundos. Un404mientras la fila del job aún se está escribiendo se ignora y el sondeo continúa. - En cuanto el job se lee como
success, se devuelve el resultado. En cuanto se lee comofailed, se lanzaEnconvert::APIErrorcon estado500y el mensaje de error del servidor. - El plazo de sondeo es de 300 segundos. Pasado ese punto, el cliente lanza
Enconvert::APIErrorcon estado504y el mensajeConversion timed out.
El job_id generado por el cliente también se fusiona en cada respuesta correcta, de modo que result.job_id siempre está relleno y puedes sondear get_job_status tú mismo más adelante si quieres.
convert_website_to_pdf y convert_website_to_screenshot no crean una fila por job, así que un 5xx ahí significa que falló el propio envío y se expone de inmediato en lugar de sondearse.
Configuración#
client = Enconvert::Client.new(
api_key: ENV.fetch("ENCONVERT_API_KEY"),
timeout: 300,
base_url: "https://api.enconvert.com"
)
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
api_key |
String |
obligatorio | Tu clave de API privada. Un valor nil o en blanco lanza Enconvert::Error en la construcción. |
timeout |
Integer |
300 |
Timeout de apertura y de lectura en segundos, aplicado a cada solicitud. |
base_url |
String |
"https://api.enconvert.com" |
URL base de la API. Las barras finales se eliminan. Sobrescríbela para un gateway autoalojado. |
Las solicitudes se autentican con el encabezado X-API-Key. Cuando pasas save_to, la descarga omite deliberadamente ese encabezado, porque la URL prefirmada de almacenamiento no debe recibir tu clave de API.
Forma del resultado#
Toda conversión de un solo archivo y de una sola página devuelve un Enconvert::ConversionResult:
result = client.convert_document("report.docx", save_to: "report.pdf")
result.presigned_url # URL firmada para la salida convertida
result.object_key # clave del objeto en almacenamiento
result.filename # nombre de archivo del servidor
result.file_size # bytes, o nil
result.conversion_time_seconds # segundos, o nil
result.job_id # siempre rellenado por el cliente
Los demás Structs de V1 son JobStatus (status, presigned_url, object_key, error), BatchSubmission (batch_id, status, url_count, total_discovered, discovery_method, output_format), BatchStatus (batch_id, status, total, completed, failed, in_progress, output_mode, zip_download_url, items) y BatchItem (source_url, status, download_url, output_file_size, duration).
En el lado de V2, PerceiveResult es el que hay que conocer:
op.operation_id # "per_..."
op.status # queued, processing, completed, failed
op.url, op.url_final, op.content_hash
op.render_quality, op.cache_hit # de 0.0 a 1.0 (o nil), y si vino de la caché
op.outputs # Hash de nombre => V2OutputArtifact
op.structured, op.extraction_tier # Hash (o nil), y cómo se extrajo
op.tokens.input, op.tokens.output, op.cost_cents, op.duration_ms
op.error, op.warnings # String o nil, y Array de String
op.status_code # estado HTTP del origen de la página
op.deductions, op.options_echo # por qué bajó render_quality, y las opciones usadas
Un V2OutputArtifact lleva url, object_key, size_bytes, content_type y expires_in (900 segundos por defecto), así que las URL de artefacto firmadas son de vida corta. Vuelve a firmarlas con get_perceive_operation, o descarga los bytes con download_perceive_artifact. Las cargas útiles proporcionadas por el usuario, como schemas, datos extraídos, campos rastreados y cambios de diff, pasan intactas como Hashes de Ruby corrientes.
Las URL prefirmadas de las conversiones V1 también son temporales. Pasa save_to cuando quieras los bytes en disco, o cópialos a tu propio bucket para almacenamiento permanente.
Código fuente e incidencias#
- RubyGems: enconvert
- GitHub: conversionapi/ruby-sdk
- Ruby: 3.0.0 o posterior, sin dependencias en tiempo de ejecución
- Licencia: MIT
- Otros lenguajes: Todos los SDK
- Referencia de la API: Visión general de endpoints, Autenticación
Preguntas frecuentes#
¿Cómo convierto archivos en Ruby con una gem?#
Ejecuta gem install enconvert, construye un cliente con Enconvert::Client.new(api_key: ENV.fetch("ENCONVERT_API_KEY")) y llama a un método como convert_document, convert_image o convert_url_to_pdf. Pasa save_to: y el SDK descarga el resultado a esa ruta por ti, creando los directorios padre que hagan falta.
¿Cómo convierto DOCX a PDF en Ruby?#
client.convert_document("report.docx", save_to: "report.pdf"). El formato de salida es "pdf" por defecto, así que solo necesitas output_format: cuando quieres otra cosa. La misma llamada funciona con .doc, .xls, .xlsx, .ppt, .pptx, .odt, .ods, .odp, .ots, .pages y .numbers.
¿Cómo convierto una URL a PDF en Ruby?#
client.convert_url_to_pdf("https://example.com", save_to: "page.pdf"). Establece single_page: false para paginar en lugar de producir una única página continua, y pasa pdf_options: para el tamaño de página, la orientación, los márgenes y la escala. Para una página tras autenticación HTTP basic, añade auth: { username: ..., password: ... }.
¿Cómo convierto HEIC a WebP en Ruby?#
client.convert_image("photo.heic", output_format: "webp", save_to: "photo.webp"). El formato de entrada procede de la extensión del nombre de archivo, y la gem convierte libremente entre jpeg, png, svg, heic y webp. También rasteriza un PDF a JPEG con output_format: "jpeg".
¿Cómo extraigo una página web a Markdown desde Ruby?#
Dos opciones. client.convert_url_to_markdown(url, save_to: "article.md") te da Markdown limpio con sabor GitHub y frontmatter YAML. client.v2.perceive(url, outputs: %w[markdown]) te da el mismo contenido más una puntuación render_quality, avisos y extracción estructurada opcional, que es lo que quieres cuando un agente va a leer el resultado.
¿Qué significa render_quality y por qué debería comprobarlo?#
Es una puntuación de 0.0 a 1.0 adjunta a cada lectura V2 que refleja con qué limpieza se renderizó realmente la página. Una página de desafío, un muro de cookies, una barrera de inicio de sesión, una página de error HTTP o un armazón vacío de JavaScript puntúan bajo. El contenido se sigue devolviendo para que puedas inspeccionarlo, con warnings y deductions que explican la puntuación. Ponle una condición antes de guardar el texto o de dárselo a un modelo.
¿Reintenta el SDK de Ruby cuando una conversión larga supera el timeout?#
Sí. Cada solicitud de conversión lleva un job_id generado por el cliente y, si la solicitud falla con un estado de 500 o superior, el SDK sondea GET /v1/convert/status/{job_id} cada 3 segundos durante hasta 300 segundos. Devuelve el resultado en cuanto el job tiene éxito y lanza Enconvert::APIError con estado 504 si vence el plazo. Los envíos de lotes de sitios completos quedan excluidos, porque no tienen una fila por job.
¿Qué pasa si pido un par de conversión que la API no implementa?#
La gem lanza Enconvert::Error localmente, antes de cualquier llamada de red, y el mensaje enumera las salidas válidas para esa entrada. Puedes consultar la misma tabla tú mismo con Enconvert.valid_outputs_for("json"), que devuelve ["csv", "toml", "xml", "yaml"].
¿Puedo usar la gem enconvert dentro de Rails o en un job en segundo plano?#
Sí, y un job en segundo plano es el lugar adecuado para ella. La gem es net/http puro sin dependencias en tiempo de ejecución ni estado global, así que una instancia de Enconvert::Client se puede construir por job o memoizar por proceso con total seguridad. Las conversiones largas bloquean el hilo que llama hasta el timeout configurado, que es de 300 segundos por defecto, así que mantenlas fuera del ciclo de una petición web.
¿Qué versiones de Ruby admite el SDK?#
Ruby 3.0.0 y posteriores, según declara required_ruby_version en el gemspec. No hay dependencias de gems en tiempo de ejecución, así que se instala en cualquier grupo de Bundler sin arrastrar un árbol de dependencias detrás.
¿Cómo verifico la firma de un webhook de ingest?#
Llama a client.v2.get_webhook_secret, que crea el secreto de firma del proyecto en el primer uso y lo devuelve junto con signature_header, timestamp_header, signature_scheme y replay_tolerance_seconds. Calcula el HMAC sobre el cuerpo entregado y compáralo con el encabezado de firma. Usa rotate_webhook_secret para invalidar el secreto antiguo, y retry_ingest_webhook(job_id) para reenviar la notificación de un job completado.