Lookup

POST /v2/lookup runs a web search in one of six categories and hands back a flat, provider-neutral result list. Set perceive_top and it also renders the top-N result URLs in a real browser, so an agent gets the search-engine results page (SERP) and the page content behind each hit in a single round trip. It is EnConvert's answer to Firecrawl /search: one call where you would otherwise hit a search API, parse its results, then fan out a scraper.

Serper backs it today. The request and response speak a neutral search vocabulary — category, country, locale, time_filter — so a future provider swap does not change the contract you code against.

Here is the smallest useful call. Send a query, get the top web results back:

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

The response is a flat result list plus provenance for support correlation:

{
    "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 — 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": []
}

Worth flagging up front. /v2/lookup is a V2 endpoint with its own quota counter, lookup_queries, separate from V1 conversions and from the perceive counter. V1 conversion plans do not include lookup queries — you need a V2-inclusive plan to call it. See the pricing page for the per-tier allowances, and the V2 overview for how the V2 counters relate.


Endpoints

Method Path Purpose
POST /v2/lookup Run one search and, optionally, auto-perceive the top-N result URLs.

/v2/lookup is a single-call endpoint — there is no separate status or retrieval path. When you auto-perceive, each rendered page becomes a first-class perceive operation with its own operation_id, which you can re-fetch later through GET /v2/perceive/{operation_id}.

Content-Type: application/json.


Authentication

Authenticate with a private key in the X-API-Key header for server-to-server calls. This is the path the examples below use.

X-API-Key: sk_live_your_private_key

Public keys with a JWT bearer token also work, using the same flow as every other endpoint — generate a token with your pk_ key, then send it as Authorization: Bearer <token>. The full flow, including domain locking and token refresh, is in the authentication guide.

Each API key carries an allowed-endpoints allowlist. If /v2/lookup is not on the key's list, the request is rejected with 403.


How lookup works

One request runs one provider search, and — only if you ask for it — renders the top results afterward.

  1. Quota gate. Before anything bills, the handler checks the lookup_queries counter for your plan. A disabled plan or an exhausted monthly quota is rejected with 402, so nothing is charged on a rejection.
  2. Search. The query goes to the search provider (Serper today) on the endpoint for your category. Recency, country, locale, location, page size, and autocorrect map onto the provider's parameters.
  3. Normalise. Each provider hit is flattened into a neutral LookupResulttitle, url, snippet, position, and the category-specific extras land in extra so the contract never grows a column per provider quirk.
  4. Charge and audit. The search succeeded, so one lookup_queries is consumed and one ch_lookup_queries audit row is written. The row id comes back as lookup_id for support correlation.
  5. Auto-perceive (optional). If perceive_top > 0, the top-N result URLs are rendered one at a time through the shared headless Chrome singleton — the same pipeline as the perceive endpoint. Each render is a full /v2/perceive operation: its own quota decrement, its own operation row, its own operation_id. Auto-perceive requests Markdown only — no screenshot, PDF, or LLM extraction.

Auto-perceive is best-effort. A single URL that fails, or the perceive quota running out part-way through, degrades to a warning and still returns the search results — the SERP is the primary product here, so a perception problem never sinks the whole call.


Request parameters

Query and category

Parameter Type Default Description
query string The search query. 1–512 characters, trimmed of leading/trailing whitespace. A query that is empty after trimming is rejected with 422. Required.
category string "web" One of web, news, images, scholar, patents, maps.

Targeting and recency

Parameter Type Default Description
country string null Google gl country code, e.g. us, in. Max 8 characters.
locale string null Google hl interface language, e.g. en. Max 16 characters.
time_filter string null Restrict to results from the past period: hour, day, week, month, or year.
location string null Free-text location string, e.g. "Austin, Texas". Max 128 characters.
autocorrect boolean true Whether the provider may autocorrect the query spelling.

Pagination

Parameter Type Default Description
num_results integer 10 Results per page. 1–100.
page integer 1 Page number. 1–10.

Auto-perceive

Parameter Type Default Description
perceive_top integer 0 Auto-perceive the first N result URLs that have a navigable link. 0–10. Each one is a full browser render that runs sequentially through the shared singleton and consumes one perceive_operations from your quota — which is why it is capped at 10. For larger sets, take the url fields and call the perceive batch endpoint. 0 disables auto-perceive.

Categories

Each category hits a different provider endpoint and surfaces a slightly different result shape. The universal fields (title, url, snippet, position) are always typed; category-specific fields land in extra.

category What it searches Notable fields populated
web General web results title, url, snippet, date, position
news News articles adds source, image_url
images Image results image_url, thumbnail_url, source (often no snippet)
scholar Academic results same shape as web; citation counts in extra
patents Patent results same shape as web; patent fields in extra
maps Local places url is the place website; snippet carries the address; rating, coordinates in extra

Worth flagging: for images and maps, url can be null for a given hit when the provider returns no navigable link. Auto-perceive skips any result whose url is null, so a perceive_top of 5 on a SERP with two URL-less hits perceives at most three pages.


Response

The endpoint omits null fields, so a minimal web result carries only the fields that are actually populated.

Field Type Description
lookup_id integer The ch_lookup_queries audit-row id. Quote it to support. null if the audit write failed — the results are still valid.
query string The (trimmed) query you sent.
category string The category searched.
country string Echo of the country you sent, if any.
locale string Echo of the locale you sent, if any.
time_filter string Echo of the time_filter you sent, if any.
total integer Number of results returned.
results LookupResult[] The result list. See below.
perceive_top integer How many results were actually perceived — at most the value you requested, lower if quota ran out or URLs failed.
perceive_operation_ids string[] The per_... operation ids of the perceived results, in order.
answer_box object The provider's answer box, when present.
knowledge_graph object The provider's knowledge graph panel, when present.
credits integer Provider credits consumed by this query.
cost_cents number Monetary cost of the search in cents. A flat 0.06 per query today.
warnings string[] Non-fatal notes: a skipped URL-less result, an auto-perceive failure, the perceive quota running out mid-loop.

LookupResult

Field Type Description
title string Result title.
url string Canonical page link — the thing you would perceive. null for results with no navigable URL.
snippet string Result snippet. For maps, this carries the address.
position integer The result's position on the SERP.
source string Source/publisher, for news and images.
date string Published date, when the provider reports one.
image_url string Image URL, for images and news.
thumbnail_url string Thumbnail URL, for images.
extra object Category-specific fields not in the neutral set: ratings, coordinates, citation counts, and so on.
perceive PerceiveResponse The full inline perceive result for this URL — present only for the top-N when perceive_top > 0 and the render succeeded. Same object shape as the perceive endpoint.

Reading auto-perceive results

When you send perceive_top, walk the results and check for the perceive field — it is present only on the results that were perceived, and only when their render succeeded. The Markdown for each lives behind a pre-signed download URL (a short-lived, signed link to object storage) under perceive.outputs.markdown.url, the same as a direct perceive call.

{
    "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": []
}

Those signed URLs expire after 15 minutes. To download a perceived page later, re-fetch its operation with GET /v2/perceive/{operation_id} using the id from perceive_operation_ids — that re-signs the URLs and does not re-render, so it costs no quota. See the perceive retrieve section for the details.


Code examples

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

curl — recent news, localised

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

curl — search plus auto-perceive the top 3

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

Python

import requests

response = requests.post(
    "https://api.enconvert.com/v2/lookup",
    headers={"X-API-Key": "sk_live_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

const res = await fetch("https://api.enconvert.com/v2/lookup", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-API-Key": "sk_live_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);
}

If you call EnConvert from Claude, Cursor, or another Model Context Protocol (MCP) client, the search capability is exposed as a tool — see the MCP server page.


Plan gating

Lookup queries are metered on their own counter, lookup_queries, separate from V1 conversions and from the perceive counter that the perceive endpoint uses. V1 conversion plans include no lookup quota; you need a V2-inclusive plan.

Two things bill independently on a lookup call:

What you do Counter consumed
Run a search one lookup_queries
Auto-perceive N result URLs one perceive_operations per URL actually rendered

So a call with perceive_top: 3 consumes one lookup_queries and up to three perceive_operations. If your perceive quota runs out mid-loop, the search still returns — auto-perceive simply stops at the boundary and notes it in warnings, rather than failing the whole request.

Per-tier allowances for both counters live on the pricing page; they are configured per plan, not hardcoded in the endpoint. (At the time of writing, the Free plan default carries 25 lookup_queries and 50 perceive_operations per month.)


Error responses

The handler never echoes raw provider text to the client — provider and SSRF detail stays in the server logs, and the client gets a clean, generic message.

Status Condition
401 Unauthorized Missing or invalid API key / JWT token.
402 Payment Required Lookup is not on your current plan, or your monthly lookup_queries quota is exhausted.
403 Forbidden /v2/lookup is not in the API key's allowed endpoints.
422 Unprocessable Entity Request validation failed: empty/over-length query, an unknown category or time_filter, num_results or page out of range, perceive_top over 10.
502 Bad Gateway The search provider returned an error response or a non-retryable transport fault (SearchUpstreamError). Retrying may help.
503 Service Unavailable The search provider is misconfigured server-side (a missing key — our fault, SearchConfigError), or it is temporarily unavailable: the circuit breaker is open, or the provider rate-limited us (SearchUnavailableError). Retry later.
500 Internal Server Error An unexpected failure. The message is generic; quote the time of the call to support.

A failing auto-perceive never raises an error of its own — it lands in warnings and the call still answers 200. The full status-code reference is in the error-codes guide.


Limits

Limit Value
query length 1–512 characters (trimmed)
country length 8 characters
locale length 16 characters
location length 128 characters
num_results 1–100
page 1–10
perceive_top 0–10
Auto-perceive outputs Markdown only
Auto-perceive concurrency Sequential (one render at a time)
Cost per search 0.06 cents flat
Perceived-page signed URL expiry 15 minutes
Monthly lookup_queries Plan-dependent (see pricing)