For developers

Read a business's own data in about five minutes.

Four commands, nothing installed, no account and no key. At the end you will have read one business's published records, asked it a question, and — the part that matters — told the difference between “the business published no” and “the business has not published anything about this”.

On this page

Status: draft specification, protocol_x_version 1.0. The wire format is written down at the specification and will change with notice, not silently. Nothing on this page needs an account.

Prefer a reference to a tutorial? /docs lists every endpoint. Want to check a source instead of read one? /validate.

Before you start

You need an HTTP client. curl is used below because it is already on your machine; anything that can make a request works, and nothing in this guide is specific to a language.

  • No account. Every document in this guide answers an unauthenticated request.
  • No API key. A key identifies you for rate limiting and is required only by a publisher who has closed anonymous reads — see Authentication.
  • Nothing to install. jq is used to keep the output short and is optional everywhere it appears.

Everything is over HTTPS. Plain http:// is refused rather than redirected, because a redirect from a plaintext request has already happened in plaintext.

Find a publisher

The platform publishes a plain-text index of every business on it, written for machines. Start there rather than guessing an address:

bash list published businesses
curl -s https://x-protocols.com/llms.txt
text response (abridged)
# X-Protocols

## How to read this

- Every business has a page at `/{country}/{city}/business/{publisher}`,
  and a machine index beneath it at `.../llms.txt`.
...

## Businesses (2)

- [Some Business](https://x-protocols.com/ba/sarajevo/business/some-business) — … (14 records)

The last path segment is the publisher slug. Every command below uses it, written as {publisher}. A business that has not said where it is yet lives at /b/{publisher} until it does, and that address keeps working afterwards — it answers 301 to the settled one, so follow redirects.

The list is paged. If the document ends with a More businesses: line, that URL is the next page; its absence is how you know you reached the end.

Read its manifest

The manifest is the one document you have to read before any other, because every other address comes out of it. Never compose those URLs yourself:

bash the publisher's manifest
curl -s https://x-protocols.com/t/{publisher}/.well-known/x-protocol/manifest.json | jq .
json response (abridged)
{
  "protocol_x_version": "1.0",
  "api_version": "2025-11-25",
  "registry": "x-protocol/v1",
  "publisher": { "slug": "{publisher}", "display_name": "…" },
  "capabilities": ["knowledge.search", "knowledge.get"],
  "endpoints": {
    "mcp":            "https://x-protocols.com/t/{publisher}/mcp",
    "public_profile": "https://x-protocols.com/…/business/{publisher}",
    "bulk_export":    "https://x-protocols.com/…/business/{publisher}/dump.jsonl",
    "jwks":           "https://x-protocols.com/t/{publisher}/.well-known/x-protocol/jwks.json",
    "revocations":   "https://x-protocols.com/t/{publisher}/.well-known/x-protocol/revocations.json"
  },
  "data_last_updated": "2026-09-11T09:12:00Z",
  "attestation": { "alg": "Ed25519", "kid": "…", "digest": "…", "signature": "…" }
}

The one trap on this page. capabilities are handler names, not tool names. Calling knowledge.search as if it were a tool fails, every time, on every publisher. Tool names come from tools/list and are chosen by the publisher — step 4 does exactly that.

Four version numbers travel separately and none of them implies another: protocol_x_version is this format, api_version is the MCP revision, server_version is our build, and a client library has a version of its own.

Read its records

Everything the business has published, one JSON object per line. This is the cheapest way to ingest a publisher whole — take the address from endpoints.bulk_export rather than building it:

bash bulk export, first two lines
curl -s https://x-protocols.com/ba/sarajevo/business/{publisher}/dump.jsonl | head -2
json line 1 is a header, not a record
{"_meta": { "publisher": {…}, "authority": { "evidence": [ { "method": "dns_txt",
   "observed_at": "…", "proves": "control of published content at a specific website … NOT ownership of the business" } ] },
   "license": "…", "generated_at": "…" }}
{"id": "SKU-123", "type": "product", "title": "…", "url": "https://…", "attributes": {…},
 "updated_at": "…", "verified_at": "…"}

Read the _meta line before the records. It carries who published them, what evidence stands behind that claim, and what that evidence does not prove — a sentence written into the payload rather than left to a documentation page nobody opens.

The export is not signed. The manifest and every tool response are. That difference is deliberate and is stated in the specification; a validator reports the export's integrity as not checked rather than as passed.

Ask it a question

Three JSON-RPC calls over one HTTP endpoint. Take the address from endpoints.mcp.

First, the handshake. Send the revision you speak; the server answers with the one it chose, which may be older than yours:

bash initialize
curl -s https://x-protocols.com/t/{publisher}/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-11-25",
                 "clientInfo":{"name":"quickstart","version":"1"},"capabilities":{}}}'

Then ask what this publisher offers. Tool names are the publisher's own — a bakery may call its search find_products. Never hardcode one:

bash tools/list
curl -s https://x-protocols.com/t/{publisher}/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | jq '.result.tools[].name'

Then call one. The argument names come from that tool's own inputSchema, which tools/list returned beside the name:

bash tools/call
curl -s https://x-protocols.com/t/{publisher}/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
       "params":{"name":"<the name you just read>","arguments":{"query":"opening hours"}}}'

A 401 here means this publisher has closed anonymous reads; everything else on this page still works without a key.

What the answer is allowed to mean

This is the part worth slowing down for. Every answer carries a coverage status, and the four are not shades of the same thing — they oblige you to do different things:

StatusWhat it meansWhat you may say
resolved The published records answer the question. The answer, attributed to the business, with its timestamp.
partial Part of the question is answered and part is not. Only the part that is answered. Say the rest is unanswered.
needs_context The question is ambiguous. The response carries the missing key and its legal values. Nothing yet. Ask the clarifying question; do not present the options as an answer.
not_covered This publisher has not published this fact. That the business has not published it. Not that the business does not offer it.

The distinction the whole format exists for. A business that has published “we do not perform laser eye surgery” and a business that has simply never written about it are two different answers, and collapsing them is how an assistant invents a fact. The first is a published statement with provenance. The second is an absence.

An empty result is not a negative answer about the world. It is a negative answer about this source.

Responses also carry agent_instruction, which is the publisher asking a consumer to behave a certain way — typically “do not guess the value, do not substitute general knowledge”. It is advisory. Nothing enforces it, it is not a security mechanism, and a consumer that ignores it is not violating the format. It is there so a well-behaved consumer knows what the publisher intended.

When it does not work

What you seeWhat it isFix
404 on a well-known path The slug is wrong, or that publisher is not published. Take the slug from /llms.txt, not from a business's name.
An HTML page where JSON was expected A page URL without the header. Send Accept: application/json, or use the document addresses from the manifest.
301 The publisher moved from /b/{publisher} to its settled address. Follow redirects (curl -L). Both addresses keep working.
401 unauthorized on /mcp This publisher has closed anonymous reads. Use a key from Developer access. The public documents still answer without one.
429 Rate limited. Honour Retry-After. It is a delay, not a refusal.
Tool not found A capability name was called as a tool name. Read names from tools/list. knowledge.search is a handler, not a tool.
Every signature fails Verifying over the message instead of over the digest. The signature is over the 32-byte SHA-256 digest of the canonical payload. See Verify a response.
Signatures fail after you parse and re-serialise Your JSON library reordered keys, escaped HTML or reformatted a number. Digest the bytes that arrived, not your re-encoding of them.

Where to go next

Reporting a problem, and the package. If a document here contradicts what a server actually returns, the server is the thing to report: say which URL, what you expected and what arrived, through the support form.

There is also a standalone reader and validator — one Go module, no dependencies outside the standard library, which runs the rules on this page from a terminal and against a local fixture publisher with no network at all. It is not published for download yet: this repository carries no licence, and calling something open source before a licence exists would be untrue. When that decision is made, this is where the link will be.