---
$id: https://apis.ax/axp
$type: Spec
$context: https://schema.org.ai
title: The Agent eXperience Protocol (AXP)
version: 0.3.0
publisher: apis.ax
verifier: api.qa
status: draft
date: 2026-07-21
---

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

---

## Clause 1 — OpenAPI 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.

## Clause 2 — llms.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.

## Clause 3 — machine-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.

## Clause 4 — typed 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.

## Clause 5 — hard-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.

## Clause 6 — resolver-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.

## Clause 7 — keyless 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`](./conformance/profile.json). The conformance
profile is executable as an api.qa **PinnedSpec** at
[`conformance/apis-ax-standard.spec.json`](./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` — status `200`; the request succeeded and the body carries substantive
  content.
- `EMPTY` — status `200`; the request succeeded and the truthful result is an
  empty collection.
- `BLOCKED` — `401` or `403`; a permission boundary (not permitted, not
  authenticated).
- `OFFER` — status `402`; a payment or ceiling re-authorization boundary. The
  body carries an `id` or `title` plus a `price`, `checkoutUrl`, or
  `alternatives` member (Appendix A.5). A `402` **MUST** 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.

```json
{ "type": "OK", "results": [ { "id": "r1" } ] }
```

```json
{ "type": "EMPTY", "results": [], "message": "no records match" }
```

```json
{ "type": "BLOCKED", "reason": "not permitted for your agent class" }
```

```json
{ "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:

```json
{ "model": "metered", "hardCeiling": 100, "unit": "usd-per-call", "price": 0.002 }
```

```json
{ "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 concrete `interfaces.http` entry);
- distinctness of probe URLs is judged on the fetched identity: URL fragments
  are stripped, so `/e?a=1` and `/e?a=1#dup` count as **one** probe;
- every `knownEmpty` and `knownForbidden` pathname is also observed answering
  `200` `OK` in the same verification run (`pathMustServeOk`, Clause 4) — a
  dedicated decoy endpoint that can only answer `EMPTY`/`BLOCKED` does not
  satisfy Clause 4;
- the manifest never declares expectations — the verifier derives the
  over-ceiling, below-ceiling, and zero amounts from the **observed**
  `hardCeiling` and overwrites the declared `param`;
- when `probes.pricing` declares multiple entries, the verifier derives amounts
  and applicability from **entry index 0** — the first declared pricing entry is
  the authoritative Pricing Document.

Example:

```json
{
  "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.

```json axp:pinned-probes
[
  { "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 an
  `id` or `title`, plus a `price`, `checkoutUrl`, or `alternatives` member.
- `monetization.probe` — `{ "method"?: "GET", "url": "<same-origin>" }`: a URL
  that answers HTTP `402` with a typed `OFFER` body (`"type": "OFFER"` plus
  id/title plus price | checkoutUrl | alternatives). The URL **MUST** be a
  same-origin `GET`; an off-origin, non-`GET`, or private-address probe is
  refused without being fetched (SSRF guard) and fails admission.

```json
{
  "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.

```json axp:pinned-surfaces
[
  { "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 }
]
```
