7Block Labs
Blockchain Development

ByAUJay

How to Use GET https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources in Your Dapp

Instant Description

Dive into the nitty-gritty of querying Coinbase’s x402 Bazaar discovery endpoint. We’ll walk you through pagination, parsing the response, and integrating the results into a production-level dapp that pays per request using x402. You can expect a ton of useful resources, including TypeScript, React, and backend examples, along with the latest best practices and operational guardrails. This guide is perfect for startup and enterprise decision-makers who are looking for solid, up-to-date implementation details.

Why this endpoint matters

The x402 protocol gives a new life to the usually dormant HTTP 402 status code by turning it into a payment handshake. This means your app (or AI agent) can programmatically purchase access to APIs and content.

To help you get started, there's a discovery endpoint available:
GET https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources
This endpoint shows you all the x402-enabled resources organized by the facilitator, making it easy for your dapp to find, filter, and launch paid calls as needed. Say goodbye to manual integrations! You can now dynamically add new paid features on the fly. Check out the full details in the documentation!

  • So, Discovery is actually a part of the official x402 v2 “Bazaar” extension. In this setup, servers tag their routes with JSON Schema, facilitators take in that metadata, and clients can then hit up a standard discovery endpoint to get what they need. Check it out here: (docs.cdp.coinbase.com)
  • If you're using CDP-hosted facilitators, you're in good hands! They’re production-ready and come with all the essential KYT/OFAC checks. Right now, they support Base and Solana, plus you have testnets available for those prototyping sessions. This is honestly the easiest way to launch at an enterprise level. More info can be found here: (docs.cdp.coinbase.com)

What the endpoint returns (and what it doesn’t)

  • Path: GET https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources
  • Query parameters:
    • type: string (filter by protocol, like "http")
    • limit: number (default changes; for CDP facilitator, it's 100)
    • offset: number (for pagination)
  • Response structure:
    • resources: an array of items, which include:
      • url: string (the resource URL you’ll hit with x402)
      • type: string ("http" as of now)
      • metadata: an object with discovery details, like description and input/output schemas (JSON Schema), so you can create forms or validate payloads
    • total, limit, offset: numbers to help with paging

Important Nuance

So here's the deal: the discovery payload gives you the scoop on calling an endpoint and what you'll get back. But when it comes to exact pricing and payment details, those are hashed out when you make the request through the x402 402-challenge. Just keep in mind, those details aren’t wrapped up in the discovery phase.

Make sure to design your UI in a way that shows pricing only after you get the first 402 response. You can check out more about this here.


Quick smoke test (no auth required)

The discovery list on the CDP-hosted facilitator can be accessed without any authentication. You can kick things off with a quick curl command to check if it’s up and running:

curl -s "https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources?type=http&limit=5" | jq .

You’ll notice a resources array along with pagination fields. The official documentation even has some code examples where the same call is made directly without authentication. When you’re working in production, it’s better to cache and paginate instead of polling on every single view. Check it out here: (docs.cdp.coinbase.com).


End-to-end architecture you can ship

Here's a tried-and-true method for integrating x402 discovery into your dapp with just a bit of extra code.

  • Buyer (client) flow:

    1. Start by discovering queries to find the services you might want to use, along with their input and output schemas.
    2. Allow the user or agent to pick a service, then build the parameters based on the schema you found.
    3. Make the call to the service using x402-enabled fetch; handle any 402 errors, sign, pay, and automatically retry on success.
  • Seller (provider) flow (for your own endpoints):

    • Just register the Bazaar extension in your server middleware, outline your input and output JSON schemas, and your endpoints will automatically index as the facilitator takes care of payments. Check out the details here: docs.cdp.coinbase.com

TypeScript: strongly-typed discovery client

Create Types for Documented Shape

To ensure our code aligns nicely with the documented shape, let’s create some types. This not only makes our code cleaner but also keeps us on track as things evolve.

Add Defensive Parsing

We don’t want our application to break with every little change, right? So, it’s smart to add some defensive parsing. This way, if there are any future tweaks to the schema, we’ll be ready for them.

Here's a quick overview of what we’ll do:

  • Define types: These types should accurately reflect the structure we’re working with.
  • Implement defensive parsing: This involves checking for any changes or additional fields that might pop up down the road.

By doing this, we can keep our application stable and robust against unforeseen changes. Let’s dive in!

// types/x402-bazaar.ts
export type X402Resource = {
  url: string;
  type: "http" | string; // future-proof; docs show "http" today
  metadata?: {
    description?: string;
    input?: unknown;  // JSON Schema fragment
    output?: unknown; // JSON Schema fragment
  };
};

export type X402DiscoveryResponse = {
  resources: X402Resource[];
  total: number;
  limit: number;
  offset: number;
};

// lib/discovery.ts
const BASE = "https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources";

export async function listX402Resources(params: {
  type?: string;
  limit?: number;
  offset?: number;
} = {}): Promise<X402DiscoveryResponse> {
  const qp = new URLSearchParams();
  if (params.type) qp.set("type", params.type);
  if (typeof params.limit === "number") qp.set("limit", String(params.limit));
  if (typeof params.offset === "number") qp.set("offset", String(params.offset));

  const res = await fetch(`${BASE}?${qp.toString()}`, {
    // no auth currently required for listing
    headers: { "Accept": "application/json" },
  });
  if (!res.ok) {
    throw new Error(`Discovery failed: ${res.status} ${res.statusText}`);
  }
  const json = (await res.json()) as X402DiscoveryResponse;
  // Basic guards
  if (!Array.isArray(json.resources)) throw new Error("Malformed discovery response");
  return json;
}
  • According to the official docs, type, limit, and offset are the query parameters. They also provide details about the CDP endpoint and the default limit. You can check it out here: (docs.cdp.coinbase.com).

Pagination and caching pattern

Avoid calling discovery every time a page loads. Instead, cache those results for several minutes rather than just a few seconds, and make sure to consider pagination when dealing with large catalogs.

// lib/discovery-cache.ts
const TTL_MS = 5 * 60 * 1000;
let cache:
  | { at: number; key: string; data: X402DiscoveryResponse }
  | null = null;

export async function getCachedDiscovery(opts = { type: "http", limit: 100, offset: 0 }) {
  const key = JSON.stringify(opts);
  if (cache && cache.key === key && Date.now() - cache.at < TTL_MS) {
    return cache.data;
  }
  const data = await listX402Resources(opts);
  cache = { at: Date.now(), key, data };
  return data;
}
  • The CDP-hosted endpoint kicks things off with a default of 100 results. So, if you're building pagination UIs, make sure to set up that “Load more” feature with offset += limit. Check out the details in the documentation.

React: render discoverable services and pay on click

Generate a catalog UI using discovery and connect it to the x402 payment helpers. With Coinbase’s SDKs, you can take advantage of “fetchWithPayment,” which makes the 402 handshake, signing, and retry process a breeze. Check out the details here: (docs.cdp.coinbase.com).

// components/ServiceGallery.tsx
import React, { useEffect, useState } from "react";
import { getCachedDiscovery } from "../lib/discovery-cache";
// Option A: CDP SDK (works with Embedded Wallets)
import { fetchWithX402 } from "@coinbase/cdp-core"; // see docs for setup
// Option B: Vanilla x402 client libs exist as well (see x402 repo)

export const ServiceGallery: React.FC = () => {
  const [items, setItems] = useState([]);
  const [busy, setBusy] = useState<string | null>(null);
  useEffect(() => {
    getCachedDiscovery({ type: "http", limit: 100, offset: 0 })
      .then(r => setItems(r.resources as any[]))
      .catch(console.error);
  }, []);

  async function invoke(url: string) {
    try {
      setBusy(url);
      // This wrapped fetch will handle HTTP 402 payment flows under the hood
      const wrapped = fetchWithX402(); // create per session as needed
      const resp = await wrapped(url, { method: "GET" });
      const data = await resp.json();
      console.log("Paid response", data);
      alert("Success! Open console for data.");
    } catch (e) {
      console.error(e);
      alert("Payment or fetch failed");
    } finally {
      setBusy(null);
    }
  }

  return (
    <div className="grid">
      {items.map((r: any) => (
        <div key={r.url} className="card">
          <h3>{r.metadata?.description ?? r.url}</h3>
          <p>Type: {r.type}</p>
          {/* if metadata has input schema, you could render a form dynamically */}
          <button onClick={() => invoke(r.url)} disabled={busy === r.url}>
            {busy === r.url ? "Paying..." : "Pay & Call"}
          </button>
        </div>
      ))}
    </div>
  );
};
  • The CDP docs offer “fetchWithX402” and “fetchWithPayment” to help buyers navigate the x402 flow effortlessly. Check it out here: (docs.cdp.coinbase.com)

Seller-side: make your own endpoints discoverable (v2)

If you're looking to make some money from your APIs, getting your routes auto-indexed by the Bazaar is a smart move. Just register the Bazaar extension and set up your input/output schemas. It’s really that simple--no need for any custom indexing jobs. Check out the details here: (docs.cdp.coinbase.com)

Node/Express Example:

Let's get started with a simple Node.js and Express application. This example will give you a basic overview of how to set things up and get a server running.

Setting Up the Project

First off, you’ll want to create a new directory for your project. You can name it whatever you like. Open up your terminal and run:

mkdir my-express-app
cd my-express-app

Next, you’ll need to initialize a new Node.js project. Just run:

npm init -y

This command creates a package.json file with default settings. It's like a blueprint for your project.

Installing Express

Now, let’s install Express, which is a minimal and flexible Node.js web application framework. You can do this by running:

npm install express

Creating the Server

With Express installed, let’s create your server. Create a new file named app.js in your project directory:

touch app.js

Open app.js in your favorite text editor and add the following code:

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello, World!');
});

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

Here’s a quick breakdown of what’s happening:

  • We’re importing the Express module.
  • We create an instance of an Express app.
  • We're defining a route that listens for GET requests to the root URL ('/').
  • When that route is hit, it sends back a simple "Hello, World!" message.
  • Finally, we tell the app to listen on a specified port.

Running the Server

To get your server up and running, go back to your terminal and enter:

node app.js

You should see a message that says:

Server is running on http://localhost:3000

Now, if you open your web browser and type in http://localhost:3000, you should see "Hello, World!" displayed on your screen.

Conclusion

And that’s it! You've set up a basic Express server in no time. From here, you can start exploring more complex routes, middleware, and additional features that Express has to offer. Happy coding!

If you want to dive deeper into Express, check out the official documentation.

import express from "express";
import { paymentMiddleware } from "@x402/express";
import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server";
import { registerExactEvmScheme } from "@x402/evm/exact/server";
import {
  bazaarResourceServerExtension,
  declareDiscoveryExtension,
} from "@x402/extensions/bazaar";

const app = express();
const facilitatorClient = new HTTPFacilitatorClient({
  url: "https://x402.org/facilitator",
});

const server = new x402ResourceServer(facilitatorClient);
registerExactEvmScheme(server);
server.registerExtension(bazaarResourceServerExtension);

// One paid route with discovery metadata
app.use(
  paymentMiddleware(
    {
      "GET /weather": {
        accepts: { scheme: "exact", price: "$0.001", network: "eip155:84532", payTo: "0x..." },
        extensions: {
          ...declareDiscoveryExtension({
            output: {
              example: { temperature: 72, conditions: "sunny" },
              schema: {
                properties: {
                  temperature: { type: "number" },
                  conditions: { type: "string" },
                },
                required: ["temperature", "conditions"],
              },
            },
          }),
        },
      },
    },
    server
  )
);

app.get("/weather", (_, res) => res.json({ temperature: 72, conditions: "sunny" }));
app.listen(4021);
  • The documentation now features v2's extension-based declarations, which include JSON Schema validation, taking the place of the older v1 pattern. It's best to stick with v2 from here on out. (docs.cdp.coinbase.com)

Network and token realities you need to plan for

  • The CDP-hosted facilitator currently supports Base and Solana, including Base Sepolia and Solana Devnet. This setup is all set for production and includes KYT/OFAC checks. If you need access to other networks or prefer private infrastructure, you can go for the self-hosted option. Check out more details here.
  • x402 is pretty flexible when it comes to networks; it’s designed to be network-agnostic, while facilitators handle the specifics of verification and settlement. To streamline things, make sure your buyer can pre-check which network they can pay on, and try to select services that align with that. For further info, visit this link.
  • In 2025, we rolled out updates that broadened Solana support across x402 specs and improved facilitator routing. Don’t forget to keep your SDKs and facilitator versions up to date! Check out what’s new here.

Production-grade filtering strategy

Use the Discovery Metadata to Cut Down on Failed 402 Handshakes and "Wrong-Network" Retries

By leveraging the discovery metadata, you can significantly minimize those pesky failed 402 handshakes and retries that happen when you're on the wrong network. Here’s how to make the most of it:

Tips to Optimize Handshakes

  1. Understand Discovery Metadata: This is your secret weapon! It helps devices identify each other efficiently. Make sure you’re using the right metadata for your device.
  2. Streamline Configuration: Ensure your network settings are aligned with the discovery information. Misconfigurations can lead to those annoying handshake failures.
  3. Monitor Network Conditions: Stay updated on your network’s status. An unstable connection can cause retrials and dropouts.
  4. Use Correct Protocols: Double-check that your devices are using the right communication protocols outlined in the metadata. This can make a world of difference!
  5. Log and Analyze Failures: Keep track of any failed attempts. Analyzing these logs can help you spot patterns or recurring issues that need addressing.

Conclusion

By incorporating these strategies and paying attention to your discovery metadata, you can drastically reduce the number of failed handshakes and minimize those frustrating "wrong-network" retries. It's all about making the right connections!

  • For now, let’s filter by type=http (we might see other types pop up later, so let’s keep our type checks flexible).
  • Check out metadata.output to figure out if it’s JSON or something else, and direct it to the appropriate client.
  • Keep a short allowlist of networks/tokens that your users can use for payments (like USDC on Base or Solana), and rearrange the catalog to showcase those first.
  • Implement caching per query (using type, limit, and offset), and include a “refresh” option that lets users invalidate the cache whenever they need to.

All these patterns match up with the documented shape and the v2 extension architecture that handles input/output JSON Schemas. You can check it out for more details at (docs.cdp.coinbase.com).


Payments: how calls actually get paid

When you hit up a discovered endpoint without making a payment, you’re going to get back an HTTP 402 response that includes payment instructions. A client that’s compliant with x402 will:

  1. Take a look at the 402 response and pull out the details like amount, asset, network, and payTo.
  2. Build and sign a payment transaction based on that info.
  3. Go ahead and submit it through the facilitator for settlement.
  4. Once everything's settled, give the original request another shot.

With CDP hooks or core SDKs, you don’t have to worry about handling this sequence yourself. It’s honestly the quickest way to get your web and mobile surfaces up and running. Check out the details here.


Backend: scheduled ingest + feature flags

For businesses, we suggest setting up a backend layer that regularly pulls in discovery listings and shares them with your clients through your own API:

// worker/ingest.ts
import { listX402Resources } from "../lib/discovery";
import { upsertMany } from "../db/catalog";

async function run() {
  let offset = 0;
  const limit = 100;
  while (true) {
    const page = await listX402Resources({ type: "http", limit, offset });
    await upsertMany(page.resources); // normalize & index by url
    offset += page.resources.length;
    if (page.resources.length < limit) break;
  }
}
  • Roll out new resources to specific groups of users and keep an eye on how they're converting and any error rates.
  • Add compliance notes for each resource, like internal allow/deny rules based on network or token.

Observability and controls

  • Make sure to log both the discovery resource URL and the paid invocation URL when you get a chance.
  • Keep track of the 402 challenge fields (like the amount and network) along with the transaction hash so you can reconcile everything later.
  • Set up alerts for situations like “402 received but no settlement” or “payment settled but service never returned 2xx.”
  • If you're working with CDP-hosted routes, you can take advantage of the built-in KYT/OFAC screening and enjoy fee-free USDC settlement in production regions. Just remember to still monitor your own business logic outcomes. (docs.cdp.coinbase.com)

Security and compliance posture

  • Make sure not to reveal any server secrets during your discovery calls--they're not necessary.
  • If you're working with other CDP REST endpoints, stick to those JWT-based auth patterns and keep those Secret API Keys on the server-side only. While discovery itself doesn’t require authentication, don’t assume that applies to all other CDP APIs. Check out the details here: (docs.cdp.coinbase.com).
  • Present only the networks and tokens that your compliance program approves. CDP-hosted options give you a nice head start, but remember, you’re still accountable for your app’s policies. For more info, take a look at this: (docs.cdp.coinbase.com).

Edge cases and hardening

  • Empty results: If there are no results, let’s handle those zero-length pages nicely. Just show a friendly “no services found” message along with a retry option.
  • Schema drift: Since we're dealing with JSON Schema for metadata, let’s keep it flexible! We should favor feature detection over sticking to strict schema rules. And don’t forget to update the display whenever we spot new fields. (docs.cdp.coinbase.com)
  • Timeouts: Let’s treat discovery like fetching from a catalog. If it times out, we should display the cached results and throw up a banner to inform users.
  • Client wallets: Embedded Wallets + x402 deliver the best buyer experience on both web and mobile. Make sure to double-check that sign-in and account readiness are all set before processing any payments to steer clear of those pesky 402 loops. (docs.cdp.coinbase.com)

Practical checklist (copy/paste)

  • Discovery

    • Start by querying GET /platform/v2/x402/discovery/resources with type=http and limit it to 100.
    • Don’t forget to paginate using offset; and it’s a good idea to cache those pages for about 5-15 minutes.
    • When rendering cards, use metadata.description; and if you have JSON-Schema-driven forms available, attach those too. (docs.cdp.coinbase.com)
  • Invocation

    • Use a payment-enabled fetch (think CDP SDK) so that the 402 → pay → retry process is smooth and automatic. (docs.cdp.coinbase.com)
    • It's best to stick with resources that align with your supported networks/tokens. (docs.cdp.coinbase.com)
  • Ops

    • Make sure to log that 402 challenge along with the settlement transaction, and set up alerts for any incomplete flows.
    • It’s a smart move to run a nightly ingest to pre-index and feature-flag any new resources that pop up.
  • Governance

    • Keep those Secret API Keys stored server-side for any other CDP REST calls; remember that discovery stays unauthenticated. (docs.cdp.coinbase.com)
    • Stay on top of network support as Coinbase continues to grow (like the Solana support changes that are expected to roll out in late 2025). (docs.cdp.coinbase.com)

Advanced: programmatic discovery with the x402 client extensions

You can also access discovery using the x402 client extension APIs, instead of going through raw HTTP. The documentation features a “withBazaar” wrapper around a facilitator client that offers handy helper methods such as extensions.discovery.listResources. This is super useful if you’re already working with x402 SDKs and want to maintain consistent configuration for facilitator URLs along with typed helpers. Check it out in the docs!

import { HTTPFacilitatorClient } from "@x402/core/http";
import { withBazaar } from "@x402/extensions/bazaar";

const facilitator = withBazaar(
  new HTTPFacilitatorClient({ url: "https://x402.org/facilitator" })
);

const result = await facilitator.extensions.discovery.listResources({
  type: "http",
  limit: 50,
  offset: 0,
});

console.log(result.resources.length, "services discovered");

Putting it all together: a minimal buyer journey

  1. The catalog view loads up: your backend quickly serves up a cached slice of discovery.
  2. The user clicks on the “Weather API” tile. Your client then displays a small form based on the input schema (city, units).
  3. When the user hits “Pay & Call,” your x402-aware fetch goes like this:
    • It sends a GET request → the server replies with a 402 status and payment details.
    • The client then signs and pays (for example, using USDC on Base) through the embedded wallet.
    • The SDK tries again and sends back a 200 status with a JSON body; your UI then shows the result. (docs.cdp.coinbase.com)

Common pitfalls we see (and how to avoid them)

  • When you're checking out the discovery lists for prices, keep in mind that it doesn’t guarantee what you'll pay. Think of discovery more as a way to find capabilities or schema info; the actual pricing comes into play with the 402 challenge. Check it out here: (docs.cdp.coinbase.com).
  • Don’t forget about network specifics: sometimes, an endpoint might only work with Solana or Base. So, make sure to filter or reorder things to match what your wallet can handle. More info can be found here: (docs.cdp.coinbase.com).
  • If you're still working with v1 assumptions, it’s time to upgrade your thinking! v2 has introduced formal extensions and JSON Schema. Just double-check that your server libraries and examples are all pointing to v2. Get the details here: (docs.cdp.coinbase.com).

Where to go next

  • Get familiar with the x402 payment handshake and 402 semantics to create a smoother UX and better error handling. Check it out here.
  • If you’re setting up your own endpoints, don’t forget to hook up the Bazaar extension so your services can be found right from the start. More info here.
  • Keep an eye on network support and facilitator updates since Coinbase is constantly rolling out new features and expanding to new areas. You can find the details here.
  • Dive into the open-source x402 repository for some server/client patterns, including Express, Go, and more. It's all waiting for you here.

TL;DR for decision-makers

  • By using a simple GET request to https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources, your dapp can easily find x402-enabled APIs, create forms from JSON Schema, and handle payments through embedded wallets--all without the hassle of custom onboarding or messing around with API keys. Check it out in the documentation!
  • With CDP-hosted facilitators, you get production-ready features like KYT and OFAC compliance. Right now, it supports both Base and Solana, and it’s only going to grow. This means your team can move faster and deal with fewer complications. Dive into the details here!

7Block Labs is here to help you get this architecture up and running from start to finish. We cover everything from user-friendly design and wallet integrations to monitoring and compliance checks. That way, you can easily monetize or use paid APIs without any hassle.

Like what you're reading? Let's build together.

Get a free 30-minute consultation with our engineering team.

7BlockLabs

Full-stack blockchain product studio: DeFi, dApps, audits, integrations.

7Block Labs is a trading name of JAYANTH TECHNOLOGIES LIMITED.

Registered in England and Wales (Company No. 16589283).

Registered Office address: Office 13536, 182-184 High Street North, East Ham, London, E6 2JA.

© 2026 7BlockLabs. All rights reserved.