---
seo_title: Lookup (Fase 2): API de búsqueda web para agentes | EnConvert
meta_desc: Beta privada, Fase 2: búsqueda web, de noticias, académica o de mapas y, opcionalmente, auto-percepción de los mejores resultados. Neutral respecto al proveedor.
keywords: api de búsqueda web para llm, alternativa a serp api, api de google search para agentes de ia, api de búsqueda con contenido de página, alternativa a serper api, alternativa a firecrawl search, api de búsqueda web para rag, api de búsqueda de noticias en json
---

# API de Búsqueda Web para Agentes LLM

<div class="alert alert-warning">
<strong>Beta privada.</strong> Lookup ya se puede llamar hoy con tu clave de API habitual, en cualquier plan incluido Founding, y descuenta de tu cuota mensual de ops igual que cualquier otra llamada. No está anunciado ni disponible de forma general: las formas de la solicitud y de la respuesta pueden cambiar sin avisar, y no hay ningún compromiso de estabilidad ni de soporte, así que todavía no montes nada crítico encima. La hoja de ruta está en <a href="/es/docs/coming-soon">Próximamente</a>, y cada versión se anuncia en <a href="/es/changelog">el changelog</a>.
</div>

`POST /v2/lookup` es una API de búsqueda web construida para agentes LLM:
ejecuta una búsqueda en una de seis categorías y devuelve una lista de
resultados plana y neutral respecto al proveedor. Define `perceive_top` y
también renderiza las URLs de los N primeros resultados en un navegador
real, de modo que un agente obtiene la página de resultados del motor de
búsqueda (SERP) *y* el contenido de la página detrás de cada resultado en
un único viaje de ida y vuelta. Como alternativa a una API SERP, colapsa
la pila habitual (llamar a una API de búsqueda, analizar sus resultados
y luego lanzar un scraper) en una sola llamada. Será la respuesta de
EnConvert a `/search` de Firecrawl.

Serper es el proveedor de búsqueda que está detrás. La solicitud y la
respuesta hablan un vocabulario de búsqueda neutral (`category`,
`country`, `locale`, `time_filter`), de modo que un futuro cambio de
proveedor no altera el contrato contra el que programas.

Esta es la llamada útil más pequeña. Envía una consulta y obtén de vuelta
los mejores resultados web:

```bash
curl -X POST https://api.enconvert.com/v2/lookup \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "headless chrome pdf rendering"
  }'
```

La respuesta es una lista de resultados plana más procedencia para la
correlación con soporte:

```json
{
    "lookup_id": 81423,
    "query": "headless chrome pdf rendering",
    "category": "web",
    "total": 10,
    "results": [
        {
            "title": "Generate PDFs with headless Chrome",
            "url": "https://example.com/guide/chrome-pdf",
            "snippet": "Render a page and print it to PDF...",
            "position": 1
        },
        {
            "title": "Print to PDF with the Chrome DevTools Protocol",
            "url": "https://example.dev/cdp/print-to-pdf",
            "snippet": "Page.printToPDF returns base64 PDF data...",
            "position": 2
        }
    ],
    "perceive_top": 0,
    "perceive_operation_ids": [],
    "credits": 1,
    "cost_cents": 0.06,
    "warnings": []
}
```

---

## Endpoints

| Método | Ruta | Propósito |
|--------|------|---------|
| `POST` | `/v2/lookup` | Ejecuta una búsqueda y, opcionalmente, auto-percibe las URLs de los N primeros resultados. |

`/v2/lookup` es un endpoint de llamada única, sin una ruta separada
de estado o de recuperación. Cuando auto-percibes, cada página renderizada
se convierte en una operación de [percepción](/es/docs/endpoints/perceive.md) de
primer nivel con su propio `operation_id`, que puedes volver a obtener más
tarde mediante `GET /v2/perceive/{operation_id}`.

**Content-Type:** `application/json`.

---

## Autenticación

Autentícate con una clave privada en el encabezado `X-API-Key` para
llamadas de servidor a servidor. Este es el camino que usan los ejemplos
siguientes.

```http
X-API-Key: sk_your_private_key
```

Las claves públicas con un token bearer JWT 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, incluyendo el bloqueo de dominio y la renovación del token, está
en [la guía de autenticación](/es/docs/authentication.md).

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

---

## Cómo funciona la búsqueda

Una solicitud ejecuta una búsqueda del proveedor y, solo si lo pides,
renderiza después los mejores resultados.

1. **Verificación de cuota.** Antes de que se facture nada, el handler
   comprueba la cuota mensual unificada de ops de tu plan. Un plan
   deshabilitado o una cuota agotada se rechaza con `402`, de modo
   que no se cobra nada ante un rechazo.
2. **Búsqueda.** La consulta va al proveedor de búsqueda (Serper) en
   el endpoint correspondiente a tu `category`. La actualidad, el país, la
   configuración regional, la ubicación, el tamaño de página y la
   autocorrección se mapean a los parámetros del proveedor.
3. **Normalización.** Cada resultado del proveedor se aplana en un
   `LookupResult` neutral que lleva `title`, `url`, `snippet` y
   `position`. Los extras específicos de la categoría van a parar a
   `extra`, de modo que el contrato nunca crece con una columna por cada
   particularidad del proveedor.
4. **Cobro y auditoría.** La búsqueda tuvo éxito, así que se factura
   una op y se escribe una fila de auditoría
   `ch_lookup_queries`. El id de la fila vuelve como `lookup_id` para la
   correlación con soporte.
5. **Auto-percepción (opcional).** Si `perceive_top > 0`, las URLs de los
   N primeros resultados se renderizan una a una a través del singleton
   compartido de Chrome headless, el mismo pipeline que [el endpoint de
   percepción](/es/docs/endpoints/perceive.md). Cada renderizado es una operación
   `/v2/perceive` completa: su propia op facturada contra la cuota compartida, su propia fila
   de operación, su propio `operation_id`. De forma predeterminada, la
   auto-percepción solo solicita Markdown, sin captura de pantalla, PDF ni
   extracción con LLM; envía un objeto `enrich` para ampliar las salidas,
   ejecutarlas en paralelo, o añadir extracción por esquema y una
   respuesta sintetizada.

La auto-percepción es de mejor esfuerzo. Una única URL que falle, o que la
cuota de ops se agote a mitad de camino, degrada a una advertencia y
aun así devuelve los resultados de la búsqueda. El SERP es el producto
principal aquí, así que un problema de percepción nunca hunde toda la
llamada.

---

## Parámetros de la solicitud

### Consulta y categoría

| Parámetro | Tipo | Predeterminado | Descripción |
|-----------|------|---------|-------------|
| `query` | `string` | ninguno | La consulta de búsqueda. 1–512 caracteres, recortada de espacios en blanco al inicio/final. Una consulta que quede vacía tras el recorte se rechaza con `422`. Obligatorio. |
| `category` | `string` | `"web"` | Uno de `web`, `news`, `images`, `scholar`, `patents`, `maps`. |

### Segmentación y actualidad

| Parámetro | Tipo | Predeterminado | Descripción |
|-----------|------|---------|-------------|
| `country` | `string` | `null` | Código de país `gl` de Google, p. ej. `us`, `in`. Máximo 8 caracteres. |
| `locale` | `string` | `null` | Idioma de interfaz `hl` de Google, p. ej. `en`. Máximo 16 caracteres. |
| `time_filter` | `string` | `null` | Restringe a resultados del período pasado: `hour`, `day`, `week`, `month`, o `year`. |
| `location` | `string` | `null` | Cadena de ubicación en texto libre, p. ej. `"Austin, Texas"`. Máximo 128 caracteres. |
| `autocorrect` | `boolean` | `true` | Si el proveedor puede autocorregir la ortografía de la consulta. |

### Paginación

| Parámetro | Tipo | Predeterminado | Descripción |
|-----------|------|---------|-------------|
| `num_results` | `integer` | `10` | Resultados por página. 1–100. |
| `page` | `integer` | `1` | Número de página. 1–10. |

### Auto-percepción

| Parámetro | Tipo | Predeterminado | Descripción |
|-----------|------|---------|-------------|
| `perceive_top` | `integer` | `0` | Auto-percibe las URLs de los primeros N resultados que tengan un enlace navegable. 0–10. Cada una es un renderizado completo del navegador que factura una op de tu cuota mensual, por eso está limitado a 10. Para conjuntos más grandes, toma los campos `url` y llama a [el endpoint de percepción por lotes](/es/docs/endpoints/perceive.md#batch-perception). `0` deshabilita la auto-percepción. |

### Enriquecimiento (`enrich`)

Un objeto `enrich` opcional ajusta cómo se leen los N primeros resultados
(`perceive_top`), y puede sintetizar una única respuesta fundamentada a
partir de ellos. Cuando se omite `enrich`, `perceive_top` conserva su
comportamiento predeterminado (solo Markdown, un resultado a la vez).

| Parámetro | Tipo | Predeterminado | Descripción |
|-----------|------|---------|-------------|
| `enrich.outputs` | `string[]` | `["markdown"]` | Qué outputs de perceive producir por cada resultado enriquecido, p. ej. `markdown`, `html_cleaned`, `links`, `screenshot`, `structured`. Consulta [los outputs de perceive](/es/docs/endpoints/perceive.md#outputs). |
| `enrich.concurrency` | `integer` | `3` | Cuántas URLs de resultados enriquecer en paralelo. 1–5. Los renderizados de Markdown/HTML se paralelizan; los de captura de pantalla/PDF se serializan en el navegador compartido. |
| `enrich.schema` | `object` | `null` | Ejecuta la extracción estructurada guiada por schema contra cada resultado enriquecido. Los datos extraídos aparecen bajo el `perceive.structured` de cada resultado. Un objeto JSON-Schema o un mapa plano `{field: description}`. |
| `enrich.synthesize_answer` | `boolean` | `false` | Sintetiza una única respuesta citada y fundamentada a la consulta a partir de los resultados enriquecidos, devuelta como `answer` (con `answer_sources`). Usa el contenido percibido de la página cuando está disponible; en caso contrario, los fragmentos de los resultados. |
| `enrich.answer_prompt` | `string` | `null` | Una pregunta a responder en lugar de la consulta original. Solo se usa cuando `synthesize_answer` es `true`. Máximo 1,000 caracteres. |

`enrich.schema` y `enrich.synthesize_answer` usan el nivel de extracción
LLM. Si ese paso de extracción no se puede ejecutar, se degradan a una
advertencia y el resto de la respuesta no se ve afectado.

```json
{
  "query": "best open-source vector databases",
  "perceive_top": 3,
  "enrich": {
    "outputs": ["markdown"],
    "concurrency": 3,
    "synthesize_answer": true
  }
}
```

---

## Categorías

Cada categoría llama a un endpoint distinto del proveedor y expone una
forma de resultado ligeramente diferente. Los campos universales
(`title`, `url`, `snippet`, `position`) siempre están tipados; los campos
específicos de la categoría van a parar a `extra`.

| `category` | Qué busca | Campos destacados poblados |
|------------|------------------|--------------------------|
| `web` | Resultados web generales | `title`, `url`, `snippet`, `date`, `position` |
| `news` | Artículos de noticias | añade `source`, `image_url` |
| `images` | Resultados de imágenes | `image_url`, `thumbnail_url`, `source` (a menudo sin `snippet`) |
| `scholar` | Resultados académicos | misma forma que `web`; conteos de citas en `extra` |
| `patents` | Resultados de patentes | misma forma que `web`; campos de patentes en `extra` |
| `maps` | Lugares locales | `url` es el sitio web del lugar; `snippet` lleva la dirección; calificación, coordenadas en `extra` |

Vale la pena señalarlo: para `images` y `maps`, `url` puede ser `null`
para un resultado dado cuando el proveedor no devuelve ningún enlace
navegable. La auto-percepción omite cualquier resultado cuyo `url` sea
`null`, así que un `perceive_top` de 5 en un SERP con dos resultados sin
URL percibe como máximo tres páginas.

---

## Respuesta

El endpoint omite los campos `null`, de modo que un resultado `web`
mínimo lleva solo los campos que están realmente poblados.

| Campo | Tipo | Descripción |
|-------|------|-------------|
| `lookup_id` | `integer` | El id de la fila de auditoría `ch_lookup_queries`. Cítalo a soporte. `null` si falló la escritura de auditoría, aunque los resultados siguen siendo válidos. |
| `query` | `string` | La consulta (recortada) que enviaste. |
| `category` | `string` | La categoría buscada. |
| `country` | `string` | Eco del `country` que enviaste, si lo hubo. |
| `locale` | `string` | Eco del `locale` que enviaste, si lo hubo. |
| `time_filter` | `string` | Eco del `time_filter` que enviaste, si lo hubo. |
| `total` | `integer` | Número de resultados devueltos. |
| `results` | `LookupResult[]` | La lista de resultados. Ver más abajo. |
| `perceive_top` | `integer` | Cuántos resultados fueron *realmente* percibidos: como máximo el valor que solicitaste, y menor si se agotó la cuota de ops o fallaron URLs. |
| `perceive_operation_ids` | `string[]` | Los ids de operación `per_...` de los resultados percibidos, en orden. |
| `answer_box` | `object` | El cuadro de respuesta del proveedor, cuando está presente. |
| `knowledge_graph` | `object` | El panel de grafo de conocimiento del proveedor, cuando está presente. |
| `answer` | `string` | La respuesta citada y sintetizada a partir de los resultados enriquecidos. Presente solo cuando `enrich.synthesize_answer` es `true` y tuvo éxito. |
| `answer_sources` | `string[]` | Las URLs usadas como fundamento para `answer`, en orden de citación. |
| `credits` | `integer` | Créditos del proveedor consumidos por esta consulta. |
| `cost_cents` | `number` | Costo monetario de la búsqueda en centavos. Un valor fijo de `0.06` por consulta hoy. |
| `warnings` | `string[]` | Notas no fatales: un resultado sin URL omitido, un fallo de auto-percepción, la cuota de ops agotándose a mitad de bucle. |

### LookupResult

| Campo | Tipo | Descripción |
|-------|------|-------------|
| `title` | `string` | Título del resultado. |
| `url` | `string` | Enlace canónico de la página, lo que percibirías. `null` para resultados sin URL navegable. |
| `snippet` | `string` | Fragmento del resultado. Para `maps`, lleva la dirección. |
| `position` | `integer` | La posición del resultado en el SERP. |
| `source` | `string` | Fuente/editor, para `news` e `images`. |
| `date` | `string` | Fecha de publicación, cuando el proveedor la reporta. |
| `image_url` | `string` | URL de la imagen, para `images` y `news`. |
| `thumbnail_url` | `string` | URL de la miniatura, para `images`. |
| `extra` | `object` | Campos específicos de la categoría que no están en el conjunto neutral: calificaciones, coordenadas, conteos de citas, etc. |
| `perceive` | `PerceiveResponse` | El resultado de percepción completo en línea para esta URL, presente solo para los N primeros cuando `perceive_top > 0` y el renderizado tuvo éxito. Misma forma de objeto que [el endpoint de percepción](/es/docs/endpoints/perceive.md#response). |

---

## Cómo leer los resultados de auto-percepción

Cuando envías `perceive_top`, recorre los resultados y comprueba el campo
`perceive`. Está presente solo en los resultados que fueron percibidos, y
solo cuando su renderizado tuvo éxito. El Markdown de cada uno vive
detrás de una URL de descarga pre-firmada (un enlace firmado y de corta
duración al almacenamiento de objetos) en `perceive.outputs.markdown.url`,
igual que en una llamada directa a perceive.

```json
{
    "lookup_id": 81910,
    "query": "react server components data fetching",
    "category": "web",
    "total": 10,
    "results": [
        {
            "title": "Data fetching with RSC",
            "url": "https://example.com/rsc/data",
            "snippet": "Fetch on the server, stream to the client...",
            "position": 1,
            "perceive": {
                "operation_id": "per_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7",
                "status": "completed",
                "url": "https://example.com/rsc/data",
                "outputs": {
                    "markdown": {
                        "url": "https://spaces.example.com/...signed...",
                        "size_bytes": 7421,
                        "content_type": "text/markdown; charset=utf-8",
                        "expires_in": 900
                    }
                },
                "cost_cents": 0.0,
                "duration_ms": 5840
            }
        }
    ],
    "perceive_top": 1,
    "perceive_operation_ids": [
        "per_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7"
    ],
    "credits": 1,
    "cost_cents": 0.06,
    "warnings": []
}
```

Esas URLs firmadas expiran después de 15 minutos. Para descargar una
página percibida más tarde, vuelve a obtener su operación con
`GET /v2/perceive/{operation_id}` usando el id de
`perceive_operation_ids`. Eso vuelve a firmar las URLs y no vuelve a
renderizar, así que no cuesta ops. Consulta [la sección de recuperación
de perceive](/es/docs/endpoints/perceive.md#retrieve-an-operation) para más
detalles.

---

## Ejemplos de código

### curl: búsqueda web

```bash
curl -X POST https://api.enconvert.com/v2/lookup \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "open source vector database",
    "num_results": 20
  }'
```

### curl: noticias recientes, localizadas

```bash
curl -X POST https://api.enconvert.com/v2/lookup \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "rbi monetary policy",
    "category": "news",
    "country": "in",
    "locale": "en",
    "time_filter": "week"
  }'
```

### curl: búsqueda más auto-percepción de los 3 primeros

```bash
curl -X POST https://api.enconvert.com/v2/lookup \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "langchain retrieval augmented generation",
    "perceive_top": 3
  }'
```

### Python

```python
import requests

response = requests.post(
    "https://api.enconvert.com/v2/lookup",
    headers={"X-API-Key": "sk_your_private_key"},
    json={
        "query": "langchain retrieval augmented generation",
        "perceive_top": 3,
    },
)
response.raise_for_status()
data = response.json()

# Pull the Markdown of every result that was perceived
for result in data["results"]:
    perceived = result.get("perceive")
    if not perceived:
        continue
    markdown_url = perceived["outputs"]["markdown"]["url"]
    page_text = requests.get(markdown_url).text
    print(result["url"], len(page_text), "chars")

for note in data["warnings"]:
    print("warning:", note)
```

### Node.js

```javascript
const res = await fetch("https://api.enconvert.com/v2/lookup", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-API-Key": "sk_your_private_key"
    },
    body: JSON.stringify({
        query: "langchain retrieval augmented generation",
        perceive_top: 3
    })
});

const data = await res.json();

// Pull the Markdown of every result that was perceived
for (const result of data.results) {
    if (!result.perceive) continue;
    const markdownUrl = result.perceive.outputs.markdown.url;
    const pageText = await fetch(markdownUrl).then(r => r.text());
    console.log(result.url, pageText.length, "chars");
}

for (const note of data.warnings) {
    console.log("warning:", note);
}
```

Si llamas a EnConvert desde Claude, Cursor u otro cliente de Model Context
Protocol (MCP), la capacidad de búsqueda también se expondrá allí como una
herramienta. Consulta [la página del servidor MCP](/es/mcp.md).

---

## Respuestas de error

El handler nunca devuelve texto crudo del proveedor al cliente. El
detalle del proveedor y de SSRF se queda en los logs del servidor, y el
cliente recibe un mensaje limpio y genérico.

| Estado | Condición |
|--------|-----------|
| `401 Unauthorized` | Falta la clave de API / token JWT, o es inválida. |
| `402 Payment Required` | Lookup no está en tu plan actual, o tu cuota mensual de ops está agotada. |
| `403 Forbidden` | `/v2/lookup` no está en los endpoints permitidos de la clave de API. |
| `422 Unprocessable Entity` | Falló la validación de la solicitud: `query` vacío o demasiado largo, un `category` o `time_filter` desconocido, `num_results` o `page` fuera de rango, `perceive_top` superior a 10. |
| `502 Bad Gateway` | El proveedor de búsqueda devolvió una respuesta de error o un fallo de transporte no reintentable (`SearchUpstreamError`). Reintentar puede ayudar. |
| `503 Service Unavailable` | El proveedor de búsqueda está mal configurado del lado del servidor (falta una clave de nuestro lado, `SearchConfigError`), o está temporalmente no disponible: el circuit breaker está abierto, o el proveedor nos limitó la tasa (`SearchUnavailableError`). Reintenta más tarde. |
| `500 Internal Server Error` | Un fallo inesperado. El mensaje es genérico; cita la hora de la llamada a soporte. |

Una auto-percepción que falla nunca genera un error propio. Va a parar a
`warnings` y la llamada igual responde `200`. La referencia completa de
códigos de estado está en [la guía de códigos de error](/es/docs/reference/errors.md).

---

## Límites

| Límite | Valor |
|-------|-------|
| Longitud de `query` | 1–512 caracteres (recortado) |
| Longitud de `country` | 8 caracteres |
| Longitud de `locale` | 16 caracteres |
| Longitud de `location` | 128 caracteres |
| `num_results` | 1–100 |
| `page` | 1–10 |
| `perceive_top` | 0–10 |
| Ops por llamada | 1 por la consulta, más 1 por cada resultado auto-percibido |
| Salidas de auto-percepción | Markdown de forma predeterminada; se amplía con `enrich.outputs` |
| Concurrencia de auto-percepción | Secuencial de forma predeterminada; 1–5 con `enrich.concurrency` |
| Costo por búsqueda | 0.06 centavos fijos |
| Expiración de URL firmada de página percibida | 15 minutos |

---

## Preguntas frecuentes

### ¿Cómo ejecuto una búsqueda web y obtengo el contenido de la página en una sola llamada a la API REST?

Envía `POST /v2/lookup` con un `query` y define `perceive_top` (0–10).
Las URLs de los N primeros resultados se renderizan en un navegador real,
y cada resultado percibido lleva un objeto `perceive` en línea cuyo
Markdown está detrás de una URL pre-firmada en
`perceive.outputs.markdown.url`.

### ¿Es /v2/lookup una alternativa a una API SERP que puedo adoptar sin atarme a un proveedor?

Sí. Serper es el proveedor de búsqueda que está detrás, pero la solicitud
y la respuesta hablan un vocabulario de búsqueda neutral (`category`,
`country`, `locale`, `time_filter`), de modo que un futuro cambio de
proveedor no altera el contrato contra el que programas.

### ¿Qué categorías de búsqueda admite la API de lookup?

Seis: `web` (la predeterminada), `news`, `images`, `scholar`, `patents` y
`maps`. Los campos universales (`title`, `url`, `snippet`, `position`)
siempre están tipados, y los extras específicos de la categoría van a
parar a `extra`.

### ¿Por qué la búsqueda percibió menos páginas que mi valor de perceive_top?

La auto-percepción omite los resultados cuyo `url` es `null`, se detiene
si la cuota mensual de ops se agota a mitad de bucle, y degrada
un renderizado fallido a una advertencia. El `perceive_top` de la
respuesta informa cuántas páginas fueron realmente percibidas, y
`warnings` explica los vacíos.
