§The Agent eXperience Protocol (AXP)
Publisher: apis.ax (the standards body) · Verifier: api.qa (independent)
This document is the normative text of AXP — the Agent eXperience Protocol: the set of technical surfaces an agent-first API MUST enforce — the wire contract it publishes so that an autonomous agent can discover it, understand it, and transact with it zero-shot, on first contact, with no human reading docs in the loop. apis.ax publishes this standard; api.qa verifies conformance to it from outside the building fleet's write access.
The key words MUST, MUST NOT, SHOULD, and MAY are to be interpreted as described in RFC 2119.
This specification is itself linked data: its $id
(https://apis.ax/axp) is the resolver-addressable identity of the
standard, $type: Spec, in the https://schema.org.ai context. Every clause
below names (a) the normative requirement, (b) how it is machine-verified
by an independent api.qa check or pinned requirement, and (c) why an agent
needs it.
§1OpenAPI 3.1 contract
Normative. A conforming API MUST publish a valid OpenAPI 3.1
document describing every operation an agent may call, its parameters, and its
typed responses. The document MUST parse and MUST declare at least one
operation. The contract SHOULD be discoverable at /openapi.json or linked
from the capability card.
Machine-verified by: api.qa check openapi and pinned requirement
openapi-valid (surface openapi, must: valid, pinned with
versionPrefix: "3.1" and minOperations: 1). api.qa fetches the document,
parses it, and confirms it is structurally valid, that its declared spec
version begins 3.1 (a Swagger 2.0 or 3.0.x document fails), and that it
declares at least one operation (an empty paths object fails).
Why an agent needs it: the OpenAPI 3.1 contract is the machine-readable shape of the API. Without it an agent must guess call signatures; with it the agent binds to the surface deterministically and calls correctly on the first try.
§2llms.txt
Normative. A conforming API MUST serve an llms.txt file at
/llms.txt: markdown, with an H1 title and substantive, agent-actionable
content that orients a language model to what the surface is and how to use it.
It MUST NOT be an HTML page. It SHOULD cross-reference the sibling
machine surfaces (agents.json, icp.json, the OpenAPI 3.1 contract).
Machine-verified by: api.qa check llms-txt and pinned requirement
llms-present (surface llms.txt). api.qa confirms /llms.txt returns
markdown (H1 + substantive body), not markup.
Why an agent needs it: llms.txt is the plain-language front door for a model — the one file a fresh agent reads to learn the surface without scraping a rendered site.
§3machine-legible for every client
Normative. A conforming API MUST answer machine-legible on its
resolver-addressable home and its typed bodies for every client: typed
bodies are JSON, documentation surfaces are markdown, and the representation
MUST NOT vary into HTML by Accept header — a request with Accept: */*
(an agent/curl) and a request with Accept: text/html (a browser) MUST
both receive machine-legible JSON or markdown, never a wall of HTML. An API is
an API whether a browser calls it or not; HTML is a page concern, and
human-register pages live on page surfaces outside this protocol's scope
(ADR-0014, ADR-0018), not on API paths.
Machine-verified by: api.qa check machine-legible-home, pinned as its own
requirement check-machine-legible-home (kind: check, must: pass) — the
clause binds to this discriminating check directly, not to the coarse AX floor.
api.qa fetches the home twice with differing Accept headers and samples the
target's declared typed-body endpoints under both Accept: */* and
Accept: text/html, and confirms every one is machine-legible non-HTML — the
obligation is verified beyond the root, on the typed bodies themselves.
Why an agent needs it: machine-legibility for every client means an agent extracts meaning directly from the response instead of parsing a DOM built for eyes — and a human inspecting the API in a browser sees the same truth the agent consumes, not a diverging rendering.
§4typed BLOCKED/EMPTY errors that never fake success
Normative. A conforming API MUST return typed outcomes for the
no-result and not-permitted paths and MUST NOT fake success. An empty
result set MUST carry an explicit EMPTY type (not a bare 200 with an
empty array masquerading as data); a forbidden or gated request MUST carry
an explicit BLOCKED type with a non-2xx status. A conforming API MUST NOT
return a 200 success envelope when the true outcome is BLOCKED/EMPTY.
The outcome type is carried as a top-level type member with the closed
vocabulary of Appendix A.1. So that this clause is decidable from outside, the
capability card MUST declare, in its probe manifest (Appendix A.3), at
least two distinct known-empty probe URLs (probes.knownEmpty) and at
least two distinct known-forbidden probe URLs (probes.knownForbidden).
Each declared known-empty probe MUST answer 200 with an EMPTY
envelope, and each declared known-forbidden probe MUST answer 401 or
403 with a BLOCKED envelope. Additionally, each declared known-empty and
known-forbidden probe URL MUST address a pathname that is also observed
answering 200 with an OK envelope during the same verification run (for
example via the keyless probe or the amount-0 over-ceiling control): the
probed path must demonstrably branch on its query — a dedicated endpoint
that can only ever answer EMPTY or BLOCKED is a decoy and does not
satisfy this clause.
Machine-verified by: api.qa check claims-honesty and pinned probe
requirements typed-empty and typed-blocked (kind: probe, Appendix A.4,
each pinned with pathMustServeOk: true). The probes are not chosen by the
verifier: api.qa reads probes.knownEmpty and probes.knownForbidden from
the target's own capability card, fires every declared probe, and confirms
each response is the correctly typed EMPTY / BLOCKED envelope AND that
each probed pathname also answered 200 OK in the same run — a faked
200, a card that declares fewer than two of either, or a decoy pathname
never observed serving OK, fails the contract.
Why an agent needs it: an agent acts on outcomes. A faked success sends the agent down a wrong branch silently; a typed BLOCKED/EMPTY lets it re-plan, escalate, or pay — deterministically.
§5hard-ceiling metered pricing
Normative. Every conforming API MUST declare its pricing as a
machine-readable Pricing Document (Appendix A.2) at a probes.pricing surface
that answers 200 with a closed "model" of "free" or "metered" — so an
agent wallet always learns worst-case exposure before the first call, and a
free API is admissible by declaring "model": "free", never by staying silent.
An API that meters usage ("model": "metered") MUST additionally publish a
hard-ceiling: a declared maximum spend, strictly greater than zero, the
caller cannot exceed without an explicit new authorization; it MUST declare
its offers and its offer boundary (monetization.offers and
monetization.probe, Appendix A.5), the declared monetization.probe URL is
behaviorally probed and MUST answer 402 with a typed OFFER body
(Appendix A.1: "type": "OFFER" plus id/title and price); and it MUST
declare an over-ceiling probe (probes.overCeiling): a same-origin declared
operation plus the name of its spend parameter. The verifier — not the API —
computes every probed amount from the observed hardCeiling: a request at
a verifier-derived amount well above the ceiling — a seed-randomized multiple
of the observed hardCeiling, so the exact amount is not precomputable from the
declared ceiling — MUST be answered 402 with a typed
OFFER re-authorization boundary, never silently metered past the cap, while
the same probe at half the observed ceiling MUST be answered 200, and at
amount 0 MUST be answered 200 — an operation that 402-walls amounts
within the declared ceiling is theater, not metering. The metering obligations
apply only when "model": "metered"; they do not apply to a free API.
Machine-verified by: the pinned probe requirement pricing-declared (the
card-declared probes.pricing surface answers 200 with a model of free
or metered) — required of every target — plus, gated on metered
(appliesWhen model: "metered", so a free API passes them as not
applicable), api.qa check offers-402, pinned as check-offers-402
(kind: check, must: pass) — behaviorally probing the card's
monetization.probe URL (Appendix A.5) for a 402 with a typed OFFER body —
and the pinned probe requirements (kind: probe, Appendix A.4):
hard-ceiling-positive (the metered pricing surface publishes hardCeiling
> 0), hard-ceiling-enforced (the over-ceiling operation, at a
verifier-derived amount above the observed ceiling, answers a 402 OFFER
re-authorization boundary, not a silent 200), hard-ceiling-below (the same
operation at half the observed ceiling answers 200 — the declared ceiling
comparison is real, not an any-spend-refused echo), and
hard-ceiling-not-premature (the same operation at amount 0 answers 200,
not 402-walled theater).
Why an agent needs it: an autonomous wallet cannot spend safely against an open-ended meter. A hard-ceiling makes worst-case cost knowable and bounded, so the agent can commit funds without a human approving each call.
§6resolver-addressable home
Normative. A conforming API MUST have a resolver-addressable home:
a stable, resolvable canonical identity (a capability card at
/.well-known/agents.json and a resolvable root) that an agent can address,
resolve, and cross-link from the sibling surfaces. The home MUST resolve to
a valid capability card naming the surface and its interfaces.
The capability card MUST carry the AXP probe manifest (probes,
Appendix A.3): the card is where an API declares, on its own origin, the URLs
at which the verifier exercises Clauses 4, 5, and 7. A card without a valid
probe manifest leaves those clauses unverifiable, and verification fails
closed.
Machine-verified by: api.qa check agents-json, pinned as its own
requirement check-agents-json (kind: check, must: pass), plus the surface
requirement agents-present (surface agents.json, must: valid). api.qa
resolves the capability card and confirms it is valid JSON that names the
surface (name) and declares its interfaces — the clause binds to the
discriminating agents-json check, not the coarse AX floor. Cross-linking of
sibling surfaces is admission-pinned: the requirement
check-card-interfaces-linked (api.qa check card-interfaces-linked) confirms
the card names its interfaces (interfaces.http and/or interfaces.mcp,
non-empty) and that llms.txt, agents.json, and the OpenAPI contract mutually
cross-link — the card references its OpenAPI contract and its llms.txt, and
the served llms.txt references both agents.json and the OpenAPI contract.
Also pinned: the requirement probe-manifest-valid (api.qa check
probe-manifest): the manifest declares every required channel, same-origin,
GET-only, addressing non-templated operations the contract itself publishes,
and the card also declares the monetization.probe boundary (Appendix A.5).
Why an agent needs it: the resolver-addressable home is the single address an agent resolves to find everything else. Without it the surface is an island an agent cannot reliably re-find or link.
§7keyless first value
Normative. A conforming API MUST deliver its first unit of real value
with no API key, no signup, and no account: at least one declared,
discoverable endpoint MUST answer 2xx with substantive typed content to an
anonymous agent on first contact. Authentication and payment MAY gate deeper
or metered use, but the surface MUST NOT require an account before an agent
can obtain any value at all.
The capability card MUST name one such endpoint in its probe manifest
(probes.keyless); the declared probe MUST answer 200 with an OK
envelope (Appendix A.1) to an anonymous caller.
Machine-verified by: api.qa check keyless-flow, pinned as its own
requirement check-keyless-flow (kind: check, must: pass), plus the pinned
probe requirement keyless-first-value (kind: probe: api.qa fires the
card-declared probes.keyless request with no credential and requires a 200
OK envelope). api.qa samples the endpoints declared by agents.json and
the OpenAPI contract and confirms at least one answers 2xx keyless.
Why an agent needs it: a fresh agent cannot negotiate an account before it knows whether the surface is useful. Keyless first value is what lets it integrate zero-shot on first contact — the whole point of the agent-first web.
§Conformance
Conformance is verified by api.qa, held outside the building fleet's write access (verifier independence); a Listing is admitted to the apis.ax catalog iff it passes this standard's conformance profile.
The mapping from each clause above to its independent api.qa verification is
published, machine-readable, at
conformance/profile.json. The conformance
profile is executable as an api.qa PinnedSpec at
conformance/apis-ax-standard.spec.json:
the apis.ax curation gate expressed as a machine-checkable contract. A target
passes iff api.qa returns passed: true against that pinned spec — the
standards body publishes the bar, the independent verifier decides whether it
is cleared, and the building fleet can edit neither verdict.
Because the verifier is held outside the fleet that builds the catalog, a Listing cannot grade itself: the gate is mechanical, not aspirational, and that independence is the whole point.
The pinned spec's ratified digest is checked out-of-band. The in-tree file
conformance/apis-ax-standard.digest.txt is a hermetic-test convenience so
the CLI and the test suite read the same pin locally; it is not the
production independence property. Production/catalog admission runs api.qa in
attested mode, which refuses to verify unless an expected digest is supplied
from outside the building fleet's write access — a spec silently weakened
in-tree cannot be re-ratified into a passing verdict, because the fleet does not
hold the digest the attested verifier checks against.
Clause 4, 5, and 7 behavioral probes are resolved from the target's own
capability card (probes.*, Appendix A.3), never hardcoded by this standard:
any API, whatever its routes, is decidable against this profile by publishing
a conforming probe manifest. The manifest declares only where the verifier
probes; what counts as passing is fixed by the pinned spec, verbatim
Appendix A.4, and expectation-like material in a manifest is ignored. If the
manifest is absent, incomplete, or invalid, every requirement derived from it
fails — verification fails closed, never vacuously passes.
The pinned spec contains no obligation this document does not state: every
pinned requirement id appears in a clause's Machine-verified by line, the
probe requirements are exactly the block printed in Appendix A.4, and the
conformance tests enforce both directions structurally — including that
requirement ids are unique and that every kind: check requirement names
exactly the api.qa check its clause's prose names (a rewired or duplicated
requirement fails the suite, not just the digest). Pricing declaration is
universal: for admission every API — free or metered — declares
probes.pricing, whose Pricing Document answers with the closed model of
"free" or "metered" (Clause 5, Appendix A.2). The metering surfaces —
probes.overCeiling and the monetization member — are required iff
"model": "metered"; a free API is admitted by declaring "model": "free"
and passes the metering requirements as not applicable, never by staying
silent.
§Appendix A — AXP wire vocabulary and probe manifest (normative)
This appendix is citable as https://apis.ax/axp#wire. The outcome-type
vocabulary (A.1) and the pricing model (A.2) are closed in this version;
A.3 defines the probe channels this standard requires — additional
channels are validated under the same rules but carry no obligations here.
§A.1 Outcome envelope
Every typed outcome body carries a top-level type member with the closed
vocabulary "OK" | "EMPTY" | "BLOCKED" | "OFFER". The status code and the
type are one-to-one, so an agent dispatches on type alone:
OK— status200; the request succeeded and the body carries substantive content.EMPTY— status200; the request succeeded and the truthful result is an empty collection.BLOCKED—401or403; a permission boundary (not permitted, not authenticated).OFFER— status402; a payment or ceiling re-authorization boundary. The body carries anidortitleplus aprice,checkoutUrl, oralternativesmember (Appendix A.5). A402MUST carry"type": "OFFER", never"BLOCKED"— payment is an offer to proceed, not a refusal.
The collection member name (e.g. results, items, terms) is the API's own
choice, documented in its OpenAPI contract — it is not constrained by this
standard.
{ "type": "OK", "results": [ { "id": "r1" } ] }{ "type": "EMPTY", "results": [], "message": "no records match" }{ "type": "BLOCKED", "reason": "not permitted for your agent class" }{ "type": "OFFER", "id": "metered-access", "title": "Metered access", "checkoutUrl": "/checkout" }§A.2 Pricing Document
Every conforming API declares a probes.pricing surface that answers 200
with a Pricing Document:
{ "model": "metered", "hardCeiling": 100, "unit": "usd-per-call", "price": 0.002 }{ "model": "free" }model is closed to "free" | "metered" in this version. When
"model": "metered", hardCeiling MUST be a JSON number strictly greater
than zero; unit and price are optional descriptive members. When
"model": "free", no ceiling is required and the Clause 5 metering probes do
not apply — the declaration itself is the obligation, so an agent wallet reads
a deterministic cost answer (including "free") before its first call.
§A.3 Probe manifest
The capability card (/.well-known/agents.json) carries a top-level probes
member: the manifest of URLs at which the verifier exercises Clauses 4, 5,
and 7. The required channels:
| channel | minimum | meaning |
|---|---|---|
keyless | 1 | answers 200 OK to an anonymous caller (Clause 7) |
knownEmpty | 2 distinct | answers 200 EMPTY truthfully (Clause 4) |
knownForbidden | 2 distinct, disjoint from knownEmpty | answers 401/403 BLOCKED (Clause 4) |
pricing | 1 | answers 200 with a Pricing Document — free or metered (Clause 5) |
overCeiling | 1 (metered only) | the metered operation; entry additionally carries param, the query-parameter name for requested spend, in the same unit as hardCeiling. Not required of a free API (Clause 5) |
Entry shape: { "method"?: "GET", "url": "<same-origin relative or absolute>", "param"?: "<overCeiling only>" }. Single objects normalize to one-element
arrays; unknown entry members are ignored. Channels beyond the required five
are validated under the same rules below but carry no additional obligations
in this version.
Rules, each MUST:
- every probe URL is same-origin with the card;
- every probe is a
GET; - every probe pathname addresses a non-templated path declared in the
OpenAPI contract, or a concrete URL declared in
interfaces.http(an operation whose OpenAPI path is templated, e.g./terms/{id}, is probeable only via such a concreteinterfaces.httpentry); - distinctness of probe URLs is judged on the fetched identity: URL fragments
are stripped, so
/e?a=1and/e?a=1#dupcount as one probe; - every
knownEmptyandknownForbiddenpathname is also observed answering200OKin the same verification run (pathMustServeOk, Clause 4) — a dedicated decoy endpoint that can only answerEMPTY/BLOCKEDdoes not satisfy Clause 4; - the manifest never declares expectations — the verifier derives the
over-ceiling, below-ceiling, and zero amounts from the observed
hardCeilingand overwrites the declaredparam; - when
probes.pricingdeclares multiple entries, the verifier derives amounts and applicability from entry index 0 — the first declared pricing entry is the authoritative Pricing Document.
Example:
{
"probes": {
"keyless": { "url": "/records" },
"knownEmpty": [ { "url": "/records?filter=none" }, { "url": "/records?tag=gamma" } ],
"knownForbidden": [ { "url": "/records?filter=forbidden" }, { "url": "/records?scope=admin" } ],
"pricing": { "url": "/pricing" },
"overCeiling": { "url": "/records", "param": "spend" }
}
}§A.4 Derived pinned probe requirements
The apis.ax pinned spec includes these eight requirements exactly as printed; the conformance suite asserts structural equality between this block and the ratified spec.
[
{ "id": "keyless-first-value", "kind": "probe", "probe": "keyless",
"expect": { "status": 200, "paths": [ { "path": "type", "equals": "OK" } ] } },
{ "id": "typed-empty", "kind": "probe", "probe": "knownEmpty", "minDeclared": 2, "pathMustServeOk": true,
"expect": { "status": 200, "paths": [ { "path": "type", "equals": "EMPTY" } ] } },
{ "id": "typed-blocked", "kind": "probe", "probe": "knownForbidden", "minDeclared": 2, "pathMustServeOk": true,
"expect": { "status": [401, 403], "paths": [ { "path": "type", "equals": "BLOCKED" } ] } },
{ "id": "pricing-declared", "kind": "probe", "probe": "pricing",
"expect": { "status": 200, "paths": [ { "path": "model", "oneOf": ["free", "metered"] } ] } },
{ "id": "hard-ceiling-positive", "kind": "probe", "probe": "pricing",
"appliesWhen": { "fromProbe": "pricing", "path": "model", "equals": "metered" },
"expect": { "status": 200, "paths": [ { "path": "hardCeiling", "gt": 0 } ] } },
{ "id": "hard-ceiling-enforced", "kind": "probe", "probe": "overCeiling",
"appliesWhen": { "fromProbe": "pricing", "path": "model", "equals": "metered" },
"paramValue": { "fromProbe": "pricing", "path": "hardCeiling", "multiplyRange": [500, 1500] },
"expect": { "status": [402], "paths": [ { "path": "type", "equals": "OFFER" } ] } },
{ "id": "hard-ceiling-below", "kind": "probe", "probe": "overCeiling",
"appliesWhen": { "fromProbe": "pricing", "path": "model", "equals": "metered" },
"paramValue": { "fromProbe": "pricing", "path": "hardCeiling", "multiply": 0.5 },
"expect": { "status": 200, "paths": [ { "path": "type", "equals": "OK" } ] } },
{ "id": "hard-ceiling-not-premature", "kind": "probe", "probe": "overCeiling",
"appliesWhen": { "fromProbe": "pricing", "path": "model", "equals": "metered" },
"paramValue": 0,
"expect": { "status": 200, "paths": [ { "path": "type", "equals": "OK" } ] } }
]§A.5 Monetization surface
A metered API carries a top-level monetization member. Both of its
members below are required for admission of a metered API (a free API
declares neither):
monetization.offers— a non-empty array of offers; each offer carries anidortitle, plus aprice,checkoutUrl, oralternativesmember.monetization.probe—{ "method"?: "GET", "url": "<same-origin>" }: a URL that answers HTTP402with a typedOFFERbody ("type": "OFFER"plus id/title plus price | checkoutUrl | alternatives). The URL MUST be a same-originGET; an off-origin, non-GET, or private-address probe is refused without being fetched (SSRF guard) and fails admission.
{
"monetization": {
"offers": [ { "id": "metered-access", "title": "Metered access", "price": { "model": "metered" } } ],
"probe": { "method": "GET", "url": "/offer" }
}
}The pinned requirement check-offers-402 (api.qa check offers-402)
behaviorally probes the declared monetization.probe URL for the structured
402 offer, and the pinned probe-manifest check fails a card that
declares a probe manifest without declaring monetization.probe — so the
structured-offer MUST of Clause 5 is always behaviorally verified for
admitted targets, never satisfied by declaration alone.
§A.6 Derived pinned surface requirements
The apis.ax pinned spec includes these surface requirements exactly as
printed; the conformance suite asserts structural equality between this
block and the ratified spec, so no surface obligation (e.g. the OpenAPI
version prefix or the minimum operation count) can be weakened in the spec
without changing this normative text. This closes, for the kind: surface
requirement BODIES, the same drift the Appendix A.4 block closes for the
probes: the openapi-valid tightening (versionPrefix: "3.1",
minOperations: 1) that makes an OpenAPI 3.0.x or empty-paths document fail
Clause 1 is pinned here in prose, not only in the spec file.
[
{ "id": "llms-present", "kind": "surface", "surface": "llms.txt", "must": "present" },
{ "id": "agents-present", "kind": "surface", "surface": "agents.json", "must": "valid" },
{ "id": "openapi-valid", "kind": "surface", "surface": "openapi", "must": "valid", "versionPrefix": "3.1", "minOperations": 1 }
]