> ## Documentation Index
> Fetch the complete documentation index at: https://exa.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The Exa API is served at https://api.exa.ai. Authenticate with `Authorization: Bearer $EXA_API_KEY` (or `x-api-key: $EXA_API_KEY`); create keys at https://dashboard.exa.ai/api-keys?utm_source=docs&utm_medium=docs&utm_campaign=markdown_instructions. Every way an agent can get access to Exa is described at https://exa.ai/auth.md.
> Prefer the official SDKs, `exa-py` (`pip install exa-py`) and `exa-js` (`npm install exa-js`); both read `EXA_API_KEY` from the environment.
> Tool-using agents can call Exa without writing code through the hosted MCP server at https://mcp.exa.ai/mcp, or install the Exa agent skill with `npx skills add exa-labs/agent-skills` (skill file: https://exa.ai/docs/skill.md).
> The OpenAPI specs at https://exa.ai/docs/exa-spec.yaml and https://exa.ai/docs/team-management-spec.yaml are the source of truth for request and response schemas.

# Tempo MPP GTM Enrichment Cookbook

> Build a GTM enrichment workflow that pays per Exa search and contents request with Tempo MPP — no API key required.

Use this cookbook to build a GTM enrichment agent or pipeline on top of Exa's
`/search` and `/contents` endpoints, paid per request through the Machine
Payments Protocol (MPP). MPP supports multiple payment methods; the examples
here use stablecoins on [Tempo](https://tempo.xyz). No monthly subscription, no
API key, and no seat-based pricing: fund a wallet with USDC.e and pay as you
enrich leads or companies.

> [!NOTE]
>
> MPP is currently supported on Exa's `/search` and `/contents` endpoints only.
> The Agent API (`/agent/runs`) and `/answer` require an Exa API key and go
> through the standard API key billing flow.

## What you'll build

A lightweight enrichment pipeline that, given a list of company names or target
descriptions:

1. Uses Exa `/search` with `type: "deep"` and `outputSchema` to find the
   official company page and extract key metadata.
2. Uses `contents.highlights` on the returned result to pull source snippets
   for funding, headquarters, employees, and product.
3. Emits a CSV or JSON enrichment record per input.

This pattern works for lead-list enrichment, account research, and outbound
personalization. Because it is composed of discrete `/search` + `/contents`
calls, every step can be paid for with MPP.

## Prerequisites

* A Tempo-compatible wallet funded with **USDC.e** on Tempo mainnet.
* A safe way to load the wallet private key at runtime (see below; never commit
  the key or expose it in source code).
* `mppx` (TypeScript) or `pympp` (Python) installed.

> [!NOTE]
>
> For a command-line setup that doesn't need a raw private key, use the [Tempo Wallet CLI](https://exa.ai/docs/integrations/payments/mpp/quickstart.md#pay-from-the-command-line). `tempo wallet login` creates or connects a wallet and may include free MPP Credits for new sign-ups.

## MPP setup

### Install the client

```bash TypeScript theme={null}
npm install mppx viem
```

```bash Python theme={null}
pip install "pympp[tempo]"

# Or, if using uv:
uv add "pympp[tempo]"
```

### Load your private key safely

Never hardcode a private key. The examples below read `WALLET_PRIVATE_KEY` from
your runtime environment for local development only. In production, load it from
a secrets manager such as 1Password, AWS Secrets Manager, or HashiCorp Vault.

```bash TypeScript theme={null}
# Set in your shell or CI secrets store; never commit this value
export WALLET_PRIVATE_KEY="0x..."
```

```bash Python theme={null}
# Set in your shell or CI secrets store; never commit this value
export WALLET_PRIVATE_KEY="0x..."
```

### Make a paid search request

```typescript TypeScript theme={null}
import { Mppx, tempo } from "mppx/client";
import { privateKeyToAccount } from "viem/accounts";

// In production, load this from a secrets manager — never commit the raw value.
const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as `0x${string}`);
const mppx = Mppx.create({
  methods: [tempo.charge({ account })],
});

const response = await mppx.fetch("https://api.exa.ai/search", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    query: "Series A fintech companies with 50-200 employees",
    numResults: 5,
    contents: { highlights: true },
  }),
});

const data = (await response.json()) as { results: { title: string; url: string }[] };
console.log(data.results);
console.log("Payment receipt:", response.headers.get("Payment-Receipt"));
```

```python Python theme={null}
import asyncio
import os

from mpp.client import Client
from mpp.methods.tempo import ChargeIntent, TempoAccount, tempo


async def main() -> None:
    # In production, load this from a secrets manager — never commit the raw value.
    account = TempoAccount.from_key(os.environ["WALLET_PRIVATE_KEY"])
    method = tempo(
        account=account,
        chain_id=4217,
        intents={"charge": ChargeIntent()},
    )

    async with Client(methods=[method]) as client:
        response = await client.post(
            "https://api.exa.ai/search",
            json={
                "query": "Series A fintech companies with 50-200 employees",
                "numResults": 5,
                "contents": {"highlights": True},
            },
        )

    data = response.json()
    for result in data["results"]:
        print(result["url"], result["title"])
    print("Payment receipt:", response.headers.get("Payment-Receipt"))


asyncio.run(main())
```

A successful response returns Exa results plus a `Payment-Receipt` header with
the on-chain transaction hash.

### Make a paid contents request

```typescript TypeScript theme={null}
const contentsResponse = await mppx.fetch("https://api.exa.ai/contents", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    urls: ["https://www.example.com"],
    text: true,
    summary: true,
  }),
});

const contentsData = (await contentsResponse.json()) as {
  results: { url: string; text?: string; summary?: string }[];
};
console.log(contentsData.results[0]);
```

```python Python theme={null}
response = await client.post(
    "https://api.exa.ai/contents",
    json={
        "urls": ["https://www.example.com"],
        "text": True,
        "summary": True,
    },
)
print(response.json()["results"][0])
```

## GTM enrichment recipe

### Enrich a list of companies

Given a list of company names, search for each company's page and extract
structured details.

```typescript TypeScript theme={null}
interface CompanyEnrichment {
  name: string;
  url: string;
  title: string;
  industry?: string;
  headquarters?: string;
  funding?: string;
  summary?: string;
  highlights: string[];
}

async function enrichCompanies(names: string[]): Promise<CompanyEnrichment[]> {
  const enriched: CompanyEnrichment[] = [];

  for (const name of names) {
    const response = await mppx.fetch("https://api.exa.ai/search", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        query: `${name} official company`,
        type: "deep",
        numResults: 1,
        contents: {
          highlights: { query: "funding, headquarters, employees, product" },
        },
        outputSchema: {
          type: "object",
          properties: {
            company: {
              type: "object",
              properties: {
                name: { type: "string" },
                url: { type: "string" },
                industry: { type: "string" },
                headquarters: { type: "string" },
                funding: { type: "string" },
                summary: { type: "string" },
              },
              required: ["name", "url"],
            },
          },
          required: ["company"],
        },
      }),
    });

    const data = (await response.json()) as {
      output?: { company?: CompanyEnrichment & { summary?: string } };
      results?: { highlights?: string[] }[];
    };
    const company = data.output?.company;
    const highlights = data.results?.[0]?.highlights?.slice(0, 3) ?? [];
    if (!company) continue;

    enriched.push({
      ...company,
      title: company.name,
      highlights,
    });
  }

  return enriched;
}
```

```python Python theme={null}
async def enrich_companies(names):
    enriched = []
    for name in names:
        response = await client.post(
            "https://api.exa.ai/search",
            json={
                "query": f"{name} official company",
                "type": "deep",
                "numResults": 1,
                "contents": {
                    "highlights": {"query": "funding, headquarters, employees, product"}
                },
                "outputSchema": {
                    "type": "object",
                    "properties": {
                        "company": {
                            "type": "object",
                            "properties": {
                                "name": {"type": "string"},
                                "url": {"type": "string"},
                                "industry": {"type": "string"},
                                "headquarters": {"type": "string"},
                                "funding": {"type": "string"},
                                "summary": {"type": "string"},
                            },
                            "required": ["name", "url"],
                        }
                    },
                    "required": ["company"],
                },
            },
        )
        data = response.json()
        company = data.get("output", {}).get("company")
        highlights = []
        if data.get("results"):
            highlights = data["results"][0].get("highlights", [])[:3]
        if not company:
            continue

        enriched.append({
            "name": company["name"],
            "url": company["url"],
            "title": company["name"],
            "industry": company.get("industry"),
            "headquarters": company.get("headquarters"),
            "funding": company.get("funding"),
            "summary": company.get("summary"),
            "highlights": highlights,
        })
    return enriched
```

### Enrich a person profile

This recipe uses `type: "deep"`, `contents.highlights`, and `outputSchema` to
research a person and return a structured profile.

```typescript TypeScript theme={null}
const response = await mppx.fetch("https://api.exa.ai/search", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    query: "Exa Labs founders contact and background",
    type: "deep",
    numResults: 5,
    contents: {
      highlights: { query: "email, title, education, work history, LinkedIn" },
    },
    outputSchema: {
      type: "object",
      properties: {
        people: {
          type: "array",
          items: {
            type: "object",
            properties: {
              name: { type: "string" },
              title: { type: "string" },
              company: { type: "string" },
              email: { type: "string" },
              linkedInUrl: { type: "string" },
              summary: { type: "string" },
            },
            required: ["name"],
          },
        },
      },
      required: ["people"],
    },
  }),
});

const data = (await response.json()) as {
  output?: { people: { name: string; title?: string; company?: string }[] };
};
console.log(data.output?.people);
```

```python Python theme={null}
response = await client.post(
    "https://api.exa.ai/search",
    json={
        "query": "Exa Labs founders contact and background",
        "type": "deep",
        "numResults": 5,
        "contents": {
            "highlights": {"query": "email, title, education, work history, LinkedIn"}
        },
        "outputSchema": {
            "type": "object",
            "properties": {
                "people": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "name": {"type": "string"},
                            "title": {"type": "string"},
                            "company": {"type": "string"},
                            "email": {"type": "string"},
                            "linkedInUrl": {"type": "string"},
                            "summary": {"type": "string"},
                        },
                        "required": ["name"],
                    },
                }
            },
            "required": ["people"],
        },
    },
)

print(response.json().get("output", {}).get("people"))
```

> [!NOTE]
>
> This uses `type: "deep"` for richer reasoning and `outputSchema` to shape the
> response. Deep search is priced at \$0.012 per request, and
> `contents.highlights` adds \$0.001 per result.

### Structured output

If you want JSON fields instead of raw text, use `outputSchema` in the search
request. Exa returns an `output` object shaped to your schema.

```python Python theme={null}
response = await client.post(
    "https://api.exa.ai/search",
    json={
        "query": "Series A fintech companies with 50-200 employees",
        "type": "deep-lite",
        "numResults": 5,
        "outputSchema": {
            "type": "object",
            "properties": {
                "companies": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "name": {"type": "string"},
                            "headcount": {"type": "string"},
                            "headquarters": {"type": "string"},
                            "fundingStage": {"type": "string"},
                        },
                        "required": ["name"],
                    },
                }
            },
            "required": ["companies"],
        },
    },
)
```

```javascript JavaScript theme={null}
const response = await mppx.fetch("https://api.exa.ai/search", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    query: "Series A fintech companies with 50-200 employees",
    type: "deep-lite",
    numResults: 5,
    outputSchema: {
      type: "object",
      properties: {
        companies: {
          type: "array",
          items: {
            type: "object",
            properties: {
              name: { type: "string" },
              headcount: { type: "string" },
              headquarters: { type: "string" },
              fundingStage: { type: "string" }
            },
            required: ["name"]
          }
        }
      },
      required: ["companies"]
    }
  })
});
```

> [!NOTE]
>
> `outputSchema` works best with `deep-lite` or `deep` search types.

## Pricing and limits

MPP uses the same per-request pricing as API key billing. MPP search requests
are capped at 10 results.

| Operation | Price |
| - | - |
| `/search` with `type` `lite` or `instant` | \$0.004 per request |
| `/search` with `type` `auto` or `fast` | \$0.007 per request |
| `/search` with `type` `deep-lite` or `deep` | \$0.012 per request |
| `/search` with `type` `deep-reasoning` | \$0.015 per request |
| `contents.text` | \$0.001 per URL |
| `contents.highlights` | \$0.001 per URL |
| `contents.summary` | \$0.001 per result |

See [Pay with MPP (Tempo)](https://exa.ai/docs/integrations/payments/mpp/quickstart.md) for the full reference,
including rate limits, network details, and payment headers.

## Production tips

* **Fund the wallet with USDC.e only.** Exa sponsors the Tempo network fee, so
  the wallet does not need a separate gas token.
* **Handle `402` responses.** The MPP SDK retries automatically, but a custom
  client should retry on `402` using the `WWW-Authenticate: Payment` challenge.
* **Cache `/contents` results.** Contents are priced per URL. Cache by URL to
  avoid paying twice for the same company page.
* **Watch the 10-result cap.** MPP search clamps `numResults` to 10.
* **Never commit private keys.** Load `WALLET_PRIVATE_KEY` from a secrets
  manager, not source control.

## FAQ

> [accordion] Can I use MPP with the Exa Agent API?
>
> No. MPP works only on `/search` and `/contents`. `/agent/runs` and
> `/answer` require an Exa API key and use standard API key billing.

> [accordion] Can I mix MPP and an Exa API key on the same request?
>
> No. If a request includes `x-api-key` or `Authorization: Bearer`, the API
> key flow takes priority and MPP is bypassed.

> [accordion] What happens if MPP settlement fails?
>
> Exa returns `402` with a fresh `WWW-Authenticate: Payment` challenge and no
> results. Your client can retry with a new payment. No results are returned
> until settlement succeeds.

> [accordion] Do I need a separate Tempo wallet per environment?
>
> You can reuse the same wallet, but we recommend separate wallets for
> development and production. Per-wallet QPS is 10 requests/second across all
> requests from that wallet.

## Next steps

* [Pay with MPP (Tempo)](https://exa.ai/docs/integrations/payments/mpp/quickstart.md): full MPP reference
* [Exa Search API guide](https://exa.ai/docs/search/quickstart.md): search parameter reference
* [Exa Contents API guide](https://exa.ai/docs/contents/quickstart.md): contents parameter reference
* [Tempo MPP docs](https://mpp.dev/protocol): protocol and SDK details
