You do not need an API to publish an OpenAPI document. You need a URL that returns JSON for something your pages already show, and a document that describes it. For most marketing sites that something is a pricing table, a product list, a set of locations with opening hours, or a documentation index, all of which are already public, already rendered, and already being scraped by any agent that wants them. Publishing the same data as a read-only endpoint with a spec is a bounded piece of engineering, it earns credit on check C4, and it gives an agent something more reliable than your HTML to work from.
What C4 actually looks for
Check C4 in the rubric is worth five points, is rated High effort, and accepts any of four surfaces: an agents.json, an MCP manifest, a published API documentation link, or a discoverable OpenAPI spec. One surface scores three points; two or more score the full five. The evidence block on your report carries a single field, found, listing which of the four the crawler saw. Why most sites score zero on machine endpoints covers the check as a whole; this post is about the OpenAPI surface specifically.
The way the crawler looks for that surface is worth knowing precisely, because it is literal. It fetches your homepage and tests the raw HTML, case-insensitively, for the string openapi.json, openapi.yaml or openapi.yml, or the swagger equivalents. It does not fetch the document, and it does not follow links to find it. A spec that exists at /api/openapi.json but is referenced nowhere on the homepage scores nothing; a footer link to it on every page scores. The documentation surface is tested the same way, with an href containing /docs, /doc or /api-docs, or a hostname beginning developer. or developers..
That is a discovery test, not a validity test, and it is deliberately so: the free scan fetches six pages and a fixed set of probes, and it reports only what it can prove. The reason to publish a good document rather than a token one is not the scan. It is that an agent which finds the reference will fetch and read the document, and what it reads decides whether it can use you.
The data you already render
Look at your site for anything that is a list or a table. Pricing tiers. Products, with a price and an availability. Store or clinic locations, with hours. Release notes. Documentation pages. Events, job listings, a FAQ. Each of these has a shape your templates already enforce, and each is something a person might ask an assistant about: "which plan includes SSO", "is the Bristol branch open on Sunday", "what changed in the last release".
If the data comes from a CMS or a database, a route that returns it as JSON is a thin function over the query your page already runs. If it is hard-coded in a template, it is a static file. Either way the work is choosing the fields, writing the descriptions and picking a URL, not building a product.
Keep it read-only: GET only, no key, no writes. The data is public already, so a key protects nothing and only adds a step the agent cannot complete. A document with no write operations has no abuse surface beyond what a browser already has. If you later want an agent to place an order or book a slot, that is a different design conversation, with authentication and consent in it; leave it out of the first version.
A minimal document
The example below is complete enough to serve. The full grammar is in the OpenAPI 3.1.0 specification; what matters most here is the prose.
{
"openapi": "3.1.0",
"info": {
"title": "Example Co API",
"version": "1.0.0",
"summary": "Plans and locations, read-only.",
"description": "The data shown on https://example.com/pricing and https://example.com/locations, as JSON. No key required. Human-readable documentation at https://example.com/docs."
},
"servers": [{ "url": "https://example.com/api/v1" }],
"paths": {
"/plans": {
"get": {
"operationId": "listPlans",
"summary": "Every plan with its monthly price",
"description": "Prices are in USD and match the pricing page. Use signupUrl to send a person to the right checkout.",
"responses": {
"200": {
"description": "The plans",
"content": {
"application/json": {
"schema": { "type": "array", "items": { "$ref": "#/components/schemas/Plan" } }
}
}
}
}
}
},
"/locations": {
"get": {
"operationId": "listLocations",
"summary": "Every branch with address and opening hours",
"responses": {
"200": {
"description": "The locations",
"content": {
"application/json": {
"schema": { "type": "array", "items": { "$ref": "#/components/schemas/Location" } }
}
}
}
}
}
}
},
"components": {
"schemas": {
"Plan": {
"type": "object",
"required": ["id", "name", "priceMonthlyUsd", "signupUrl"],
"properties": {
"id": { "type": "string", "example": "team" },
"name": { "type": "string" },
"priceMonthlyUsd": { "type": "number" },
"signupUrl": { "type": "string", "format": "uri" }
}
},
"Location": {
"type": "object",
"required": ["id", "name", "address", "hours"],
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"address": { "type": "string" },
"hours": { "type": "string", "description": "Plain text, e.g. Mon–Fri 09:00–17:30" }
}
}
}
}
}
Every summary and description in that document is doing the real work. An agent that has found your spec reads those strings to decide which operation answers the question it has been given, and no generator writes them for you. A framework can emit the paths and the schemas; the sentence that says "prices match the pricing page" is yours to write, and it is the sentence the agent needs.
How ours is written
Our own document is at /api/v1/openapi.json, and it is hand-written rather than generated, for exactly that reason. It describes an index plus five read-only endpoints: the rubric, the leaderboard, a per-site report, a two-site comparison, and the status of a scan in flight. The info.description tells a reader that the API needs no key and, because the one action with a cost is a scan of somebody else's site, explains that scans are requested through the form at /scan and then polled at /scans/{id}. The 404 description on /sites/{domain} says which of the three possible reasons a report might be absent. Errors are RFC 9457 problem details with a Problem schema and a detail field that says what to do.
The path list is unit-tested against the route files, so the document cannot advertise an endpoint that does not exist or omit one that does. That test is the cheapest insurance available against the most common failure of published specs, which is drift. /docs is the human-readable version of the same contract.
Making it discoverable
A document nobody links to is invisible, to the scanner and to everything else. Three references cover it.
First, a link in the homepage HTML. Ours is in the footer, labelled "OpenAPI spec", on every page, which is what the C4 pattern above matches. Second, an entry in agents.json with type: "openapi", the document URL, the base URL and an authentication field that says none; agents.json explained walks through ours line by line. Third, a line in llms.txt, so a program that reads only the text file still finds the machine surface. Our API index at /api/v1 also returns the spec, docs and MCP URLs as JSON, so an agent that lands there with no other context can find everything from one response.
Serve the document as application/json, cache it the way you cache a page, and move info.version whenever the shape changes.
What not to do
Do not publish paths that are not live yet; a spec that lies costs more trust than none. Do not add write operations without authentication and a reason. Do not generate the file and leave every description empty. Do not put it on a subdomain nothing links to. And do not treat three points as the goal: a documentation page linked from the homepage is the second surface, and with the spec that is the full five, but the value is in the agent that reads the document and gets a right answer out of you instead of a scraped one.
Check your own site
Run the free scan and open the C4 evidence; the found list shows which of the four surfaces the crawler saw on your homepage. The full definition of the check is in pillar C of the methodology, and the implementation service builds the endpoint and the document if you would rather not.