Perceive
POST /v2/perceive renderiza una URL una sola vez en un navegador real y
devuelve todas las salidas que solicites a partir de ese único renderizado:
Markdown, HTML limpio o crudo, 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). Reemplaza un conjunto de llamadas separadas —
url-to-markdown, url-to-screenshot, url-to-pdf, además de tu propio scraping —
con una sola solicitud.
Aquí tienes la llamada útil más pequeña. Envía una URL y recibe Markdown limpio y los metadatos estructurados de la página:
curl -X POST https://api.enconvert.com/v2/perceive \
-H "X-API-Key: sk_live_your_private_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/pricing",
"outputs": ["markdown", "structured"]
}'
La respuesta incluye una URL de descarga prefirmada para el archivo Markdown y el bloque estructurado en línea:
{
"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": "Pro", "offers": {"price": "99"}}
]
},
"extraction_tier": "heuristic",
"tokens": {"input": 0, "output": 0},
"cost_cents": 0.0,
"duration_ms": 6230,
"warnings": []
}
Conviene señalarlo de entrada.
/v2/perceivees un endpoint V2 con su propio contador de cuota. Los planes de conversión V1 no incluyen ninguna operación perceive de V2 — necesitas un plan que incluya V2 para llamarlo. Consulta la página de precios para ver las asignaciones por nivel.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/v2/perceive |
Percibe una sola URL y devuelve las salidas que solicitaste. |
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. |
Content-Type: application/json en cada POST.
Authentication
Autentícate con una clave privada en el encabezado X-API-Key para llamadas
servidor a servidor. Esta es la vía que usan los ejemplos de abajo.
X-API-Key: sk_live_your_private_key
Las claves públicas con un token bearer JWT también funcionan, usando el mismo
flujo que todos los demás endpoints — genera un token con tu clave pk_ y luego
envíalo como Authorization: Bearer <token>. El flujo completo, incluyendo el
bloqueo de dominio y la renovación del token, está en la guía de
autenticación.
Cada clave API lleva una lista blanca de endpoints permitidos. Si /v2/perceive
no está en la lista de la clave, la solicitud se rechaza con 403.
How perceive works
Una solicitud desencadena un renderizado del navegador a través de un singleton compartido de Chrome headless, y luego materializa cada salida a partir de ese renderizado. Nunca pagas dos veces por la misma página en una sola llamada.
- Renderizar. La página se carga en Chrome headless. Se descartan los banners de cookies, se desplaza la página para activar el contenido de carga diferida, se gestionan los encabezados fijos y se da tiempo a que las imágenes carguen — el mismo pipeline de captura que impulsa el endpoint url-to-pdf.
- Materializar. A partir del DOM renderizado, perceive construye lo que
hayas listado en
outputs: Markdown, HTML limpio/crudo, enlaces, imágenes, una captura de pantalla, un PDF. - Extraer. Si solicitaste la salida
structured, perceive ejecuta una pasada heurística para los metadatos de la página, JSON-LD, encabezados y tablas. Si además envías unschemay tu plan incluye el nivel LLM, un modelo Anthropic Claude Haiku rellena el esquema cuando la pasada heurística se queda corta. - Puntuar. Una puntuación de calidad del renderizado (0.0–1.0) marca las páginas que parecen bloqueadas por protección anti-bot u ocultas tras un muro de inicio de sesión, para que puedas distinguir un renderizado real de una página de desafío.
Las salidas binarias 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
prefirmadas que expiran tras 15 minutos. El bloque structured se devuelve en
línea en el JSON. Vuelve a obtener cualquier operación con GET
/v2/perceive/{operation_id} para conseguir un nuevo conjunto de URLs firmadas.
Request parameters
Core
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string |
— | La página a percibir. Debe empezar por http:// o https://. Máx. 2.048 caracteres. Obligatorio. |
outputs |
string[] |
["markdown", "structured"] |
Qué salidas producir. Consulta Outputs. |
extract |
string[] |
[] |
Qué campos estructurados extraer cuando structured está en outputs. Consulta Structured extraction. |
schema |
object |
null |
Un esquema JSON que describe los campos que quieres extraer. Activa el nivel de extracción LLM en los planes que lo incluyen. |
cache_mode |
string |
"enabled" |
enabled, bypass o refresh. Consulta Caching. |
Outputs
outputs acepta cualquier combinación de estos nombres:
| Output | Returned as | What you get |
|---|---|---|
markdown |
URL firmada | Markdown limpio del contenido principal, con enlaces e imágenes resueltos a URLs absolutas. |
markdown_fit |
URL firmada | Markdown "Fit" — una variante podada y más densa, ajustada para las ventanas de contexto de LLM. |
html_cleaned |
URL firmada | El HTML renderizado con los scripts, estilos y código repetitivo eliminados. |
html_raw |
URL firmada | El HTML renderizado completo, exactamente como lo produjo el navegador. |
screenshot |
URL firmada | Un PNG del viewport al tamaño de viewport solicitado (o predeterminado). |
screenshot_full_page |
URL firmada | Un PNG de página completa que captura toda la altura de desplazamiento. |
pdf |
URL firmada | Un PDF de la página. Acepta toda la superficie de pdf_options (ver más abajo). |
links |
URL firmada | Un array JSON de cada enlace encontrado, con URLs absolutas y el texto del ancla. |
images |
URL firmada | Un array JSON de cada imagen, con src absoluto y texto alt. |
structured |
JSON en línea | Datos estructurados extraídos de la página (el campo de respuesta structured). |
Rendering and waiting
| Parameter | Type | Default | Description |
|---|---|---|---|
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) salvo que se establezca viewport explícitamente. |
wait_for |
string |
null |
Espera tras la navegación a un selector CSS (".price" o "css:.price") o a una expresión JS ("js:window.dataReady === true"). |
wait_timeout_ms |
integer |
30000 |
Cuánto tiempo puede esperar wait_for, en milisegundos. 0–60.000. Un timeout se degrada a una advertencia; la página se captura tal cual. |
js_code |
string |
null |
JavaScript para ejecutar en la página tras la navegación. Máx. 20.000 caracteres. Un error se convierte en advertencia, no en fallo. |
block_resources |
string[] |
[] |
Tipos de recursos a abortar antes de que carguen. Cualquiera de image, media, font, stylesheet, script, xhr, fetch, websocket, manifest, other. Útil para renderizados 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 la salida pdf. El mismo objeto que url-to-pdf. Sin pdf_options, perceive produce una única página continua, idéntica byte a byte a url-to-pdf de V1. |
Authenticated and custom requests
| Parameter | Type | Default | Description |
|---|---|---|---|
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áx. 50. Cada una necesita name, value y domain o url. |
headers |
object |
null |
Encabezados de solicitud personalizados. Máx. 20. Nombres bloqueados: host, content-length, transfer-encoding, connection, upgrade, te, trailer. |
proxy_url (Business+),
geolocation y action_chain son aceptados por el
esquema de la solicitud pero hoy devuelven 422. Llegarán en una
versión posterior; enviarlos ahora te indica exactamente qué control no está
listo en lugar de ignorarlo en silencio.
Structured extraction
Cuando structured está en outputs, la lista extract controla qué campos
extrae perceive. No pidas nada y por defecto será metadata y structured_data.
extract value |
Field in structured |
Status |
|---|---|---|
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 — devuelve una advertencia, se omite |
contacts |
— | Aún no — devuelve una advertencia, se omite |
technologies |
— | Aún no — devuelve una advertencia, se omite |
Para ser claros: prices, contacts y technologies son nombres reservados.
Solicita uno hoy y no da error — aterriza en el array warnings y se descarta
de structured.
Schema-driven extraction
Envía un schema para extraer campos específicos a 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 LLM (Anthropic Claude Haiku) rellena el esquema, y solo
se dispara cuando se cumplen todas estas condiciones: enviaste un schema,
tu plan incluye el nivel LLM (Starter en adelante), la página no se puntuó como
bloqueada y la pasada heurística dejó vacíos los campos del esquema. Cuando se
ejecuta, extraction_tier es "llm", y tokens y cost_cents informan de lo
que costó esa extracción; en caso contrario extraction_tier es "heuristic"
y ambos son cero.
Nota. La extracción por esquema tiene un tope estricto para proteger tu factura: una sola extracción está limitada por solicitud, y el gasto diario por proyecto está limitado por el plan. Si se alcanza un tope, 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.
Response
Tanto POST /v2/perceive como GET /v2/perceive/{operation_id} devuelven el
mismo objeto.
| Field | Type | Description |
|---|---|---|
operation_id |
string |
ID opaco (per_...). Úsalo con el endpoint GET y cítalo al soporte. |
status |
string |
queued, processing, completed o failed. |
url |
string |
La URL que enviaste. |
url_final |
string |
La URL tras las redirecciones. |
content_hash |
string |
SHA-256 de la página renderizada. Impulsa la caché de 1 hora. |
render_quality |
number |
0.0–1.0. Las puntuaciones bajas marcan desafíos anti-bot o muros de inicio de sesión. |
cache_hit |
boolean |
true cuando el resultado provino de la caché en lugar de un renderizado nuevo. |
outputs |
object |
Mapa de nombre de salida a {url, object_key, size_bytes, content_type, expires_in}. Las URLs firmadas expiran en 900 segundos. |
structured |
object |
Datos estructurados en línea, presentes cuando se solicitó structured. |
extraction_tier |
string |
heuristic, css o llm. |
tokens |
object |
{input, output} tokens LLM utilizados. Cero salvo que se ejecutara el nivel LLM. |
cost_cents |
number |
Coste LLM en céntimos para esta operación. Cero salvo que se ejecutara el nivel LLM. |
duration_ms |
integer |
Tiempo de renderizado de extremo a extremo. |
error |
string |
Establecido solo cuando status es failed. |
warnings |
string[] |
Notas no fatales: un timeout de wait_for, un extract omitido, una marca de página bloqueada. |
Retrieve an operation
Las URLs firmadas expiran tras 15 minutos. Para descargar una salida más tarde, vuelve a obtener la operación — perceive vuelve a firmar cada URL a partir de las claves de objeto almacenadas. No ocurre ningún re-renderizado, así que esto no consume cuota.
curl https://api.enconvert.com/v2/perceive/per_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7 \
-H "X-API-Key: sk_live_your_private_key"
Un ID de operación desconocido, o uno que pertenece a un proyecto distinto,
devuelve 404 — la existencia nunca se filtra entre proyectos.
Batch perception
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 produce su propia fila de operación.
curl -X POST https://api.enconvert.com/v2/perceive/batch \
-H "X-API-Key: sk_live_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 en línea y responden 200 con cada
resultado poblado. Los lotes más grandes responden 202 con un job_id; las
URLs se drenan de una en una y consultas los resultados:
curl https://api.enconvert.com/v2/perceive/batch/{job_id} \
-H "X-API-Key: sk_live_your_private_key"
La respuesta del lote informa del progreso agregado y lleva un resultado perceive completo por 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 o partial (algunas
URLs tuvieron éxito, otras fallaron). Establece output_mode en zip para
agrupar todos los artefactos en un único ZIP, devuelto bajo el campo zip una
vez que el lote termina. El acceso a lotes y el tamaño máximo de lote están
limitados por plan; consulta la página de precios.
Caching
cache_mode controla cómo perceive trata su caché de resultados de 1 hora,
indexada por tu proyecto, la URL y las opciones de solicitud que afectan al
renderizado.
cache_mode |
Behaviour |
|---|---|
enabled (default) |
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é. |
Conviene señalarlo: un acierto de caché sigue contando como una operación perceive contra tu cuota mensual. La cuota mide operaciones, no renderizados del navegador — la caché te ahorra tiempo de renderizado, no cuota.
Code examples
curl — Markdown only
curl -X POST https://api.enconvert.com/v2/perceive \
-H "X-API-Key: sk_live_your_private_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/blog/post",
"outputs": ["markdown"]
}'
curl — Markdown plus structured data
curl -X POST https://api.enconvert.com/v2/perceive \
-H "X-API-Key: sk_live_your_private_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/pricing",
"outputs": ["markdown", "structured"],
"extract": ["metadata", "structured_data", "tables"]
}'
curl — Full outputs plus PDF
curl -X POST https://api.enconvert.com/v2/perceive \
-H "X-API-Key: sk_live_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_live_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_live_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.
Plan gating
Las operaciones perceive de V2 son un contador separado de las conversiones de V1. Los planes V1 no incluyen cuota de perceive; necesitas un plan que incluya V2.
| Plan | Perceive operations / month | Schema extraction (LLM tier) |
|---|---|---|
| Free | 50 | No incluido |
| Starter | 850 | Claude Haiku |
| Pro | 8,500 | Claude Haiku |
| Business | 42,500 | Claude Haiku |
| Enterprise | Ilimitado | Incluido |
El acceso a lotes y los límites de tamaño por lote, el soporte de
basic-auth/cookie/header y proxy_url (Business+, cuando se lance) están
limitados por plan. Las cifras actuales están en la página de precios.
Error responses
| Status | Condition |
|---|---|
400 Bad Request |
La URL no es http(s), lleva credenciales incrustadas, o se resuelve a una dirección privada, de loopback o de enlace local (protección SSRF). |
400 Bad Request |
auth inválido (falta username/password), cookies (no es un array, más de 50 entradas, faltan campos) o headers (no es un objeto, más de 20 entradas, nombre bloqueado). |
401 Unauthorized |
Clave API / token JWT ausente o inválido. |
402 Payment Required |
Perceive no está en tu plan actual, o tu cuota mensual de perceive se agotó. |
403 Forbidden |
/v2/perceive no está en los endpoints permitidos de la clave API. |
403 Forbidden |
El lote no está disponible 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 uno propiedad de otro proyecto. |
422 Unprocessable Entity |
Falló la validación de la solicitud (enum incorrecto en outputs/extract, wait_timeout_ms fuera de rango, viewport fuera de los límites). |
422 Unprocessable Entity |
Se envió proxy_url, geolocation o action_chain — reservados para una versión posterior. |
500 Internal Server Error |
Falló el renderizado. El mensaje incluye el operation_id para citar al soporte. |
La referencia completa de códigos de estado está en la guía de códigos de error.
Limits
| Limit | Value |
|---|---|
| Longitud de URL | 2.048 caracteres |
wait_timeout_ms |
0–60.000 ms |
Longitud de js_code |
20.000 caracteres |
| Ancho del viewport | 320–3.840 px |
| Alto del viewport | 240–2.160 px |
| Cookies por solicitud | 50 |
| Encabezados personalizados por solicitud | 20 |
Extract de main_content |
50.000 caracteres |
| URLs por solicitud de lote | 1.000 (tope del esquema; el límite de lote de tu plan es menor) |
| Umbral de lote en línea | 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 |
| Operaciones perceive mensuales | Dependiente del plan (Free: 50) |