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.
- 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.
- 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 bajoonly_main_content. Consulta Calidad del Markdown. - 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 unschemay tu plan incluye el nivel LLM, una pasada asistida por LLM completa el schema cuando la pasada heurística se queda corta. - 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
deductionspara conocer el motivo ystatus_codepara 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 atributolanguagedesnudo o un wrapper del resaltador) y se normaliza, de modo que llega```pythonen 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
YesNoyEvaluationDeploymentProduction; 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-ignoreodata-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. |
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
warningsen lugar de gastar de más. En un plan sin el nivel LLM, solo obtienes datosstructuredheurí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=markdowntransmite un artefacto de una operación pasada.outputes obligatorio cuando la operación produjo más de un artefacto. Un artefacto que superó la ventana de retención de tu plan responde410.GET /v2/perceive/batch/{job_id}?direct_download=truetransmite el ZIP del lote. Vale para lotes conoutput_mode: "zip"cuyo archivo ya está listo; de lo contrario responde400.
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é.