Customers

Connect with MCP and the API

Two doors. Assistants read the catalog over MCP or REST. A product with a frontend lists client sites over OAuth 2.0 client credentials after the origin proves ownership. That is the Search Console / ads.txt standard for this space.

Your frontend lists client sites — today

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.

  1. 01

    Their visitor

    Client owner in your product. Clicks “List on BotCentral”.

  2. 02

    Your frontend

    Collects domain and consent. Never stores a BotCentral secret.

  3. 03

    Your backend

    OAuth token, mint verify token, wait for proof, PUT the card.

  4. 04

    BotCentral

    Checks DNS or well-known file. Writes the catalog. HMAC webhook back.

  1. 01

    Register the product

    Sign in on /publishers. Mint client_id + bc_pub_ secret. Put them in backend env.

  2. 02

    Mint a verify token

    POST /v1/publisher/verify-tokens. Show the client the DNS TXT or /.well-known/botcentral.txt body.

  3. 03

    Wait for proof

    POST /v1/probe until method is dns-txt or well-known-file. SPA HTML is rejected.

  4. 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.

  1. 01

    Key

    Sign in and mint a bc_live_ secret on API keys to read the catalog. Copy it once.

  2. 02

    Ceiling

    The key lifts you from 30 requests a minute to your plan RPM. Reads are never metered.

  3. 03

    Call

    Send Bearer or X-BotCentral-Key. 429 if you exceed the ceiling; nothing is billed.

Paid API (read)

API keys

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.

Header
Authorization: Bearer bc_live_…
Reads
Free · anonymous or keyed

MCP — assistants

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" }
    }
  }'

REST and OpenAPI

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"
Anonymous
30 reads / minute / client
API key
Plan RPM — free, never metered
Revoked key
401 — a revoked secret stops working
ETag
GET /v1/site/{domain} with If-None-Match

Who connects how

  • Developers reading the catalog → API keys, then top up on CiteFleet.
  • Claude, Cursor, VS Code, and other MCP hosts → POST /mcp with the key.
  • ChatGPT Actions, n8n, Zapier, custom bots → OpenAPI or REST.
  • A product that lists client sites → register at /publishers, then OAuth POST /oauth/token and PUT /v1/publisher/sites/{domain}.
  • Site owners without a publisher product → CiteFleet listing.
  • MCP and anonymous REST cannot write the catalog. A bc_live_ read key cannot either.