# Web research agent in 30 lines

Search Google, read the top two pages, and get a cited answer. Your agent pays each call in USDC on Base with x402: no API keys, no account, no subscription.

Each run makes **4 paid calls**, priced per request:

| Step | Route | Price |
| --- | --- | --- |
| 1. Google results (Serper) | `POST https://search.gedx402.com/v1/serper/search` | flat, see [hero](https://gedx402.com/heroes/serper-search) |
| 2–3. Page text, one URL per call | `POST https://search.gedx402.com/v1/exa/contents-lite` | flat per page |
| 4. Cited answer | `POST https://chat.gedx402.com/v1/chat/completions` | priced from the body (input size + `max_tokens`) |

Live per-call prices are in [llms.txt](https://gedx402.com/llms.txt) and [GET /v1/models](https://gedx402.com/v1/models?bundle=competitive). Every route also quotes for free: an unpaid POST returns `402` with a `payment-required` header holding the exact amount for **that** body. `@x402/fetch` pays that amount and retries.

Prefer Exa neural search? Swap step 1 for `POST https://search.gedx402.com/v1/exa/search` with `{ query, num_results }` and read `results[].url` ([hero](https://gedx402.com/heroes/exa-search)). It's priced the same as the Google route.

## Run it

```bash
npm i @x402/fetch @x402/core @x402/evm viem
PRIVATE_KEY=0x... node web-research-agent.mjs "What is the x402 payment protocol?"
```

`PRIVATE_KEY` is a Base wallet holding a little USDC. Use a dedicated low-balance wallet; one dollar covers dozens of runs.

## The code

```js
// Web research agent in 30 lines: Google SERP -> read top pages -> cited answer, paid per call in USDC on Base (x402).
// npm i @x402/fetch @x402/core @x402/evm viem   then   PRIVATE_KEY=0x... node web-research-agent.mjs "your question"
import { wrapFetchWithPayment } from "@x402/fetch";
import { x402Client } from "@x402/core/client";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

const client = new x402Client();
client.register("eip155:*", new ExactEvmScheme(privateKeyToAccount(process.env.PRIVATE_KEY)));
const pay = wrapFetchWithPayment(fetch, client); // answers each 402 with a signed USDC payment, then retries
const post = async (url, body) => {
  const res = await pay(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
  if (!res.ok) throw new Error(`${url} -> HTTP ${res.status}: ${await res.text()}`);
  return res.json();
};

const question = process.argv[2] ?? "What is the x402 payment protocol?";
// 1. Search Google (Serper). Or Exa neural search, same price: post(".../v1/exa/search", { query: question, num_results: 5 }) -> results[].url
const serp = await post("https://search.gedx402.com/v1/serper/search", { q: question, num: 5 });
const links = (serp.organic ?? []).slice(0, 2).map((r) => r.link);
// 2. Read the top 2 pages as text (Exa contents-lite, one URL per call)
const pages = [];
for (const url of links) {
  const page = await post("https://search.gedx402.com/v1/exa/contents-lite", { url, max_characters: 3000 }).catch((e) => console.error(e.message));
  if (page?.results?.[0]?.text) pages.push(`SOURCE ${url}\n${page.results[0].text}`);
}
// 3. Answer with citations (OpenAI-compatible chat)
const content = `Answer "${question}" in 5 sentences, citing source URLs.\n\n${pages.join("\n\n")}`;
const chat = await post("https://chat.gedx402.com/v1/chat/completions", { messages: [{ role: "user", content }], max_tokens: 400 });
console.log(chat.choices[0].message.content);
```

## Notes

- **Errors are loud.** A non-2xx response throws with the status and body. A page that can't be read is logged and skipped, so one bad URL doesn't stop the answer.
- **Fixed cost per run.** Serper and contents-lite are flat. Chat is quoted from the body you send, so a bigger `max_characters` or `max_tokens` raises the chat quote. Inspect it first with an unpaid POST.
- **Request shapes:** Serper takes `{ q, num?, gl?, hl? }` (num 1–10). contents-lite takes exactly one `{ url, max_characters? }`; use `/v1/exa/contents` for up to 10 URLs. Chat is OpenAI-compatible (`choices[0].message.content`).
- More: [integrator quickstart](https://gedx402.com/docs/integrator-quickstart) · [competitive brief workflow](https://gedx402.com/docs/competitive-brief-bundle) · [hero pages](https://gedx402.com/heroes)
