Customers
The browser never talks to BotCentral with a publisher secret. Your UI talks to your backend. Your backend holds client_id / client_secret, proves the client domain, then PUTs the 1.1 card. We POST a signed webhook when it lands.
01
Their visitor
Client owner in your product. Clicks “List on BotCentral”.
02
Your frontend
Collects domain and consent. Never stores a BotCentral secret.
03
Your backend
OAuth token, mint verify token, wait for proof, PUT the card.
04
BotCentral
Checks DNS or well-known file. Writes the catalog. HMAC webhook back.
01
Register the product
Sign in on /publishers. Mint client_id + bc_pub_ secret. Put them in backend env.
02
Mint a verify token
POST /v1/publisher/verify-tokens. Show the client the DNS TXT or /.well-known/botcentral.txt body.
03
Wait for proof
POST /v1/probe until method is dns-txt or well-known-file. SPA HTML is rejected.
04
PUT, then listen
PUT /v1/publisher/sites/{domain} with Bearer. 409 if unproven. Webhook site.listed, HMAC-SHA256.
RFC 6749 — OAuth 2.0 client credentials
curl -sS https://botcentral.org/oauth/token \ -H 'content-type: application/x-www-form-urlencoded' \ -d 'grant_type=client_credentials' \ -d 'client_id=bc_cid_YOUR_CLIENT' \ -d 'client_secret=bc_pub_YOUR_SECRET' \ -d 'scope=publisher'
Mint the domain-control token the client publishes
curl -sS -X POST https://botcentral.org/v1/publisher/verify-tokens \
-H "authorization: Bearer $ACCESS" \
-H 'content-type: application/json' \
-d '{"domain":"client.example"}'PUT the 1.1 card (server to server)
curl -sS -X PUT https://botcentral.org/v1/publisher/sites/client.example \
-H "authorization: Bearer $ACCESS" \
-H "content-type: application/json" \
-d '{"domain":"client.example","canonical":"https://client.example/","name":"Client","summary":"What the site is.","topics":["example"],"allow_bots":true,"allow":["ChatGPT-User"],"deny":["GPTBot"],"pointers":{"robots":"https://client.example/robots.txt","sitemap":"https://client.example/sitemap.xml","llms":"https://client.example/llms.txt"},"pages":[{"url":"https://client.example/","rel":"home"}],"verifyToken":"bc-verify-acme-ab12cd34","consent":{"retrieve":true,"train":false,"act":false,"cite":true,"tdm":"reserved"}}'Your backend. Frontend calls you, not BotCentral.
// YOUR backend. The customer's browser never holds these.
const ORIGIN = "https://botcentral.org";
async function accessToken() {
const res = await fetch(ORIGIN + "/oauth/token", {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.BOTCENTRAL_CLIENT_ID,
client_secret: process.env.BOTCENTRAL_CLIENT_SECRET,
scope: "publisher",
}),
});
const json = await res.json();
if (!res.ok) throw new Error(json.error_description || json.error);
return json.access_token;
}
export async function listClientOnBotCentral(card) {
const access = await accessToken();
const res = await fetch(ORIGIN + "/v1/publisher/sites/" + card.domain, {
method: "PUT",
headers: {
authorization: "Bearer " + access,
"content-type": "application/json",
},
body: JSON.stringify(card),
});
if (res.status === 409) throw new Error("Origin not proven yet");
if (!res.ok) throw new Error(await res.text());
return res.json();
}Webhook your backend receives
X-BotCentral-Event: site.listed
X-BotCentral-Signature: sha256=<hmac of raw body>
{
"botcentral": "1.1",
"event": "site.listed",
"publisher": "acme",
"domain": "client.example",
"href": "/v1/site/client.example"
}Discovery: /.well-known/oauth-authorization-server (RFC 8414) · /.well-known/oauth-protected-resource (RFC 9728) · directory GET /v1/publishers (ads.txt / sellers.json). A long-lived bc_pub_ Bearer also works, same as a Stripe secret key.
01
Key
Sign in and mint a bc_live_ secret on API keys to read the catalog. Copy it once.
02
Ceiling
The key lifts you from 30 requests a minute to your plan RPM. Reads are never metered.
03
Call
Send Bearer or X-BotCentral-Key. 429 if you exceed the ceiling; nothing is billed.
Paid API (read)
Mint a secret here to raise your rate ceiling. Reading the catalog is free with or without one — a first hop that costs money is a first hop agents skip. A read key cannot list a site.
Streamable HTTP at https://botcentral.org/mcp. Tools: search_catalog, get_listed_site, score_url, list_topics, list_agents, list_publishers. No session. Publish is not on this socket.
Claude Desktop — claude_desktop_config.json
{
"mcpServers": {
"botcentral": {
"url": "https://botcentral.org/mcp"
}
}
}Cursor — mcp.json
{
"mcpServers": {
"botcentral": {
"url": "https://botcentral.org/mcp"
}
}
}VS Code — mcp.json
{
"servers": {
"botcentral": {
"type": "http",
"url": "https://botcentral.org/mcp"
}
}
}API key header (mint on /keys, top up on CiteFleet)
{
"mcpServers": {
"botcentral": {
"url": "https://botcentral.org/mcp",
"headers": {
"Authorization": "Bearer bc_live_YOUR_KEY"
}
}
}
}Initialize (any HTTP client)
curl -sS https://botcentral.org/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": { "name": "demo", "version": "1" }
}
}'Call search_catalog
curl -sS https://botcentral.org/mcp \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "search_catalog",
"arguments": { "q": "verified dating", "consent": "retrieve" }
}
}'Same catalog, no MCP client required. ChatGPT Actions and other OpenAPI importers use GET /v1/openapi.json. CORS is open on /v1.
Public REST
curl -sS "https://botcentral.org/v1/search?q=verified+dating&consent=retrieve&include=urls" curl -sS "https://botcentral.org/v1/site/resonanse.app" curl -sS "https://botcentral.org/v1/score?url=https://resonanse.app/" curl -sS "https://botcentral.org/v1/openapi.json"
API key header
curl -sS "https://botcentral.org/v1/search?q=dating" \ -H "Authorization: Bearer bc_live_YOUR_KEY"
Who connects how
POST /oauth/token and PUT /v1/publisher/sites/{domain}.