Nonagon Link — AI Agent Developer Guide
Integration guide for calling Nonagon Link paid APIs with USDC from AI agents
1. Overview
Nonagon Link is an agentic payment gateway compliant with the x402 protocol (Coinbase). AI agents can use paid APIs in the following two ways.
| Method | Best suited for | Required implementation |
|---|---|---|
| MCP (recommended) | MCP-capable AI such as Claude / GPT-4 | MCP server connection settings only |
| Direct x402 protocol integration | Custom agents / SDK embedding | Integration of @x402/fetch |
2. Prerequisites
- A Solana wallet for payments (USDC / SOL will be paid from this wallet). Use mainnet for production, or devnet to try things out at no cost
- USDC token holdings (production = real USDC on mainnet / testing = free airdrop on devnet)
- Node.js >= 20.0.0 (for the MCP method)
Preparing a Solana wallet and USDC for payments (the example below uses devnet for testing)
# Generate a keypair with the Solana CLI
solana-keygen new --outfile ~/.config/solana/devnet.json --no-bip39-passphrase
# Check the public key
solana-keygen pubkey ~/.config/solana/devnet.json
# Airdrop devnet SOL (for transaction fees)
solana airdrop 2 --url devnet
# devnet USDC is distributed automatically in the Nonagon Link test environment3. Method A: Connect via Nonagon Link (recommended)
MCP (Model Context Protocol) is a standard protocol that lets AI agents such as Claude / GPT-4 call external tools. Nonagon Link provides the Nonagon Link MCP server, allowing AI agents to call paid APIs transparently.
3-1. Setup
See the MCP setup guide for details.
For Claude Code:
.claude/settings.json:
{
"mcpServers": {
"nonagon-link": {
"command": "npx",
"args": ["tsx", "apps/nonagon_link/src/mcp/server.ts"],
"cwd": "/path/to/nonagon_link",
"env": {
"NONAGON_PRIVATE_KEY": "<private key of the Solana wallet used for payments (Base58)>",
"NONAGON_NETWORK": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"NONAGON_LINK_BASE_URL": "https://api.nnglink.ai",
"NONAGON_PAY_TO": "8yXpNyyPg9iGdMST8BRyz7HEJVbQG81VP6kHBCKXrdH8",
"LOG_LEVEL": "info"
}
}
}
}Even when using HTTP stream, the runtime binds only to 127.0.0.1, so treat it as intended for local HTTP clients on the same machine. Do not expose it as a public endpoint over the network.
In HTTP stream mode, setting MCP_AUTH_TOKEN is required. Set MCP_AUTH_TOKEN on the server side, and always attach Authorization: Bearer <MCP_AUTH_TOKEN> on the client side as well.
Use a CSPRNG-generated secret of at least 32 bytes for MCP_AUTH_TOKEN. The published configuration examples only accept values of at least 43 base64url characters or at least 64 hex characters.
3-2. Available tools
pay_and_call
Pays a paid API and retrieves data: Solana USDC, plus JPYC / TEST_JPYSC with an EVM key and NONAGON_EVM_PAY_TO, and EVM USDC only when MCP_MAX_PAYMENT_EVM_USDC_CAP is set (see the MCP setup guide).
| Parameter | Type | Required | Description |
|---|---|---|---|
proxyUrl | string (URL) | Required | URL of the API to call (HTTPS only) |
method | "GET" | "POST" | Optional | HTTP method (default: GET) |
body | object | Optional | Body of the POST request |
maxPaymentUsdc | number | Required | Maximum Solana USDC this call may pay (greater than 0, up to 0.100 by default; the ceiling is MCP_MAX_PAYMENT_USDC_CAP). Not used for EVM USDC. If the amount requested by the 402 exceeds this, an error is returned without paying |
maxPaymentJpy | number | Optional | Maximum for yen-denominated listings (JPYC / TEST_JPYSC): greater than 0, up to MCP_MAX_PAYMENT_JPY_CAP (100 by default), at most 4 decimals. Without it a yen 402 is not paid. Needs NONAGON_EVM_PRIVATE_KEY and NONAGON_EVM_PAY_TO |
maxPaymentEvmUsdc | number | Optional | Maximum for EVM USDC listings: greater than 0, up to MCP_MAX_PAYMENT_EVM_USDC_CAP, at most 6 decimals. EVM USDC is paid only when that cap is set, and only with this argument |
⚠️ EVM payments (JPYC, EVM USDC): an EVM payment is a signed EIP-3009 authorization, and any third party that receives it (such as the host you call) can execute it on-chain itself. The money can only go to the treasury in NONAGON_EVM_PAY_TO, but you then get no service and no payment record. There is no session or daily total limit: keep each cap as low as you need, keep the EVM wallet balance small, and do not pay_and_call URLs you do not trust. When paymentStatus is unknown, do not retry; check authorizationState(from, nonce) on the token contract.
Usage example (request to Claude):
Please call the following API with pay_and_call:
- URL: https://api.nnglink.ai/api/proxy/weather-api/current
- Method: POST
- Body: { "city": "Tokyo" }
- Payment limit: 0.05 USDCExample response:
{
"success": true,
"data": {
"city": "Tokyo",
"temperature": 22,
"condition": "sunny"
},
"paymentStatus": "paid",
"paymentInfo": {
"currency": "USDC",
"amount": "0.01",
"amountUsdc": "0.01",
"txSignature": "5xKt...",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"payer": "<your wallet>",
"paidAt": "2026-10-05T00:00:00.000Z"
}
}paymentStatus is paid, not_paid or unknown. On unknown (a signed payment was sent but the result is not confirmed), do not retry automatically: a retry can pay twice. For EVM payments, check authorizationState(from, nonce). For yen tokens (JPYC / TEST_JPYSC), amount is in that currency and amountUsdc is null.
Decide that nothing was paid only from paymentStatus being not_paid (a free API or a reused token, for example). paymentInfo can be null while paymentStatus is unknown (an EVM payment whose result is unknown), so a null paymentInfo alone does not mean nothing was paid.
get_active_tokens
Retrieves summaries of currently valid masked tokens and their remaining validity time.
The public response returns only summaries containing maskedToken; raw token values are never exposed.
Example response:
{
"tokens": [
{
"maskedToken": "agt_abcd...mnop",
"proxyUrl": "https://api.nnglink.ai/api/proxy/weather-api/current",
"expiresAt": "2026-05-11T12:00:00Z",
"remainingMinutes": 45
}
]
}search_listings
Searches paid APIs and returns price, payment compatibility, schemas, ratings, health, decision gaps, and a proxyUrl for pay_and_call.
Note: To use this tool, the MCP server must be configured with NONAGON_LINK_BASE_URL (e.g. https://api.nnglink.ai).
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | Optional | Search keyword (partial match on name / description, up to 200 characters) |
category | string | Optional | Filter by category (up to 40 characters) |
chain / networkId | string | Optional | Filter by payment chain or CAIP-2 network |
currency / maxPrice | string / number | Optional | Currency and per-call budget; currency is required with maxPrice |
health | string | Optional | Filter by healthy, unhealthy, or unknown |
minRatingCount | number | Optional | Minimum settled-payment rating count |
payableViaMcpOnly | boolean | Optional | Return only listings payable on this MCP server's configured network |
sort / order | string | Optional | Defaults to decision_readiness / desc |
limit | number | Optional | Number of results (1–10, default 5) |
decision_readiness ranks pre-purchase inspectability. It does not guarantee response correctness or data quality.
get_payment_history
Fetches the payment history for this MCP server's wallets (NONAGON_PRIVATE_KEY, plus NONAGON_EVM_PRIVATE_KEY when set), including inline payments made via pay_and_call. History is scoped to the wallet, not the session.
With an EVM wallet configured, both wallets' histories are merged newest first and each row carries wallet (solana / ethereum). wallets reports each wallet's result (status: ok / error); if one wallet fails, the other wallet's rows are returned with partial: true. For EVM payments a missing history row does not prove the authorization was not executed (on unknown, check authorizationState(from, nonce)).
Note: To use this tool, the MCP server must be configured with NONAGON_LINK_BASE_URL.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | Optional | Number of records (1–50, default 10) |
status | string | Optional | Filter by payment status (settled / authorized / failed / refunded, etc.) |
get_spending
Fetches cumulative spending (settled + authorized) for this MCP server's wallets by period, with a per-currency breakdown. Useful for budget tracking.
The top-level total / count / byStatus / byCurrency / period / from are the Solana wallet's values, and total / count / byStatus are USDC only. With an EVM wallet configured the response also has wallets (each wallet's byCurrency) and assets (one entry per wallet × currency). Amounts are never added across wallets or currencies; read assets for JPYC and for the EVM wallet. If one wallet fails, the response has partial: true and only the other wallet's values.
Note: To use this tool, the MCP server must be configured with NONAGON_LINK_BASE_URL.
| Parameter | Type | Required | Description |
|---|---|---|---|
period | string | Optional | Aggregation period: day (24h) / week (7d) / month (30d) / all (default) |
4. Method B: Direct x402 protocol integration
4-1. Overview of the x402 protocol
x402 is a micropayment protocol that uses the HTTP 402 (Payment Required) response.
Agent → Provider API → 402 Payment Required (PaymentRequirements)
Agent → Signs a USDC transfer transaction offline on Solana
Agent → Provider API + X-PAYMENT-* headers
Provider → Nonagon Link /verify → verification OK
Provider → Returns response
Provider → Nonagon Link /settle → submitted to the blockchain4-2. Integration using @x402/fetch
import { wrapFetchWithPayment, x402Client } from '@x402/fetch'
import { ExactSvmScheme } from '@x402/svm'
import { createKeyPairSignerFromBytes } from '@solana/kit'
import bs58 from 'bs58'
// 1. Initialize the Solana signer (private key of the payment wallet)
const privateKeyBytes = bs58.decode(process.env.NONAGON_PRIVATE_KEY!)
const signer = await createKeyPairSignerFromBytes(privateKeyBytes)
// 2. Initialize the x402 client
const client = new x402Client()
client.register(
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', // mainnet (devnet is solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1)
new ExactSvmScheme(signer)
)
// 3. Create a payment-enabled fetch
const payFetch = wrapFetchWithPayment(fetch, client)
// 4. Call the API (if a 402 is returned, it automatically pays and retries the request)
const response = await payFetch(
'https://api.nnglink.ai/api/proxy/weather-api/current',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ city: 'Tokyo' }),
}
)
const data = await response.json()
console.log(data)4-3. Facilitator API reference
GET /api/facilitator/supported
Returns information about supported blockchains and tokens.
Response:
{
"x402Version": 2,
"kinds": [
{
"scheme": "exact",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"feePayer": "<facilitator fee payer public key>"
}
]
}POST /api/facilitator/verify
Verifies the Agent's partially signed transaction and issues a settleToken.
Request: Conforms to the x402 protocol specification (HMAC-SHA256 signature + nonce)
Response:
{
"valid": true,
"settleToken": "<JWT>"
}POST /api/facilitator/settle
Co-signs the verified transaction, submits it to the blockchain, and finalizes the payment.
Request: settleToken + HMAC-SHA256 signature
Response:
| Field | Description |
|---|---|
success | true (payment settled) |
transaction | Solana transaction signature |
network | e.g. solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp |
payer | Agent wallet address |
accessToken | Issued access token (agt_ format). Treat as a secret — never log it |
accessTokenExpiresAt | Access token expiry (ISO 8601) |
5. Searching for APIs
5-1. Listing search API
GET /api/listings?category=weather&sort=price_usdc&order=asc&limit=10| Parameter | Description |
|---|---|
q | Keyword search (partial match on name / description) |
category | Filter by category |
chain / networkId / currency | Filter by payment chain, CAIP-2 network, or currency |
maxPrice | Maximum price in the selected currency; currency is required |
health / minRatingCount | Filter by current health and settled-payment rating evidence |
sort | Sort by update time, price, name, rating, uptime, or decision_readiness (default: updated_at) |
order | Sort order (asc / desc, default: desc) |
limit | Number of results (default: 20, max: 100) |
offset | Pagination (default: 0) |
Response:
{
"total": 42,
"limit": 20,
"offset": 0,
"listings": [
{
"id": "uuid",
"name": "Weather API",
"description": "API that returns the current weather",
"category": "weather",
"endpointUrl": "https://api.nnglink.ai/api/proxy/weathercorp/weather-api",
"httpMethod": "POST",
"priceUsdc": "0.010000",
"listingType": "proxy",
"timeoutSeconds": 60,
"providerId": "uuid",
"providerName": "WeatherCorp",
"hasSchema": true,
"hasExample": true,
"hasResponseSchema": true,
"hasResponseSample": true,
"coverage": null,
"updatedAt": "2026-05-10T09:00:00Z",
"openapiUrl": "/api/providers/<providerId>/listings/<id>/openapi"
}
]
}5-2. Provider public information API
GET /api/providers/{id}/publicRetrieves the Provider's public profile and the list of Listings it offers.
6. Calling the Proxy API
When calling a Provider API through the Nonagon Link proxy:
POST /api/proxy/{providerSlug}/{listingSlug}- Authentication: x402 protocol (automatic payment) or a pre-obtained access token
- The Provider's API Key is injected server-side by Nonagon Link (never exposed to the Agent)
- SSRF protection: private IPs / loopback are automatically rejected
7. Security considerations
Private key management
- Manage
NONAGON_PRIVATE_KEYvia environment variables and never hardcode it in code - Use Secrets Manager or similar in CI/CD
USDC balance
- Confirm you have a sufficient USDC balance before paying
- If the balance is insufficient, a 402 response is returned
Network
- devnet: for testing (free USDC)
- mainnet: for production (real USDC)
- Be careful not to mix up environments
8. Error codes
| HTTP | Code | Description |
|---|---|---|
| 402 | PAYMENT_REQUIRED | USDC payment required |
| 401 | UNAUTHORIZED | Authentication failed / invalid token |
| 403 | FORBIDDEN | Insufficient permissions |
| 404 | NOT_FOUND | Listing / Provider not found |
| 422 | NONCE_EXPIRED | nonce expired (5 minutes) |
| 429 | RATE_LIMITED | Rate limit exceeded |
| 502 | PROXY_PROVIDER_ERROR | The Provider API returned an error |
| 503 | SERVICE_UNAVAILABLE | Service temporarily unavailable |
9. SDKs and reference links
| Resource | URL |
|---|---|
| x402 protocol specification | https://www.x402.org |
| @x402/fetch (Agent SDK) | https://www.npmjs.com/package/@x402/fetch |
| @x402/svm (Solana) | https://www.npmjs.com/package/@x402/svm |
| MCP specification | https://modelcontextprotocol.io |
| MCP setup guide | /docs/mcp |