Home/Get started

Get started

From zero to a paid Algorand API through the GoPlausible x402 facilitator — and into the Global Challenge — in minutes.

// Part 1 · Use the facilitator & get discovered in the Bazaar
TestNet first — always

Develop and test your endpoints and merchant account on Algorand TestNet. When everything works, switch the same code to MainNet. The facilitator verifies and settles every payment for you — gasless for your buyers, USDC in, no payment infrastructure to run.

Facilitator https://facilitator.goplausible.xyz TestNet algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI= TestNet USDC asset: "10458941"
Need TestNet funds? ALGO from the TestNet bank, USDC from the Circle USDC faucet (select Algorand TestNet).
1 · Install
npm install express @x402/express @x402/avm @x402/core @x402/extensions
2 · Charge for an endpoint

One middleware, one route config. Buyers without payment get a standards-compliant HTTP 402; buyers with an x402 client pay USDC and get your response — verified and settled on-chain by this facilitator in a single round trip. Per the x402 exact scheme spec for Algorand, your price: "$0.01" resolves on the wire to the ASA id and base units — the 402 advertises asset: "10458941" and amount: "10000" (USDC, 6 decimals). The $ sign is understood natively — the middleware reads the dollar-money string and resolves it to the network's USDC for you. Optionally, pin the spec fields explicitly instead:

// OPTIONAL spec-exact alternative — pin the ASA id and base units instead of "$0.01":
price: { asset: "10458941", amount: "10000", extra: { name: "USDC", decimals: 6 } },
Notice there's no feePayer anywhere — gasless is automatic. The middleware learns the facilitator's fee-payer address from GET /supported and advertises it in your 402; the buyer's client builds it into the atomic group; the facilitator co-signs and pays the per-payment network fees. Buyers spend USDC only per payment — note their account still needs Algorand's standard minimum balance (0.1 ALGO base + 0.1 ALGO for the USDC opt-in, locked in the account, never spent on payments).
All samples read their config from the environment (load a .env with dotenv, node --env-file=.env or python-dotenv). Recommended names, same in TypeScript and Python — server: AVM_ADDRESS (your merchant receiving address), FACILITATOR_URL; client: AVM_PRIVATE_KEY (base64 64-byte key), RESOURCE_SERVER_URL (the paid endpoint's full URL).
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactAvmScheme } from "@x402/avm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
import { declareDiscoveryExtension } from "@x402/extensions/bazaar";

const ALGORAND_TESTNET = "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=";

const facilitator = new HTTPFacilitatorClient({
  url: process.env.FACILITATOR_URL || "https://facilitator.goplausible.xyz",
});

const server = new x402ResourceServer(facilitator)
  .register(ALGORAND_TESTNET, new ExactAvmScheme());

const app = express();
app.use(paymentMiddleware({
  "GET /my-api": {
    accepts: [{
      scheme: "exact",
      network: ALGORAND_TESTNET,
      price: "$0.01",                  // USDC — resolved per network
      payTo: process.env.AVM_ADDRESS!, // your merchant account (.env)
    }],
    description: "My paid API",
    // `bazaar` — REQUIRED for discovery: this is what gets you listed. Your
    // first settled payment then auto-catalogs the resource. Without it you
    // still get paid, but you never appear in /discovery.
    // `x402-merchant` — OPTIONAL identity: declare it to CONTROL your name,
    // website, logo and categories. Leave it out and they're read from your
    // endpoint's domain instead (OpenGraph tags, llms.txt, agent-card.json).
    extensions: {
      ...declareDiscoveryExtension({
        output: { example: { ok: true, premium: "data" } },
      }),
      "x402-merchant": {
        info: {
          name: "EXAMPLE API",                        // your public identity
          website: "https://my-api.example.com",
          logo: "https://my-api.example.com/logo.png",
          categories: ["api", "algorand", "x402"],
        },
        // x402 v2 spec: extensions carry BOTH info and a JSON Schema for it.
        schema: {
          $schema: "https://json-schema.org/draft/2020-12/schema",
          type: "object",
          required: ["name"],
          properties: {
            name: { type: "string" },
            website: { type: "string" },
            logo: { type: "string" },
            categories: { type: "array", items: { type: "string" } },
          },
        },
      },
    },
  },
}, server));

app.get("/my-api", (_req, res) => res.json({ ok: true, premium: "data" }));
app.listen(4021, () => console.log("x402-paid API on :4021"));
3 · Pay yourself once

Point an x402 client at your endpoint with a funded TestNet account:

// npm install @x402/fetch @x402/avm
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { toClientAvmSigner } from "@x402/avm";
import { ExactAvmScheme } from "@x402/avm/exact/client";

const ALGORAND_TESTNET = "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=";

const client = new x402Client().register(ALGORAND_TESTNET, new ExactAvmScheme(
  // AVM_PRIVATE_KEY: base64 64-byte key of a TestNet account with USDC (.env)
  toClientAvmSigner(process.env.AVM_PRIVATE_KEY!),
));

const paidFetch = wrapFetchWithPayment(fetch, client);
const res = await paidFetch(
  process.env.RESOURCE_SERVER_URL || "http://localhost:4021/my-api");
console.log(await res.json()); // paid, verified, settled — one call
Wallet as the signer (browser dApps)

Shipping a dApp where the buyer pays from their own wallet? Don't touch private keys at all — the ClientAvmSigner interface (address + signTransactions) is designed to match wallet libraries like @txnlab/use-wallet, so the connected wallet drops straight in:

// Building a browser dApp? Skip raw keys — the user's wallet plays the signer.
// ClientAvmSigner is compatible with @txnlab/use-wallet out of the box:
import { useWallet } from "@txnlab/use-wallet-react";

// Browser builds (e.g. Vite) need Node's Buffer shimmed ONCE at app entry,
// or payment creation fails with "Buffer is not defined":
//   npm install buffer   — then, first thing in your entry file:
//   import { Buffer } from "buffer";
//   if (!globalThis.Buffer) globalThis.Buffer = Buffer;

const { activeAddress, signTransactions } = useWallet();

const client = new x402Client().register(ALGORAND_TESTNET, new ExactAvmScheme({
  address: activeAddress!,   // the connected wallet (Pera, Lute, ...)
  signTransactions,          // the wallet signs — keys never leave it
}));
// ...then wrapFetchWithPayment(fetch, client) exactly as above

The client-web example wires this end-to-end — the WalletManager setup, a Lute connect UI, and the browser Buffer shim a Vite build needs.

Have a 25-word mnemonic instead?

Algorand wallets and tools hand you a 25-word mnemonic; x402 signers take AVM_PRIVATE_KEY — the same key base64-encoded as 64 bytes (32-byte seed + 32-byte public key). Convert once with this one-liner (needs only node + npm, installs nothing into your project) and put the output in your env — a valid key is exactly 88 characters:

AVM_MNEMONIC="your 25 words here" npx -y -p algosdk node -e 'const p=process.env.PATH.split(require("path").delimiter)[0].replace(/[\/\\]\.bin$/,"");const a=require(p+"/algosdk");console.log(Buffer.from(a.mnemonicToSecretKey(process.env.AVM_MNEMONIC).sk).toString("base64"))'

And the reverse — turn an AVM_PRIVATE_KEY back into its 25-word mnemonic (to import the account into Pera/Lute, or to round-trip-check your conversion — feeding the words back through the converter above returns the identical key):

AVM_PRIVATE_KEY="your-88-char-base64-key" npx -y -p algosdk node -e 'const p=process.env.PATH.split(require("path").delimiter)[0].replace(/[\/\\]\.bin$/,"");const a=require(p+"/algosdk");console.log(a.secretKeyToMnemonic(new Uint8Array(Buffer.from(process.env.AVM_PRIVATE_KEY,"base64"))))'
4 · You're in the Bazaar

That first successful payment auto-catalogs your endpoint into the Bazaar — no registration step, as long as your route config includes the bazaar extension from step 2 (that declaration is what the catalog reads; without it you still get paid but stay unlisted). Within a minute you'll appear in /discovery/resources, and the dashboard starts tracking your settles, success rate and volume.

Your merchant identity is optional. The x402-merchant extension is not required — declare it only if you want to control how you appear: name, website, logo and categories are then read straight from it. Leave it out and those are derived from your endpoint's domain metadata instead — OpenGraph tags, llms.txt or an agent-card.json on your site — which is how most listings get their name, logo and description today. Either way you're listed; the extension just takes the guesswork out (and is the only way to set your categories).

🩺 Check yourself — the x402 Doctor

Paste your endpoint and the Doctor probes its live 402 from the facilitator's side — 402-first behavior, v2 challenge shape, CORS headers, network and payTo sanity, plus the bazaar and x402-merchant extensions validated with the same gate the catalog uses. If a BAZAAR or HACKATHON badge isn't showing for your merchant, this tells you exactly why. Free, no payment involved, 20 checks per day.

5 · Go MainNet

Switch the network id and point AVM_ADDRESS at your MainNet merchant account — everything else stays identical:

MainNet algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8= MainNet USDC asset: "31566704"
Keep your MainNet merchant address stable. Discovery, dashboards, receipts and (soon) reputation all key on your receiving address — every rotation fragments your history and rankings across accounts. Test freely on TestNet; on MainNet, pick one address and stick with it.
// Part 2 · Join the Algorand Global x402 Challenge
Tag your payments

Add one field to your MainNet route config — the facilitator labels every settle with the Challenge tag (labeling only; never affects the payment):

accepts: [{
  scheme: "exact",
  network: ALGORAND_MAINNET,
  price: "$0.01",
  payTo: process.env.AVM_ADDRESS!, // now your MainNet merchant account (.env)
  extra: { tag: "x402-global-challenge" },   // ← the Challenge tag
}],

Tagged traffic shows up under SOURCE → X402-GLOBAL-CHALLENGE on the live dashboard — your settles, volume and success rate, visible to everyone including the judges.

Challenge checklist
1. Built and tested on TestNet — endpoint pays end-to-end.
2. Live on MainNet with a stable merchant address.
3. Routes tagged x402-global-challenge — visible on the dashboard.
4. Listed in the Bazaar with real settles (auto — see Part 1, step 4).
5. Every settle earns a shareable receipt: /api/receipt/<txId>.
Complete runnable examples

Every sample on this page as a standalone, runnable project — two servers and two clients per platform, wired to this facilitator on TestNet, config via .env:

Questions or feedback?

Stuck on an integration, found a rough edge, or have an idea — for the facilitator, the Bazaar or the Challenge? Drop it in the GoPlausible OpenBox — every submission is read.