AI agents have a boring problem: they can't sign up for things. Every
interesting API sits behind an email verification, a credit-card form and an
API key — three things a headless agent can't do. I ran into this building my
own agents, so I ended up building an API the other way round, and this is the
20-line version of what it makes possible.
We'll build a working KYB (Know Your Business) agent in ~20 lines of
TypeScript. It verifies any French company — identity, officers, insolvency
alerts, filed financials, sanctions screening — and it pays for what it uses,
per call, in USDC. No account anywhere.
The building blocks
- x402 — an open protocol that revives HTTP 402: the server answers a machine-readable payment quote; the client signs a stablecoin authorization and retries. One round-trip, no gas for the client.
- Sirenic — a pay-per-call API over official French/European company registers (INSEE/INPI open data, Etalab 2.0 license). Prices from $0.001. Disclosure: I built it.
Step 1 — look at a quote (no wallet yet)
curl -i "https://api.sirenic.eu/v1/kyb/552032534"
You get HTTP 402 Payment Required. The PAYMENT-REQUIRED header is
base64 JSON: price ($0.15 → 150000 atomic USDC units), the receiving
address, the official USDC contract and the network (Base). Nothing was
charged — quotes are free.
Step 2 — a wallet for your agent
Create a throwaway wallet (MetaMask works), export the private key, and
fund it with a couple of dollars of USDC on Base (any exchange can
withdraw to Base). The exact payment scheme uses signed authorizations,
so the client never pays gas — USDC is all the wallet needs. The whole
tutorial costs $0.15.
npm install @x402/fetch @x402/core @x402/evm viem tsx
export TEST_WALLET_KEY=0x...
Step 3 — the agent
import { privateKeyToAccount } from "viem/accounts";
import { wrapFetchWithPayment } from "@x402/fetch";
import { x402Client } from "@x402/core/client";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
const siren = process.argv[2] ?? "552032534";
const client = new x402Client();
registerExactEvmScheme(client, {
signer: privateKeyToAccount(process.env.TEST_WALLET_KEY as `0x${string}`),
});
const payingFetch = wrapFetchWithPayment(fetch, client);
const kyb = await (await payingFetch(
`https://api.sirenic.eu/v1/kyb/${siren}`,
)).json() as Record<string, any>;
console.log(`Company : ${kyb.identite.denomination} (${kyb.siren}) — ${kyb.identite.etat_administratif}`);
console.log(`Officers: ${kyb.identite.dirigeants?.length ?? 0} | VAT: ${kyb.tva_intracommunautaire}`);
console.log(`Alerts : ${kyb.alertes_bodacc.procedures_collectives?.length ?? "n/a"} insolvency proceedings`);
console.log(`Sanctions screening: ${kyb.criblage_sanctions.statut}`);
console.log(`Completeness: ${kyb.score_completude}/100`);
Run it:
$ npx tsx kyb-agent.ts 552032534
Company : DANONE (552032534) — actif
Officers: 13 | VAT: FR27552032534
Alerts : 0 insolvency proceedings
Sanctions screening: correspondances_a_verifier
Completeness: 100/100
That's the whole agent. wrapFetchWithPayment intercepted the 402, signed
a $0.15 USDC authorization, retried, and the API released the response once
the settlement was confirmed on-chain.
What actually happened in that one call
The /v1/kyb/{siren} endpoint consolidates six sources server-side:
registry identity and officers, BODACC legal announcements, filed accounts,
and a fuzzy sanctions screening of the company name and every officer
against the five official lists (UN, EU, OFAC, UK, French asset-freeze
register). Matches come back with a 0-100 confidence score and the matched
alias — never a bare yes/no, because "Emmanuel Macron vs. Emmanuel Sultani
Makenga" is exactly the kind of thing you want a human to look at.
Two production details worth stealing for your own paid APIs:
- Never charge for errors. If the company doesn't exist (404) or a source fails (503), the verified payment is cancelled — the agent pays nothing. Agents can't file support tickets; your error semantics are your support team.
- A completeness score beats silent gaps. If one upstream source is down, the response says which block is missing and why, and the score drops. The agent can decide whether 80/100 is good enough.
MCP, if you'd rather not write code
The same API is an MCP server — Claude and Cursor can use it directly:
claude mcp add --transport http sirenic https://api.sirenic.eu/mcp
Each tool takes an optional x_payment parameter with the same quote/sign/
retry loop.
Links
- Examples repo (this code + more): https://github.com/sirenic-eu/sirenic-examples
- OpenAPI: https://api.sirenic.eu/openapi.json · llms.txt: https://api.sirenic.eu/llms.txt
Data is official open data redistributed as published; responses carry
source and disclaimer fields. Screening output is a decision aid, not a
compliance opinion.
Top comments (0)