apis.ax / Typed outcomes — the five-kind algebra every call resolves to

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

KindIt meansStatusIt carriesYour move
OKThe job ran; here is the real result2xxthe result bodyact on it
EMPTYThe call worked; the set is genuinely empty2xx, typed bodyresults: [], optional messagere-plan — you were not fooled
BLOCKEDThis door is closed to this call as madenon-2xx, never 402reason, optional reauthfix the call, re-auth, or route around
OFFERPayment opens this door402, always(id | title) + (price | checkoutUrl | alternatives)settle iff price ≤ Mandate ceiling, retry → OK
NEEDS_HUMANA human decision opens this door202url + shortUrl + pollUrl, expiresAt, stakes-gated codehand 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 OFFER or 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_HUMAN ratification. The ratified wire set of A.1 today closes at OK | EMPTY | BLOCKED | OFFER. NEEDS_HUMAN is the fifth member AXP is ratifying now: the toolkit already speaks it — construct, parse, poll-and-resume — and the ruling pins the 202 pairing, the always-both url/shortUrl, expiry and decline semantics, and the verifier check that fails a dead-end gate. The day that check lands, a login wall answering BLOCKED stops passing admission.
  • The warranty rule — no value, no charge: a BLOCKED or an EMPTY is never metered.
  • The firm quote — an OFFER the seller is held to, not an advertisement of a price. The signed settlement receipt — the OK of 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