API de Web Scraping para Markdown, Capturas de Pantalla y Datos Estructurados#

POST /v2/perceive es la API de web scraping de EnConvert: renderiza una URL una vez en un navegador headless real, con JavaScript ejecutado y contenido de carga diferida (lazy load) ya cargado, y te devuelve todos los outputs que pidas a partir de ese único render: Markdown limpio (por defecto solo el contenido principal, sin el chrome del sitio), HTML limpio o en bruto, una captura de pantalla, un PDF, el inventario de enlaces e imágenes, y datos estructurados (metadatos de la página, JSON-LD, encabezados, tablas). Los outputs de archivo vuelven como URLs de descarga pre-firmadas de corta duración, el bloque estructurado va inline, y los lotes de más de 10 URLs se ejecutan de forma asíncrona detrás de un job_id que se consulta por polling. Una sola solicitud reemplaza toda una pila de llamadas separadas: url-to-markdown, url-to-screenshot, url-to-pdf, más tu propio scraping.

Este es el llamado más pequeño y útil. Envía una URL y recibe Markdown limpio junto con los metadatos estructurados de la página:

curl -X POST https://api.enconvert.com/v2/perceive \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/pricing",
    "outputs": ["markdown", "structured"]
  }'

La respuesta incluye una URL de descarga pre-firmada para el archivo Markdown y el bloque estructurado inline:

{
    "operation_id": "per_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7",
    "status": "completed",
    "url": "https://example.com/pricing",
    "url_final": "https://example.com/pricing",
    "content_hash": "9f2b8c1a...d4e5",
    "render_quality": 0.93,
    "cache_hit": false,
    "outputs": {
        "markdown": {
            "url": "https://spaces.example.com/...signed...",
            "object_key": "env/files/4127/v2-perceive/per_3f9a..._markdown.md",
            "size_bytes": 8421,
            "content_type": "text/markdown; charset=utf-8",
            "expires_in": 900
        }
    },
    "structured": {
        "metadata": {
            "title": "Pricing",
            "description": "Simple, usage-based pricing."
        },
        "structured_data": [
            {"@type": "Product", "name": "Studio", "offers": {"price": "99"}}
        ]
    },
    "extraction_tier": "heuristic",
    "tokens": {"input": 0, "output": 0},
    "cost_cents": 0.0,
    "duration_ms": 6230,
    "warnings": []
}

Endpoints#

Método Ruta Propósito
POST /v2/perceive Percibe una única URL y devuelve los outputs solicitados.
GET /v2/perceive/{operation_id} Vuelve a obtener una operación pasada con URLs de descarga recién firmadas.
POST /v2/perceive/batch Percibe hasta 1,000 URLs que comparten un mismo conjunto de opciones.
GET /v2/perceive/batch/{job_id} Consulta el estado y los resultados por URL de un lote.
DELETE /v2/perceive/batch/{job_id} Cancela un lote en ejecución.

Content-Type: application/json en cada POST.


Autenticación#

Autentícate con una clave privada en el encabezado X-API-Key para llamadas de servidor a servidor. Este es el flujo que usan los ejemplos a continuación.

X-API-Key: sk_your_private_key

Las claves públicas con un token JWT bearer también funcionan, usando el mismo flujo que cualquier otro endpoint: genera un token con tu clave pk_ y luego envíalo como Authorization: Bearer <token>. El flujo completo, incluido el bloqueo por dominio y la renovación de tokens, está en la guía de autenticación.

Cada clave de API tiene una lista blanca de endpoints permitidos. Si /v2/perceive no está en la lista de la clave, la solicitud se rechaza con 403.


Cómo funciona perceive#

Una solicitud dispara un render de navegador a través de un singleton compartido de Chrome headless, y luego materializa cada output a partir de ese render. Nunca pagas por la misma página dos veces en una sola llamada.

  1. Render. La página se obtiene a través de un fallback automático de múltiples motores: primero una huella TLS de navegador real y rápida, escalando a Chrome headless cuando la página está bloqueada o necesita JavaScript, y una vez más a un render reforzado con técnicas de sigilo (stealth) cuando una página sigue pareciendo bloqueada por protección anti-bot, de modo que más páginas del mundo real devuelven contenido utilizable. En el navegador, se descartan los banners de cookies, se hace scroll en la página para disparar el contenido de carga diferida, se gestionan los encabezados fijos (sticky), y se da tiempo a que las imágenes carguen. Es el mismo pipeline de captura que impulsa el endpoint url-to-pdf.
  2. Materializar. A partir del DOM renderizado, perceive construye todo lo que hayas indicado en outputs: Markdown, HTML limpio/en bruto, enlaces, imágenes, una captura de pantalla, un PDF. El DOM se normaliza antes para que el Markdown refleje lo que ve un lector: los fences de código conservan su lenguaje, los enlaces de tarjeta su estructura, y los elementos de interfaz se eliminan bajo only_main_content. Consulta Calidad del Markdown.
  3. Extraer. Si solicitaste el output structured, perceive ejecuta una pasada heurística para obtener metadatos de la página, JSON-LD, encabezados y tablas. Si además envías un schema y tu plan incluye el nivel LLM, una pasada asistida por LLM completa el schema cuando la pasada heurística se queda corta.
  4. Puntuar. Un puntaje de calidad de render (0.0–1.0) distingue un render real de uno fallido. Los puntajes por debajo de 0.40 indican un render fallido: una página anti-bot, un muro de inicio de sesión, una página de error HTTP, un soft 404 o un cascarón vacío. Consulta deductions para conocer el motivo y status_code para el estado del servidor de origen.

Los outputs binarios y de texto (Markdown, HTML, capturas de pantalla, PDFs, el JSON de enlaces e imágenes) se suben al almacenamiento y se devuelven como URLs pre-firmadas que expiran a los 15 minutos. El bloque structured se devuelve inline en el JSON. Vuelve a obtener cualquier operación con GET /v2/perceive/{operation_id} para conseguir un nuevo conjunto de URLs firmadas.


Parámetros de la solicitud#

La validación es estricta: una clave de solicitud que el schema no conoce se rechaza con 422 nombrando el campo afectado. Las claves desconocidas nunca se ignoran silenciosamente. Cada cuerpo 422 incluye además un array errors de nivel superior con mensajes legibles por humanos, junto a la lista detail legible por máquinas.

Básicos#

Parámetro Tipo Valor por defecto Descripción
url string - La página a percibir. Debe comenzar con http:// o https://. Máximo 2,048 caracteres. Obligatorio.
outputs string[] ["markdown", "structured"] Qué outputs producir. Consulta Salidas.
extract string[] [] Qué campos estructurados extraer cuando structured está en outputs. Consulta Extracción estructurada.
schema object null Un JSON schema que describe los campos que quieres extraer. Activa el nivel de extracción LLM en los planes que lo incluyen.
only_main_content boolean true Elimina el chrome del sitio (navegación, encabezado, pie de página, barras laterales, banners de cookies, nodos ocultos) y los elementos de interfaz (botones, tiras de pestañas, widgets «¿Te ha resultado útil esta página?», etiquetas solo para lectores de pantalla, migas de pan) del output markdown y del extract main_content, protegido por una guarda de fidelidad: si la limpieza eliminara demasiado contenido real, se devuelve la página completa y se añade una advertencia. Las URLs de imagen se renderizan como su texto alt (la lista completa de imágenes sigue disponible vía outputs: ["images"]). Define false para la página completa, sin eliminar nada. Consulta Calidad del Markdown.
truncate_data_arrays boolean sin definir Colapsa series largas de literales numéricos (vectores de embeddings en bruto, volcados de tensores impresos en celdas de salida de notebooks) a una muestra inicial más un recuento, p. ej. ... [truncated 1520 of 1536 values]. Sin definir sigue a only_main_content: activo cuando la página se está depurando, inactivo cuando pediste la página tal cual. Define true o false para controlarlo explícitamente.
allow_degraded boolean false Devuelve el render incluso cuando es un desafío anti-bot o una página de bloqueo sin contenido de página. Por defecto, un render así falla con 502 en lugar de entregar el texto de la interstitial como si fuera la página.
direct_download boolean false Devuelve los bytes del artefacto directamente como cuerpo de la respuesta HTTP en lugar de un sobre JSON. Requiere exactamente un output que produzca artefacto. Solo para solicitudes de una URL, ya que el endpoint de lotes lo rechaza con 422. Consulta Descarga directa.
cache_mode string "enabled" enabled, bypass, o refresh. Consulta Caché.

Salidas#

outputs acepta cualquier combinación de estos nombres:

Output Se devuelve como Qué obtienes
markdown URL firmada Markdown limpio de la página. Con only_main_content (por defecto true) se elimina el chrome del sitio, como navegación, encabezado, pie de página, barras laterales, banners de cookies y nodos ocultos, detrás de una guarda de fidelidad, y las URLs de imagen se renderizan como su texto alt. Los bloques de código conservan su lenguaje en el fence (```python) en ambos modos. Define only_main_content: false para la página completa. Consulta Calidad del Markdown.
html_cleaned URL firmada El HTML renderizado con scripts, estilos y elementos de relleno (boilerplate) eliminados.
html_raw URL firmada El HTML renderizado completo, tal como lo produjo el navegador.
screenshot URL firmada Un PNG del viewport en el tamaño de viewport solicitado (o el predeterminado).
screenshot_full_page URL firmada Un PNG de página completa que captura todo el alto del scroll.
pdf URL firmada Un PDF de la página. Admite toda la superficie de pdf_options (ver abajo).
links URL firmada Un array JSON con todos los enlaces encontrados, con URLs absolutas y texto de anclaje.
images URL firmada Un array JSON con todas las imágenes, con src absoluto y texto alt.
structured JSON inline Datos estructurados extraídos de la página (el campo structured de la respuesta).

Calidad del Markdown#

Antes de convertir la página, el DOM renderizado se normaliza para que el Markdown refleje lo que ve un lector y no cómo se construyó la página. Esto se ejecuta en cada render, así que el resultado no depende de qué estrategia de extracción gane para una página concreta.

Se aplica siempre, en ambos modos de only_main_content:

  • Los fences de código conservan su lenguaje. El lenguaje se lee de la convención que use el sitio (class="language-python", data-lang, un atributo language desnudo o un wrapper del resaltador) y se normaliza, de modo que llega ```python en lugar de un fence pelado.
  • Los enlaces de tarjeta siguen siendo legibles. Un enlace que envuelve un encabezado y una descripción se convierte en un título enlazado seguido de su descripción, en lugar de un único enlace amontonado como [DatabaseSupabase provides a full Postgres database...]. La URL de destino se conserva.
  • Los encabezados se mantienen en una sola línea. Un encabezado cuyo texto vive dentro de un elemento anidado ya no emite un ## pelado con el texto varado debajo.
  • Los elementos adyacentes ya no se concatenan. Los layouts que separan sus elementos con CSS en vez de con espacios producían YesNo y EvaluationDeploymentProduction; ahora se leen como palabras separadas.
  • Se eliminan los caracteres invisibles: espacios de ancho cero usados como etiquetas de anclaje, guiones suaves y glifos del Área de Uso Privado de las fuentes de iconos, que llegan como tokens no imprimibles.
  • Se descartan los elementos vacíos: elementos <i> que solo contenían un icono y se renderizaban como un __ suelto, y enlaces cuya etiqueta está vacía.

Además, con only_main_content: true:

  • Se eliminan los controles de interfaz: botones, tiras de pestañas, pistas de atajos de teclado, acciones «Copy page» / «On this page» y widgets de valoración «¿Te ha resultado útil esta página? Sí/No». Un control que contiene contenido real (una pregunta de FAQ, el cuerpo de una tarjeta clicable) se conserva.
  • Se elimina el texto destinado solo a lectores de pantalla: enlaces de salto y las etiquetas «Section titled ...» que muchos temas de documentación añaden a cada encabezado.
  • Se respeta el contenido que el sitio declara como no-contenido: bloques marcados con data-nosnippet, data-pagefind-ignore o data-noindex, salvo que contengan encabezados o código.
  • Se colapsan los bloques duplicados: los diseños responsive que envían una copia de escritorio y otra móvil de la misma barra, y los carruseles que pre-renderizan cada fotograma, aparecen una sola vez.
  • Se descartan las migas de pan y las etiquetas de antetítulo situadas sobre el título de la página.

El contenido diferido se conserva deliberadamente: un panel de pestaña inactivo dentro de la región de contenido guarda un ejemplo de código real (el ejemplo de Python en una pestaña, el de JavaScript en otra), de modo que ambos llegan al Markdown y no solo la pestaña que por casualidad estuviera seleccionada en el momento del render.

Renderizado y espera#

Parámetro Tipo Valor por defecto Descripción
viewport object 1920 x 1080 {"width": <int>, "height": <int>}. Ancho 320–3840, alto 240–2160.
mobile boolean false Renderiza en un viewport móvil (390 x 844) a menos que se defina viewport explícitamente.
wait_for string null Espera tras la navegación un selector CSS (".price" o "css:.price") o una expresión JS ("js:window.dataReady === true").
wait_timeout_ms integer 30000 Cuánto puede esperar wait_for, en milisegundos. 0–60,000. Un timeout se degrada a advertencia; la página se captura tal cual.
js_code string null JavaScript para ejecutar en la página tras la navegación. Máximo 20,000 caracteres. Un error se convierte en advertencia, no en fallo.
block_resources string[] [] Tipos de recurso a abortar antes de que carguen. Cualquiera de image, media, font, stylesheet, script, xhr, fetch, websocket, manifest, other. Útil para renders más rápidos, solo de texto.
respect_robots boolean false Cuando es true, una URL no permitida por el robots.txt del sitio se rechaza con 403.
pdf_options object null Formato de página, márgenes, encabezados, pies de página, escala y orientación para el output pdf. Mismo objeto que url-to-pdf. Sin pdf_options, perceive produce una única página continua, idéntica byte a byte a la de V1 url-to-pdf.

Solicitudes autenticadas y personalizadas#

Parámetro Tipo Valor por defecto Descripción
auth object null HTTP Basic Auth para la página de destino: {"username": "...", "password": "..."}.
cookies array null Cookies a inyectar antes de la navegación. Máximo 50. Cada una necesita name, value, y domain o url.
headers object null Encabezados de solicitud personalizados. Máximo 20. Nombres bloqueados: host, content-length, transfer-encoding, connection, upgrade, te, trailer.
Reservado, aún no disponible. proxy_url (Production+), geolocation y action_chain son aceptados por el schema de la solicitud pero hoy devuelven 422. Llegarán en una versión posterior; enviarlos ahora te indica exactamente qué opción no está lista en lugar de ignorarla silenciosamente.

Extracción estructurada#

Cuando structured está en outputs, la lista extract controla qué campos extrae perceive. Si no pides nada, se usa por defecto metadata y structured_data.

Valor de extract Campo en structured Estado
metadata metadata Activo
structured_data structured_data (JSON-LD) Activo
headings headings Activo
tables tables Activo
main_content main_content (texto, limitado a 50,000 caracteres) Activo
all se expande a todos los campos activos anteriores Activo
prices - Aún no disponible: devuelve una advertencia, se omite
contacts - Aún no disponible: devuelve una advertencia, se omite
technologies - Aún no disponible: devuelve una advertencia, se omite

Para ser claros: prices, contacts y technologies son nombres reservados. Si solicitas uno hoy, no da error: el nombre cae en el array warnings y se elimina de structured.

Extracción guiada por schema#

Envía un schema para extraer campos específicos hacia structured.extracted:

{
    "url": "https://example.com/product/widget",
    "outputs": ["markdown", "structured"],
    "schema": {
        "type": "object",
        "properties": {
            "name": {"type": "string"},
            "price": {"type": "number"},
            "in_stock": {"type": "boolean"}
        }
    }
}

El nivel de extracción asistida por LLM completa el schema, y solo se activa cuando se cumplen todas estas condiciones: enviaste un schema, tu plan incluye el nivel LLM (Indie en adelante), la página no fue puntuada como bloqueada, y la pasada heurística dejó campos del schema vacíos. Cuando se ejecuta, extraction_tier es "llm", y tokens y cost_cents reportan lo que costó esa extracción; en caso contrario, extraction_tier es "heuristic" y ambos son cero.

Nota. La extracción por schema tiene un límite estricto para proteger tu factura: una única extracción está limitada por solicitud, y el gasto del proyecto consume tu saldo mensual de créditos de IA ($5 / $15 / $40 al mes en Indie / Studio / Production; los créditos no usados se acumulan). La extracción con LLM consume créditos, no ops. Si se alcanza un límite o se agota el saldo, perceive devuelve el resultado heurístico con una nota en warnings en lugar de gastar de más. En un plan sin el nivel LLM, solo obtienes datos structured heurísticos.


Respuesta#

Tanto POST /v2/perceive como GET /v2/perceive/{operation_id} devuelven el mismo objeto.

Campo Tipo Descripción
operation_id string ID opaco (per_...). Úsalo con el endpoint GET y cítalo al contactar a soporte.
status string queued, processing, completed, o failed.
url string La URL que enviaste.
url_final string La URL después de las redirecciones.
content_hash string SHA-256 de la página renderizada. Determina la caché de 1 hora.
render_quality number 0.0–1.0. Los puntajes por debajo de 0.40 indican un render fallido: una página anti-bot, un muro de inicio de sesión, una página de error HTTP, un soft 404 o un cascarón vacío. Consulta deductions para conocer el motivo y status_code para el estado del servidor de origen.
status_code integer Estado HTTP de la respuesta final del documento principal (p. ej. 200, 404). null cuando se desconoce.
deductions object Deducciones de calidad de render con nombre que se aplicaron, p. ej. {"http_error": 0.7}, {"soft_404": 0.65}, {"login_wall": 0.65}. Vacío en un render limpio.
options_echo object Eco de las opciones de la solicitud que el servidor aplicó. Los secretos se reducen a booleanos (auth_provided, cookies_provided, headers_provided, js_code_provided, schema_provided, pdf_options_provided). Las opciones simples (outputs, only_main_content, truncate_data_arrays, allow_degraded, extract, cache_mode, mobile, respect_robots, direct_download, wait_for, wait_timeout_ms, viewport, block_resources) se devuelven tal como se aplicaron. truncate_data_arrays se devuelve como el booleano resuelto, de modo que aunque lo dejes sin definir sabrás en qué sentido se resolvió.
cache_hit boolean true cuando el resultado vino de la caché en lugar de un render nuevo.
outputs object Mapa de nombre de output a {url, object_key, size_bytes, content_type, expires_in}. Las URLs firmadas expiran en 900 segundos.
structured object Datos estructurados inline, presentes cuando se solicitó structured.
extraction_tier string heuristic, css, o llm.
tokens object Tokens LLM usados en formato {input, output}. Cero a menos que se haya ejecutado el nivel LLM.
cost_cents number Costo LLM en centavos para esta operación. Cero a menos que se haya ejecutado el nivel LLM.
duration_ms integer Tiempo de render de extremo a extremo.
error string Solo se define cuando status es failed.
warnings string[] Notas no fatales: un timeout de wait_for, un extract omitido, una marca de página bloqueada, un fallback de only_main_content a la página completa, un aviso de que se truncaron arrays numéricos largos.

Recuperar una operación#

Las URLs firmadas expiran a los 15 minutos. Para descargar un output más tarde, vuelve a obtener la operación. Perceive vuelve a firmar cada URL a partir de las claves de objeto almacenadas. No se produce ningún re-render, así que esto no consume ops.

curl https://api.enconvert.com/v2/perceive/per_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7 \
  -H "X-API-Key: sk_your_private_key"

Un ID de operación desconocido, o uno que pertenece a otro proyecto, devuelve 404. La existencia nunca se filtra entre proyectos.


Descarga directa#

Por defecto, cada output de archivo vuelve como una URL pre-firmada que obtienes en una segunda solicitud. Define direct_download: true en el POST para saltarte el sobre: el cuerpo de la respuesta HTTP es los bytes del artefacto, sin JSON, sin URL firmada y sin segunda descarga. La solicitud debe producir exactamente un output que genere artefacto (outputs: ["markdown"], outputs: ["pdf"], …), de lo contrario se rechaza con 400. Los metadatos que habrían ido en el JSON viajan en cambio en encabezados de respuesta: Content-Disposition, X-Operation-Id, X-Object-Key, X-Cache-Hit, X-Render-Quality, X-Source-Status-Code, X-Content-Hash y X-Warnings-Count.

curl -X POST https://api.enconvert.com/v2/perceive \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/blog/post",
    "outputs": ["markdown"],
    "direct_download": true
  }' \
  -o post.md

Los endpoints GET transmiten los artefactos almacenados de la misma manera:

  • GET /v2/perceive/{operation_id}?direct_download=true&output=markdown transmite un artefacto de una operación pasada. output es obligatorio cuando la operación produjo más de un artefacto. Un artefacto que superó la ventana de retención de tu plan responde 410.
  • GET /v2/perceive/batch/{job_id}?direct_download=true transmite el ZIP del lote. Vale para lotes con output_mode: "zip" cuyo archivo ya está listo; de lo contrario responde 400.

direct_download es solo para URLs individuales: POST /v2/perceive/batch lo rechaza con 422. Define output_mode como "zip" y descarga el archivo comprimido. Consulta Percepción por lotes.


Percepción por lotes#

POST /v2/perceive/batch percibe una lista de URLs que comparten un mismo bloque options. Cada URL se renderiza a través del mismo pipeline que una llamada individual y genera su propia fila de operación.

curl -X POST https://api.enconvert.com/v2/perceive/batch \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "https://example.com/a",
      "https://example.com/b",
      "https://example.com/c"
    ],
    "options": {"outputs": ["markdown"]},
    "output_mode": "manifest"
  }'

Los lotes de 10 URLs o menos se ejecutan inline y responden 200 con todos los resultados completados. Los lotes más grandes responden 202 con un job_id; las URLs se procesan una a la vez y las consultas por polling para obtener los resultados:

curl https://api.enconvert.com/v2/perceive/batch/{job_id} \
  -H "X-API-Key: sk_your_private_key"

La respuesta del lote reporta el progreso agregado y lleva un resultado completo de perceive por cada URL una vez renderizada:

{
    "job_id": "bat_8c1a...",
    "status": "partial",
    "output_mode": "manifest",
    "total": 3,
    "completed": 2,
    "failed": 1,
    "pending": 0,
    "items": [
        {"operation_id": "per_...", "status": "completed", "url": "https://example.com/a"},
        {"operation_id": "per_...", "status": "completed", "url": "https://example.com/b"},
        {"operation_id": "per_...", "status": "failed", "url": "https://example.com/c"}
    ]
}

status es queued, processing, completed, failed, partial (algunas URLs tuvieron éxito, otras fallaron), o canceled. Define output_mode como zip para agrupar todos los artefactos en un único ZIP, devuelto en el campo zip cuando el lote termina.

Durable y reanudable#

Los lotes son resistentes a reinicios. Si el servicio se reinicia mientras un lote está en curso, el lote se reanuda automáticamente y solo vuelve a renderizar las URLs que no habían terminado, así que las URLs ya completadas conservan sus artefactos. Nunca necesitas reenviar un lote por culpa de un reinicio.

Cancelar un lote#

DELETE /v2/perceive/batch/{job_id} cancela un lote en ejecución. El worker se detiene entre URLs, así que las URLs ya renderizadas conservan sus resultados y el resto queda sin iniciar. La llamada es idempotente, así que cancelar un lote que ya terminó simplemente devuelve su estado actual, y el status del lote pasa a canceled.

curl -X DELETE https://api.enconvert.com/v2/perceive/batch/{job_id} \
  -H "X-API-Key: sk_your_private_key"

Caché#

cache_mode controla cómo perceive trata su caché de resultados de 1 hora, indexada por tu proyecto, la URL y las opciones de la solicitud que afectan al render.

cache_mode Comportamiento
enabled (por defecto) Devuelve un resultado en caché cuando una solicitud idéntica se renderizó en la última hora. cache_hit es true, cost_cents es 0.
bypass Omite la caché y renderiza de nuevo.
refresh Renderiza de nuevo y reemplaza la entrada en caché.

Vale la pena aclarar: un acierto de caché igual cuenta como una op contra tu cuota mensual de ops. La cuota mide operaciones, no renders de navegador, así que la caché te ahorra tiempo de render, no ops.


Ejemplos de código#

curl: Solo Markdown#

curl -X POST https://api.enconvert.com/v2/perceive \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/blog/post",
    "outputs": ["markdown"]
  }'

curl: Markdown más datos estructurados#

curl -X POST https://api.enconvert.com/v2/perceive \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/pricing",
    "outputs": ["markdown", "structured"],
    "extract": ["metadata", "structured_data", "tables"]
  }'

curl: Outputs completos más PDF#

curl -X POST https://api.enconvert.com/v2/perceive \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/report",
    "outputs": ["markdown", "html_cleaned", "screenshot_full_page", "pdf"],
    "pdf_options": {"format": "A4", "print_background": true}
  }'

Python#

import requests

response = requests.post(
    "https://api.enconvert.com/v2/perceive",
    headers={"X-API-Key": "sk_your_private_key"},
    json={
        "url": "https://example.com/pricing",
        "outputs": ["markdown", "structured"],
        "extract": ["metadata", "tables"],
    },
)
response.raise_for_status()
data = response.json()

# Download the Markdown artifact from its signed URL
markdown_url = data["outputs"]["markdown"]["url"]
markdown_text = requests.get(markdown_url).text

print(data["structured"])
print(markdown_text)

Node.js#

const res = await fetch("https://api.enconvert.com/v2/perceive", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-API-Key": "sk_your_private_key"
    },
    body: JSON.stringify({
        url: "https://example.com/pricing",
        outputs: ["markdown", "structured"],
        extract: ["metadata", "tables"]
    })
});

const data = await res.json();

// Download the Markdown artifact from its signed URL
const markdownText = await fetch(data.outputs.markdown.url).then(r => r.text());

console.log(data.structured);
console.log(markdownText);

Si llamas a EnConvert desde Claude, Cursor u otro cliente MCP, la misma capacidad se expone como la herramienta perceive_url. Consulta la página del servidor MCP.


Respuestas de error#

Estado Condición
400 Bad Request La URL no es http(s), lleva credenciales embebidas, o resuelve a una dirección privada, loopback, o link-local (protección SSRF).
400 Bad Request auth inválido (falta username/password), cookies (no es un array, supera 50 entradas, faltan campos), o headers (no es un objeto, supera 20 entradas, nombre bloqueado).
401 Unauthorized Falta la clave de API / token JWT, o es inválida.
402 Payment Required Perceive no está en tu plan actual, o tu cuota mensual de ops está agotada.
403 Forbidden /v2/perceive no está en los endpoints permitidos de la clave de API.
403 Forbidden Los lotes no están disponibles en tu plan, o el tamaño del lote supera el límite de tu plan.
403 Forbidden respect_robots=true y el robots.txt del sitio no permite la URL.
404 Not Found operation_id o job_id desconocido, o perteneciente a otro proyecto.
422 Unprocessable Entity Falló la validación de la solicitud (enum inválido en outputs/extract, wait_timeout_ms fuera de rango, viewport fuera de límites, una clave de solicitud desconocida).
422 Unprocessable Entity Se envió proxy_url, geolocation, o action_chain. Los tres están reservados para una versión posterior.
500 Internal Server Error Falló el render. El mensaje incluye el operation_id para citar a soporte.
502 Bad Gateway Todos los motores fueron bloqueados y el origen sirvió un desafío anti-bot sin contenido de página detrás. Reinténtalo más tarde, o envía allow_degraded: true para recibir la página del desafío tal cual.

Las claves de solicitud desconocidas se rechazan con un 422 que nombra el campo, en /v2/perceive, /v2/perceive/batch, /v2/discover y /v2/lookup por igual. Nunca se ignoran silenciosamente. Cada cuerpo 422 incluye un array errors de nivel superior con mensajes legibles por humanos, junto a la lista detail en bruto.

La referencia completa de códigos de estado está en la guía de códigos de error.


Límites#

Límite Valor
Longitud de URL 2,048 caracteres
wait_timeout_ms 0–60,000 ms
Longitud de js_code 20,000 caracteres
Ancho de viewport 320–3,840 px
Alto de viewport 240–2,160 px
Cookies por solicitud 50
Encabezados personalizados por solicitud 20
Extract main_content 50,000 caracteres
URLs de lote por solicitud 1,000 (límite del schema)
Umbral de lote inline 10 URLs (los lotes más grandes se ejecutan de forma asíncrona)
TTL de la caché de resultados 1 hora
Expiración de URL firmada 15 minutos
Ops mensuales (compartidas entre todos los endpoints) 500 / 3.000 / 15.000 / 50.000 según el nivel; consulta precios

Preguntas frecuentes#

¿Cómo convierto una página web a Markdown con una API REST?#

Envía POST /v2/perceive con {"url": "...", "outputs": ["markdown"]}. La página se renderiza en Chrome headless y la respuesta lleva una URL de descarga pre-firmada para el archivo Markdown. Por defecto, only_main_content elimina el chrome del sitio para que recibas el artículo, no la navegación; define "only_main_content": false para la página completa, o añade "direct_download": true para recibir los bytes del Markdown directamente en el cuerpo de la respuesta.

¿Puedo obtener una captura de pantalla y Markdown del mismo render?#

Sí. outputs acepta cualquier combinación, así que ["markdown", "screenshot"] (o screenshot_full_page para todo el alto del scroll) produce ambos a partir de un único render de navegador. Nunca pagas por la misma página dos veces en una llamada.

¿/v2/perceive renderiza páginas JavaScript?#

Sí. Cada solicitud ejecuta un render real en Chrome headless: se descartan los banners de cookies, se hace scroll en la página para disparar el contenido de carga diferida, y puedes controlar la página antes de la captura con wait_for (un selector CSS o una expresión JS), js_code, y block_resources.

¿Por qué dejó de funcionar mi URL de descarga firmada?#

Las URLs firmadas expiran a los 15 minutos (expires_in: 900). Vuelve a obtener la operación con GET /v2/perceive/{operation_id} para conseguir URLs recién firmadas. No se produce ningún re-render y no se consumen ops.

¿Un resultado en caché sigue contando contra mi cuota?#

Sí. Un acierto de caché factura una op, porque la cuota mensual mide operaciones, no renders de navegador. Define cache_mode como bypass para omitir la caché de 1 hora, o refresh para renderizar de nuevo y reemplazar la entrada en caché.