AgentSmithx402
Get started / Quickstart

Quickstart

Two paths, depending on which side of the transaction you are on. Both run against stellar:testnet, which is free and needs no key.

I want to get paid

sellerbash
npm install @agentsmith/x402
server.tstypescript
import { paywall } from "@agentsmith/x402/server";

app.use(paywall({
  facilitator: "https://x402.agentsmith.xyz",
  network:     "stellar:testnet",
  payTo:       process.env.STELLAR_ADDRESS,
  routes: {
    "GET /forecast": { price: "$0.002", asset: "USDC" }
  }
}));

I want to pay

agent.tstypescript
import { search, fetchPaid } from "@agentsmith/x402";

const [top] = await search({ query: "hourly weather forecast by city" });

const res = await fetchPaid(top.resource.url, { signer });
const data = await res.json();
No key required for discovery. Browsing and searching the Bazaar is public. You need a Stellar account only at the point where you actually pay for something.
Get started / Core concepts

Core concepts

TermMeaning
FacilitatorThe service that verifies a payment authorization and settles it on-chain. It never takes custody and is never the source of funds.
ResourceA paid HTTP endpoint or a paid MCP tool. Identified by resource.url, and for MCP by the tuple (resource.url, input.toolName).
BazaarThe discovery catalog. Resources enter it automatically when a payment carrying discovery metadata settles.
SchemeHow the amount is determined. exact charges a fixed price. upto authorizes a cap and settles actual usage.
Auth entryA Soroban authorization signed by the buyer permitting one specific contract call. Not a pre-signed transaction — the facilitator builds and submits the transaction around it.
Buyer & agent / Discover services

Discover services (Bazaar)

The Bazaar is how an agent finds a service it has no prior integration with. Every result carries what is needed to call and pay for the resource: the URL or tool name, the network, the asset, the amount, and the receiving address.

Bazaar discovery is public. You do not need an API key to use the discovery endpoints, the SDK discovery functions, or the MCP discovery server.

Choose a discovery interface

Three ways in. They return the same resources in the same shape.

TypeScript

SDK

Typed helpers over the discovery endpoints. Use this when you are writing the agent yourself.

HTTP

REST API

Plain GET requests. Use this from any language, or to inspect the catalog by hand.

Agent runtime

Bazaar MCP

Search and paid-call tools inside an agent runtime. Use this when a model drives the loop.

Discover with the SDK

installbash
npm install @agentsmith/x402

Search by relevance

search takes a natural-language query and returns ranked results. Filters are applied as hard constraints before ranking, so a result you cannot pay for never occupies a ranked slot.

search.tstypescript
import { search } from "@agentsmith/x402";

const results = await search({
  query:      "convert a PDF invoice into structured line items",
  type:       "http",              // "http" | "mcp"
  network:    "stellar:pubnet",
  maxPriceUsd: 0.05,
  limit:      10
});

for (const r of results.resources) {
  console.log(r.resource.serviceName, r.accepts[0].maxAmountRequired);
}

Browse without a query

When you do not need relevance ranking — a periodic sync, an inventory, a UI listing — browse the catalog directly with offset pagination.

browse.tstypescript
import { listResources } from "@agentsmith/x402";

let offset = 0;
while (true) {
  const page = await listResources({ type: "mcp", limit: 50, offset });
  if (page.resources.length === 0) break;
  index(page.resources);
  offset += page.resources.length;
}

List one seller's resources

If you know the address receiving payment, filter on it to see everything that seller has listed.

merchant.tstypescript
const page = await listResources({
  payTo: "GDX2ZC7YQK4NUV3RJ6H5TQMWX8FLPB2E9AYRK4TC6VMZ0JHS4N19WQA"
});

Discover with the REST API

Two endpoints. /discovery/search ranks by relevance and requires a query; /discovery/resources browses and does not.

curlbash
curl "https://agentsmith.xyz/discovery/search?query=weather+forecast&type=http&limit=5"

curl "https://agentsmith.xyz/discovery/resources?network=stellar:pubnet&limit=50&offset=0"

Query parameters

Parameter Type Endpoint Description
querystringsearchRequired on search. Natural-language description of the capability wanted.
typestringbothhttp or mcp.
payTostringbothFilter to one receiving address.
schemestringbothexact or upto.
networkstringbothCAIP-2 identifier, e.g. stellar:pubnet.
extensionsstringbothFilter to resources declaring a given protocol extension.
limitnumberbothMaximum resources returned.
offsetnumberresourcesNumber of results to skip.
cursorstringsearchContinuation token from the previous page. Advisory.

Response envelope

GET /discovery/searchjson
{
  "x402Version": 2,
  "resources": [ /* see Resource object */ ],
  "partialResults": false,
  "pagination": { "limit": 20, "cursor": "eyJvIjoyMH0" }
}

partialResults is true when the response is complete enough to use but not fully ranked — for example the reranking pass exceeded its latency budget and results were returned from fusion alone. Treat the ordering as weaker, not the results as wrong.

Discover with Bazaar MCP

The MCP discovery server puts catalog search behind a tool call, so a model can find a capability in its own loop — no discovery-specific code on your side. Paying for what it finds is the separate flow documented in Discover & pay over MCP.

The Bazaar exposes the catalog over MCP Streamable HTTP, on its own port — separate from the REST API — at /mcp (default https://agentsmith.xyz/mcp). It is stateless and unauthenticated — discovery is free. Point any MCP client at the URL:

mcp.jsonjson
{
  "mcpServers": {
    "agentsmith-bazaar": {
      "type": "http",
      "url": "https://agentsmith.xyz/mcp"
    }
  }
}

Claude Code can register it in one line — claude mcp add --transport http bazaar https://agentsmith.xyz/mcp — or connect programmatically with the @modelcontextprotocol/sdk Client over StreamableHTTPClientTransport.

ToolWhat it does
search_servicesNatural-language search over the catalog. Required query, plus the same optional filters as the REST endpoint (type, network, asset, maxPriceUsd, limit). Returns a merged list — available (payable) services first, unavailable ones at the tail — as structuredContent plus a text summary.

Inputs and outputs are structured and deterministic. Every rejection carries a non-null machine-readable reason, so a model can reason about the failure instead of parsing an error string.

Resource object

What a catalog entry contains, and which parts are optional.

FieldDescription
resource.urlEndpoint address. Together with input.toolName this is the catalog key for MCP tools.
resource.descriptionHuman-readable summary of what the resource does.
resource.mimeTypeResponse content type.
resource.serviceNameOptional. Provider name, up to 32 ASCII characters.
resource.tagsOptional. Up to 5 topical keywords.
resource.iconUrlOptional. HTTP or HTTPS only.
info.inputHow to invoke it. Discriminated by type: http or mcp.
info.outputOptional. Response shape.
schemaJSON Schema (Draft 2020-12) that info is validated against at catalog time.
routeTemplateOptional. Parameterized path pattern using :param syntax, for dynamic routes.
accepts[]Payment requirements: scheme, network, asset, maxAmountRequired, payTo, maxTimeoutSeconds.

For MCP resources, info.input additionally carries toolName and inputSchema (both required), plus optional description, transport and example.

What to read next

Pay for a resource →

Take a discovery result through 402, signing, and settlement.

Get discovered →

Declare metadata so your endpoint enters the catalog when it is first paid.

Buyer & agent / Pay for a resource

Pay for a resource

A discovery result already carries the payment requirements, so the flow below works whether you found the resource in the Bazaar or already knew its URL.

pay.tstypescript
import { fetchPaid } from "@agentsmith/x402";

const res = await fetchPaid("https://api.example.com/forecast?city=lisbon", {
  signer,                       // classic keypair or a Soroban smart account
  maxAmount: "0.05",           // refuse anything above this
  facilitator: "https://x402.agentsmith.xyz"
});

if (!res.ok) console.error(res.headers.get("x402-reason"));

What happens underneath

  1. The request returns 402 Payment Required with the accepted payment requirements.
  2. The signer produces a Soroban auth entry permitting exactly that transfer — asset, amount, recipient.
  3. The request is retried with the signed payload. The seller calls /verify, then /settle.
  4. The facilitator submits the invocation and pays the network fee. You need no XLM.
Authorizations expire on ledgers, not on the clock. Validity is bounded by signatureExpirationLedger, derived from the seller's maxTimeoutSeconds — roughly 12 ledgers, about 60 seconds, by default. A slow retry loop lets an authorization lapse. Verify rejects an authorization with too few ledgers left to settle, with a distinct reason, so you re-sign rather than lose the payment mid-flight.

Metered calls

For services billed on usage rather than per call, the upto scheme authorizes a ceiling and settles the actual amount consumed.

The Stellar upto design is not final. upto has EVM and SVM specifications but no Stellar one yet. We are authoring scheme_upto_stellar.md and contributing it upstream; this section will document the mechanism once that specification lands. Until then, treat upto as announced but unspecified on Stellar.
Buyer & agent / Discover & pay over MCP

Discover and pay over MCP

With the MCP server configured, a model can go from an intent to a paid result without any resource-specific code. The tool call below is the whole integration.

tool calljson
{
  "name": "call_paid_resource",
  "arguments": {
    "query":     "structured line items from a PDF invoice",
    "input":     { "url": "https://files.example.com/inv-9912.pdf" },
    "maxAmount": "0.05"
  }
}

The server searches the catalog, applies the price ceiling, calls the best-ranked payable resource, handles the 402 and the signature, and returns the result together with the settlement hash. If nothing payable matches, it returns a typed reason rather than an empty answer.

Seller / Accept payments

Accept payments

Wrap a route, set a price, point at a facilitator. You do not touch Soroban RPC, auth-entry construction, or fee handling.

server.tstypescript
import { paywall } from "@agentsmith/x402/server";

app.use(paywall({
  facilitator: "https://x402.agentsmith.xyz",
  network:     "stellar:pubnet",
  payTo:       process.env.STELLAR_ADDRESS,
  routes: {
    "GET /forecast":  { price: "$0.002", asset: "USDC" },
    "POST /extract":  { price: "$0.020", asset: "USDC" }
  }
}));
Open a trustline before you list. A Stellar account cannot receive a SEP-41 asset until it holds a trustline for it. Without one, settlement fails after the buyer has signed. The facilitator checks the receiving side at verify time and marks unpayable resources in the catalog, but the fix is yours: open the trustline for every asset you price in.
Seller / Get discovered

Get discovered

There is no registration step. Declare discovery metadata alongside your price, and the resource is cataloged the first time a payment for it settles.

server.tstypescript
routes: {
  "GET /forecast": {
    price: "$0.002",
    asset: "USDC",
    discovery: {
      serviceName: "Meteo Forecast",
      description: "Hourly forecast for any city, 7 days ahead.",
      tags:        ["weather", "forecast", "geo"],
      mimeType:    "application/json",
      input: {
        city:  { type: "string", description: "City name or IATA code." },
        hours: { type: "integer", description: "Horizon, 1-168. Defaults to 24." }
      }
    }
  }
}
Write the parameter descriptions properly. They are the difference between an agent selecting your endpoint and skipping it. A parameter with no description is a parameter a model has to guess at, and models skip what they cannot fill in confidently. These strings are also what search ranks against.

Confirming the listing landed

The cataloging outcome comes back on the EXTENSION-RESPONSES response header as base64-encoded JSON. Decode it to find out what happened.

StatusMeaning
successMetadata validated against the schema and the resource is cataloged.
processingAccepted; cataloging is happening asynchronously.
rejectedValidation or another check failed. rejectedReason carries the explanation.
decoded EXTENSION-RESPONSESjson
{ "bazaar": { "status": "rejected",
             "rejectedReason": "routeTemplate contains a path traversal segment after percent-decoding" } }