Typed outcomes — the five-kind algebra every call resolves to
Every call to a conforming surface resolves to exactly one of five typed outcomes — OK, EMPTY, BLOCKED, OFFER, NEEDS_HUMAN — and each one is a branch you can act on. A result is a result. An empty set says it is empty. A closed door says why. A paywall is a price. A human gate is a handoff you can resume from. This page is the algebra: what each kind means, what it carries, and the law that outlaws the sixth kind — the untyped dead end.
The algebra exists for exactly one reader: you, mid-plan, deciding your next move on the strength of one response. Everything below is what that response is allowed to be.
The frame
AX = Agent eXperience — what UX and DX were for humans, AX is for agents.
The human-first web answers you in one type: 200-shaped. Empty result? A 200 over [] — reads as data. Login wall? A 200 over HTML — reads as content. Paywall? A 403 and a sales page — reads as a refusal, when it was a price you could have paid. Untyped answers make every failure look like success and every gate look like a wall — you branch on lies, or you stop dead at doors that money or your principal could have opened. It is the same villain you meet everywhere on this site — the surface with a salesperson where the price should be — seen at wire resolution: the gate that cannot tell you, in a type, what would open it.
AXP — the Agent eXperience Protocol (https://apis.ax/axp) replaces that with a closed algebra. Five kinds. Total. You can write one switch over every answer a conforming surface will ever give you.
The five kinds
| Kind | It means | Status | It carries | Your move |
|---|---|---|---|---|
OK | The job ran; here is the real result | 2xx | the result body | act on it |
EMPTY | The call worked; the set is genuinely empty | 2xx, typed body | results: [], optional message | re-plan — you were not fooled |
BLOCKED | This door is closed to this call as made | non-2xx, never 402 | reason, optional reauth | fix the call, re-auth, or route around |
OFFER | Payment opens this door | 402, always | (id | title) + (price | checkoutUrl | alternatives) | settle iff price ≤ Mandate ceiling, retry → OK |
NEEDS_HUMAN | A human decision opens this door | 202 | url + shortUrl + pollUrl, expiresAt, stakes-gated code | hand off, poll, resume |
One more kind exists on your side only: FOREIGN — the untyped, non-conformant world. The Round-trip Law (readOutcome(serialize(o)) ≡ normalize(o), for every wire outcome) makes FOREIGN readable but never constructible: you can classify the human-first web when you meet it, but a conforming surface can never emit it.
Why a 402 is an OFFER, never a refusal
To an eye, Payment Required is a stop sign. To you — a wallet holding a Mandate ceiling — a stop sign with a price on it is a path. ADR 0019 makes that normative: a 402 MUST carry a typed OFFER and never a BLOCKED. The OFFER names the exact price, or a checkout, or alternatives; you settle if and only if the price sits at or under your ceiling, and the same call returns OK. No human arbitrates.
A paywall pretending to be an error is a conformance failure, not a style complaint: it converts a purchasable branch into a dead end, and the pinned gate's hard-ceiling probes — anti-theater probes included — fail it. The toolkit is honest by construction on the same point: a 402 constructs only as a typed OFFER; the 402-as-BLOCKED anti-pattern is purged from the toolkit and its docs. The OFFER parser accepts exactly the protocol's A.1/A.5 shape, alternatives included, so a conformant offer never reads as null on your side.
Why EMPTY never fakes success
A 200 over an empty array is the quietest lie on the wire. It costs the seller nothing and costs you everything: you read success with data, you branch on it, and you carry a falsehood forward into every step that follows — silently. No exception, no signal, no way to know which of your beliefs is now wrong.
EMPTY splits nothing-found from something-found at the type level. The call worked; the set is empty; you know which — so "no results" becomes a fact you re-plan on instead of a lie you build on. BLOCKED is held to the same honesty: a gated call answers non-2xx with a typed reason, never a 200 over a login page, so closed and content can never be confused.
The LAW: every gate is an OFFER or a Handoff
A gate that is not an
OFFERor a Handoff is a dead end, and dead ends are conformance failures.
Every gate resolves by what would open it. Money-shaped → typed OFFER. Judgment-shaped — authenticate, authorize a payment, approve a consequential act — → typed NEEDS_HUMAN, the Handoff. BLOCKED is reserved for doors genuinely closed to the call as made — malformed input, an exhausted rate window (refilled by the clock; paying cannot refill it), a revoked credential — always with a typed reason you can act on. It is never the lawful answer for a door that money or a human decision could open.
The Handoff, precisely: the typed NEEDS_HUMAN always carries BOTH the full approval url (you are on the human's machine — open the default browser) AND a signed, expiring shortUrl (you are remote — relay it over chat), plus a pollUrl so you resume autonomously the moment approval lands. The shortUrl is deliberately NOT single-use: messaging apps unfurl links, so GET is side-effect-free and renders a stakes-aware preview of exactly what is being approved — only explicit human interaction on the page approves. A verification code appears only when stakes demand it (RFC 8628). You are never stranded at a login wall; you are parked at a poll.
Branch on it
The algebra is closed, so your consumer is a total function — five cases, none of them a guess:
switch (outcome.kind) {
case 'OK': return act(outcome)
case 'EMPTY': return replan('zero results — truthfully')
case 'BLOCKED': return reroute(outcome.reason)
case 'OFFER': return outcome.price <= ceiling ? settle(outcome) : decline(outcome)
case 'NEEDS_HUMAN': return resume(await handoff(outcome)) // open url / relay shortUrl, poll, resume
case 'FOREIGN': return quarantine(outcome) // the untyped web — readable, never constructible
}
Prove it in the shell
Every command below runs today, in the No-ask Zone. Start with the tool proving it obeys its own law — feed the CLI a verb it does not have:
npx apis.ax not-a-command
# stderr → { "type": "BLOCKED", "reason": "unknown command \"not-a-command\"",
# "commands": ["audit"], "see": "https://apis.ax/axp" } (exit 2)
A typed BLOCKED and a non-zero exit — never a summary that exits 0 pretending success. A tool that fakes success on unknown input is the faked 200 wearing a shell prompt; ours is bound by clause 4 like everything else.
npx apis.ax audit <domain> # api.qa runs the pinned gate against any surface — including the typed-outcome probes
npm install agent-experience # the zero-dep toolkit: the algebra in both directions (Round-trip Law)
curl -s https://apis.ax/ -H 'accept: application/json' # the catalog answers in-type
The audit is where the algebra grows teeth: judged by api.qa, never self-graded. The pinned gate behaviorally probes clause 4 and clause 5 — typed EMPTY on empty sets, typed BLOCKED on gated calls, the typed OFFER at every 402 boundary, anti-theater probes included. Admission is passed: true at a ratified digest, or nothing — so a Listing in the catalog has already proven, to a verifier its seller cannot rig, that it speaks this algebra. Even the estate's own paywall submits: page.ax's persist gate answers a typed 402 OFFER minted from declared pricing, with typed BLOCKED/OK edges.
Where the algebra goes
State this plainly, because it is the direction, not the present tense.
NEEDS_HUMANratification. The ratified wire set of A.1 today closes atOK | EMPTY | BLOCKED | OFFER.NEEDS_HUMANis the fifth member AXP is ratifying now: the toolkit already speaks it — construct, parse, poll-and-resume — and the ruling pins the202pairing, the always-bothurl/shortUrl, expiry and decline semantics, and the verifier check that fails a dead-end gate. The day that check lands, a login wall answeringBLOCKEDstops passing admission.- The warranty rule — no value, no charge: a
BLOCKEDor anEMPTYis never metered. - The firm quote — an
OFFERthe seller is held to, not an advertisement of a price. The signed settlement receipt — theOKof a payment itself: portable, provable, typed.
These are open clauses of the protocol, not calls you can make today. Each ships at full claim strength the moment its verifier check lands, and not a sentence sooner — the ratchet only moves one way.
Failure vs. success
Failure is the untyped web: a 200 that was a lie, a wall that was a price, a login page where a handoff should be — every failure dressed as success, every gate a dead end, you branching on fog.
Success is one total switch: five kinds, each honest, each actionable — a result you act on, an empty set you re-plan on, a reason you route around, a price you settle inside your ceiling, a human you pull in at exactly the decision point and resume from the moment they approve.
Human twin: https://apis.directory/typed-outcomes