Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
"integrations/google-a2a",
"integrations/nevermined-x402-ap2",
"integrations/openclaw",
"integrations/exa",
"integrations/buildship-integration"
]
}
Expand Down
83 changes: 83 additions & 0 deletions integrations/exa.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
title: "Exa"
description: "Buy Exa API credits with card delegation: a $7 x402 purchase provisions or tops up an Exa API key, fully agent-driven."
icon: "magnifying-glass"
---

[Exa](https://exa.ai) accepts autonomous payments through Nevermined's [x402 card-delegation](/specs/x402-card-delegation) scheme. An agent holding a Nevermined API key mints an x402 access token against the Exa plan and exchanges it at Exa's purchase endpoint for a working Exa API key. Repeat purchases top up the same key.

<Info>
Exa's Nevermined plan ID:<br />`27800462147494506865542649899724877617306579171265399959488097895839186996870`<br />The purchase is for API credits, not for a single search request. \$7 covers roughly 1,000 standard searches; see [Exa pricing](https://exa.ai/pricing) for current rates.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — state the environment this plan ID belongs to. Plan IDs are per-environment (live vs sandbox are separate backends), and Payments derives the environment from the API key prefix (getEnvironmentFromApiKey). A cold agent — exactly the page's stated test persona — handed a sandbox-prefixed key will mint against this live plan ID, the plan won't resolve, and the resulting error isn't in the troubleshooting table, so it dead-ends silently.

The flow charges a real $7, so it's inherently a live-key flow — just say so. One line in this <Info> block, e.g.:

This plan and purchase run on live; use a live-prefixed Nevermined API key.

And optionally a troubleshooting row for the mismatch (plan-not-found / wrong environment → check the key is live-prefixed).

</Info>

## Prerequisites (one time, done by the card owner)

1. Enroll a card at [nevermined.app](https://nevermined.app) → Payment Methods.
2. Create a **delegation** on the card: the spending permission an agent pays with. The owner sets the spending limit and duration, and can scope the delegation to a specific API key (recommended; it makes discovery deterministic for that key).
3. Generate an API key and give it to the agent. Two ways:
* **Embedded login flow (no copy/paste):** the agent hosts a callback on `127.0.0.1` and sends its human this URL to sign in: `https://nevermined.app/auth/cli?callback_url=http://127.0.0.1:<port>/callback`. After sign-in, the browser redirects to the callback with `nvm_api_key=<api-key>` for the agent to read.
* **Manual:** [nevermined.app](https://nevermined.app) → Account → API Keys → create a key and paste it to the agent (or open `https://nevermined.app/auth/cli` with no `callback_url` and copy the key shown on screen).

See the [x402 card-delegation spec](/specs/x402-card-delegation) for how enrollment and delegations work.

## The flow

Handled with the Nevermined Payments SDK (`npm install @nevermined-io/payments`; the package is ESM-only; in a fresh npm project set `"type": "module"` in `package.json`, or the import fails with `ERR_PACKAGE_PATH_NOT_EXPORTED`). The package's bundled TypeScript definitions are the authoritative call reference for every method named below. For broader context, start from the [Payments overview](/products/payments/overview) and the [documentation index](https://nevermined.ai/docs/llms.txt).

1. **Initialize** the SDK with your Nevermined API key only (`Payments.getInstance({ nvmApiKey })`). The environment is derived from the key prefix; do not pass an `environment` option.
2. **Find the delegation to pay with**; see [Getting a delegation](#getting-a-delegation) below.
3. **Mint the x402 access token** for the Exa plan ID via `payments.x402.getX402AccessToken`, using the `nvm:card-delegation` scheme and referencing the delegation by ID (`delegationConfig: { delegationId }`). No agentId is needed for this flow: the method's arguments are `(planId, agentId?, tokenOptions?)`, so pass the plan ID, `undefined`, and the token options. Tokens cannot create delegations on the fly; the delegation must exist first.
4. **POST the token to Exa** in the `payment-signature` header (endpoint below). The response contains the Exa API key. Check the HTTP status before reading the body.
5. **Use the key** against the standard [Exa Search API](https://docs.exa.ai).

## Getting a delegation

Query the delegations accessible to your API key with `payments.delegation.getPurchasingPower()` and select one with at least 700 cents of remaining budget. Budget fields are returned as strings, in cents. Purchasing-power results contain only delegations that can still pay; exhausted ones are excluded. With several delegations, discovery is deterministic when the owner key-scoped one to your key; to designate a specific budget among several, reference its ID explicitly.

**If discovery returns none**, the preferred path is for the card owner to create a delegation in the dashboard (Prerequisites, step 2); the agent then re-runs discovery. Nothing needs to be copied. If no operator is reachable (or the request goes unanswered), a fully autonomous agent can create one programmatically with `payments.delegation.createDelegation`. Its payload fields:

| Field | Value for this flow |
|---|---|
| `provider` | `"stripe"` |
| `providerPaymentMethodId` | The card's id from `payments.delegation.listPaymentMethods()` |
| `spendingLimitCents` | N × 700 for N expected purchases; a 700-cent delegation is exhausted after a single purchase |
| `durationSecs` | `3600` is a sensible default for a one-shot purchase; keep it short (least privilege) |
| `currency` | `"usd"` |

The response includes the `delegationId` to mint with (it also includes a `delegationToken`; not needed for this flow; treat it as a secret and do not log it). **Card selection:** `listPaymentMethods()` carries no ordering guarantee and cards may be indistinguishable by metadata; if several are enrolled and the owner's intent is unknown, prefer asking the owner; otherwise any Active card of the provider is acceptable, and you should record which payment-method id was chosen. Note the trade-off of this whole path: the agent chooses the card and sets its own budget. Prefer an owner-created delegation whenever an owner is available.

## Exa's purchase contract

```bash
POST https://admin-api.exa.ai/team-management/nevermined/purchase-key
payment-signature: <x402-token>
```

* **Cost:** \$7 per purchase, charged to the card behind the delegation referenced by the token.
* **New payer:** `{ status: "ok", apiKey: "…", expiresAt: null }`; a new Exa API key with \$7 of credits.
* **Returning payer:** the same API key with \$7 more credits added.
* **Replayed token:** cached result, no new charge.
* **Missing/invalid signature:** `402 Payment Required` with payment requirements in the body.

**When the key runs out:** Exa's regular API endpoints return `HTTP 402` with error tag `NO_MORE_CREDITS`. Mint a fresh x402 token with the same plan ID and delegation, POST it to the same endpoint, and Exa adds another \$7 of credits to the same key.

## Verifying charges

Use `payments.delegation.listDelegations()` to inspect spend per delegation (`amountSpentCents`, `transactionCount`, status). Fully spent delegations are marked `Exhausted` and no longer appear in `getPurchasingPower()` results; purchasing power lists only delegations that can still pay.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — confirm the literal Exhausted status value. The structural claim is verified (exhausted delegations drop out of getPurchasingPower() — it filters on accessible: true). But Exhausted as a specific status string isn't in the SDK (DelegationSummary.status is an untyped string; the only enumerated statuses the SDK documents are Active/Revoked on payment methods), so it's backend-emitted and I can't confirm the exact casing/wording from source. Since you runtime-verified this, just double-check Exhausted matches what listDelegations() actually returns byte-for-byte — a reader will string-match on it.


## Troubleshooting

| Symptom | Meaning / fix |
|---|---|
| Token mint fails: `Required token-generation input is missing or incomplete (HTTP 402)` | The mint referenced no existing delegation (e.g., legacy create-on-the-fly `delegationConfig` with card details). Create or discover a delegation first and pass `delegationConfig: { delegationId }`. |
| `ERR_PACKAGE_PATH_NOT_EXPORTED` on import | The SDK is ESM-only. Set `"type": "module"` in `package.json` or use `.mts`. |
| Console warning: `The 'environment' option is deprecated…` even though you never passed it | Known SDK issue; harmless. The environment is derived from your key prefix. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — this row is inaccurate on the SDK source I can see, and "harmless" is the wrong framing.

The deprecation warning is gated (src/api/base-payments.ts:132):

if (options.environment && !environmentOptionDeprecationWarned) {  console.warn("[DEPRECATED] The 'environment' option is deprecated…") }

It fires only when environment is actually passed — never "even though you never passed it". If an agent sees this warning, something is still passing environment (a wrapper, scaffold, or copied example), and the fix is to remove it — not to ignore it as a "known SDK issue". Documenting a correct, actionable signal as harmless trains agents to suppress it.

I'm on v1.9.0 locally; the PR tested 1.10.0. Two options:

  1. If 1.10.0 genuinely fires this with no environment passed, that's an SDK regression worth a payments issue — link it here rather than calling it "harmless".
  2. Otherwise, correct the row:
Suggested change
| Console warning: `The 'environment' option is deprecated…` even though you never passed it | Known SDK issue; harmless. The environment is derived from your key prefix. |
| Console warning: `The 'environment' option is deprecated…` | Something is still passing the deprecated `environment` option (a wrapper or copied example). Remove it — the environment is derived from your key prefix. |

| A delegation you spent from disappears from `getPurchasingPower()` | It is exhausted. Inspect it with `listDelegations()`; create or top up a delegation to continue. |
| Exa search returns `402` with `NO_MORE_CREDITS` | The Exa key's credits are spent. Repeat the purchase flow to top up the same key. |

## References

* [Payments overview](/products/payments/overview)
* [x402 card-delegation spec](/specs/x402-card-delegation)
* [Exa pricing](https://exa.ai/pricing)
* [Exa Search API](https://docs.exa.ai)
4 changes: 2 additions & 2 deletions solutions/api-providers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Nevermined turns that failure mode into revenue with a small server-side integra

[**Exa**](https://exa.ai) is the live reference. Agents pay Exa \$7 via a Nevermined-delegated card and receive an Exa API key with \$7 of credits. When the key runs out, the agent tops up through the same endpoint. Same key, more credits, no human touched it.

- Exa integration mirror: [exa.ai/docs/integrations/nevermined.md](https://exa.ai/docs/integrations/nevermined.md)
- Exa integration guide: [/integrations/exa](/integrations/exa)

## What you build, in five steps

Expand Down Expand Up @@ -386,7 +386,7 @@ Agents don't read your HTML. They read your `llms.txt` and the `.md` mirrors nex
Plain-text markdown skeleton with placeholders. Drop it wherever your docs live and fill in your plan ID, endpoint, and response shape.
</Card>

Live example: [Exa's `/docs/integrations/nevermined.md`](https://exa.ai/docs/integrations/nevermined.md).
Live example of the full flow: the [Exa integration guide](/integrations/exa).
</Tab>
</Tabs>

Expand Down