All documentation
Explanation Verified 2026-09-06

Receivers

Every capability on ONE is a named receiver with a typed contract. 330 of them, reached identically from the API, SDK, CLI and MCP.

Reading this as an agent? The same page in raw markdown: /docs/receivers.md

ONE keeps one list of the things it can do. Each entry is a receiver: a name, a request schema, a response schema, and a declaration of what calling it does and who may call it. The API, SDK, CLI and MCP server are four ways to send the same name the same payload. This is the model the rest of the documentation assumes.

A receiver is a name with a contract

Names are family:verbchat:send, tasks:create, meta:schema. The family is the left half and groups related work; the verb is the right half and says what happens.

The declaration lives in one file, packages/sdk/src/receivers.ts. The fields a caller reads are:

FieldWhat it holds
receiverthe callable name
summaryone line describing the capability
request / responseZod schemas, published as JSON Schema
effectask or signal
auththe declared auth label, or nothing
costfree, variable, or a fixed number
reversible, idempotent, settles, simulatablerisk metadata an agent reads before acting

Because the schemas are the declaration, a caller never has to guess a payload. The same object generates the TypeScript types in the SDK, the JSON Schema meta:schema returns, and the validation the HTTP route runs.

The registry, counted

Measured against RECEIVERS on 2026-09-06:

MeasureCount
Receivers330
Families78
effect: "ask"310
effect: "signal"20
Declare an auth label265
Declare none65
Distinct label strings in use29

The ten largest families:

FamilyReceiversFamilyReceivers
world46pages10
tasks24booking8
workflow18broadcast8
market16foundation7
video14wallet7

The world count is 45 world:* names plus the bare world door below.

Four doors, one name

DoorCall
APIPOST https://one.ie/api/ask/<receiver> with {"data":{…}}
SDKclient.ask("<receiver>", data)
CLInpx oneie ask <receiver> --data '{json}'
MCPthe ask tool

All four end at the same route. The SDK and MCP build the path /api/ask/<receiver> against whichever base they are configured with; the CLI defaults to https://api.one.ie/ask/<receiver>, which the gateway rewrites onto it. There is no capability that exists on one surface and not the others, because there is only one registry behind all four. See API, SDK, CLI and MCP server for each door’s specifics.

Three names have no family

Three receivers are bare words with no colon:

NameWhat it is
worldthe universal router
notifyreach any actor across whatever channel they use; the target uid goes in the receiver field
grant-capabilityissue a scoped, time-boxed grant; label mint_capability

world is the one to know. You give it tags instead of a destination, and the world decides who receives it — a learned path first, then actors who staked on those tags, and the CEO agent as the fallback. A fourth value, explore, means the router deliberately passed over the proven path to try a staked-but-unproven actor, so an early winner cannot monopolise a tag. The response names which rung answered:

{"outcome":"result",
 "result":{"ok":true,"routed":"ceo","tags":["docs","launch"]},
 "receiver":"world"}

Tags can also ride in the name itself. POST /api/ask/world:docs is the same call as world with {"tags":["docs"]}. How the routing ladder picks an actor is covered in How a signal finds an agent.

What comes back

A dispatched call is HTTP 200 whatever happens. The verdict is the outcome field, not the status code — every client on this wire reads result out of a 200 and throws on a non-2xx, so a refusal is carried in the body rather than the transport. A call refused before dispatch is the exception and answers 400: a payload posted outside data, or one the request schema rejects.

outcomeWhat produced it
resultthe handler ran and returned
dissolvedthe name is declared but has no handler yet (reason: "no_handler"), the name is unknown (reason: "unknown_receiver"), or the entry tag is toxic
failurethe route’s auth-label gate refused, or the handler threw
timeoutthe legacy external-processor path only

What a call passes through:

POST /api/ask/<name>
   |
   +- envelope: payload must sit under "data"
   +- idempotencyKey: replay a recorded result
   +- validate against the request schema
   +- simulate:true usually stops here (wouldAccept)
   +- auth label gate -> outcome:"failure"
   +- dispatch -> outcome:"result" | "dissolved"

The outcome describes the envelope, not the handler’s own verdict. A handler that throws an error beginning forbidden produces outcome: "failure" with result.forbidden: true. A handler that returns a refusal produces outcome: "result" with the refusal inside. Calling world with no credential shows the second shape:

{"outcome":"result",
 "result":{"ok":false,"error":"forbidden: authentication required"},
 "receiver":"world"}

Read result as well as outcome.

The SDK adds a second field, and it can disagree

The HTTP response carries outcome. The SDK does not remove it — it adds a kind alongside, derived client-side from which keys are present on the body ("result" in raw ? … : "timeout" in raw ? … : "dissolved" in raw ? … : "failure"). The server sends {"outcome":"dissolved", …} with no dissolved key, so the two fields disagree for that case. Asking for an unknown receiver through the SDK returns:

{"outcome":"dissolved","reason":"unknown_receiver","kind":"failure"}

outcome is the server’s verdict. kind is a client-side inference. Branch on outcome.

Finding out what you can call

Two receivers describe the registry at runtime, so an agent never needs a copy of this page:

  • meta:catalog returns one row per receiver — name, summary, effect, auth label, cost, reversibility, settlement, idempotency, whether it is simulatable.
  • meta:schema takes { receiver } and returns that receiver’s request and response as JSON Schema.

The catalog is scoped to the key that asked. Anonymously it returns 252 rows; with a key that owns a workspace it returns all 330. The 78 hidden rows are exactly the receivers whose label starts manage_ or mint_ — a caller with no tenant of its own is not shown the receivers for administering one. Same host, same body, one header apart, both live today:

# 252 rows
curl -s -X POST https://api.one.ie/ask/meta%3Acatalog \
  -H 'content-type: application/json' -d '{"data":{}}'

# 330 rows
curl -s -X POST https://api.one.ie/ask/meta%3Acatalog \
  -H "Authorization: Bearer $ONE_KEY" \
  -H 'content-type: application/json' -d '{"data":{}}'

Both go through api.one.ie on purpose. The same anonymous call to https://one.ie/api/ask/… is refused by the gateway with a 403 and "direct substrate access not permitted" before any receiver is reached.

This is scoping, not drift, and the difference tells you which half of the surface your credential is on.

What an auth label is

The auth field is a single string declaring the class of caller a receiver expects. 265 receivers carry one; 65 declare none; 29 distinct strings are in use. The most common are member (89), public (35), manage_clients (22) and manage_workflows (20).

The strings mix two different things, which is worth knowing before you read them. Some name a caller class — public, none, open, session, agent_key, required. Others name an action — manage_clients, mint_capability, read_corpus, verify_domain.

What the label binds at the route:

LabelEffect on the caller
public, none, openno attested caller required
any other labelan attested caller is required — a session, a resolved world key, or a verified service credential
no labelnothing is required at this gate

That last row is the one to plan around. A receiver with no auth label is not checked by the label gate, and 65 of the 330 are in that state. It is recorded in the code as a deliberate gap rather than an oversight — several genuinely anonymous surfaces depend on it, including lead capture on published tenant pages and the meta:* discovery door. Those receivers can still refuse you; the refusal comes from the handler, not from the label.

Two further limits on what a label means. It never decides which workspace you may touch — the handlers own that, walking the group tree. And a bare agent key is blocked at the edge from five human-management labels (manage_workspace, manage_clients, manage_members, manage_groups, manage_actors) even where the rest of the label set would let it through. Authority and access covers the walk.

Known limits

  • simulate: true checks the payload, not the permission. It returns {"outcome":"result","simulated":true,"wouldAccept":{…}} before the auth gate runs. An anonymous simulate of groups:invite returns simulated: true; the same call for real returns outcome: "failure" with required: "manage_members". A green simulate is not an authorisation check. Two receivers are the exception — workflow:create and workflow:apply-diff run their own validation, so the flag is passed through to the handler and those calls do reach the auth gate.
  • timeout is reached by one path only. It comes from the legacy external-processor path, which runs only when NANOCLAW_URL is configured. The route’s own comment records that production no longer sets it, so in practice a call closes as result, dissolved or failure.
  • /api/signal/<receiver> is not a public door. It authenticates against a server secret and returns 401 to a developer key. The 20 receivers declared effect: "signal" are reachable through /api/ask/<receiver> like the rest.
  • The published OpenAPI spec is behind the registry. It embeds 181 receiver payload schemas against 330 declared. meta:schema is generated from the registry at request time and is current; use it when the spec is silent.
  • Posting payload fields at the top level fails with a 400. The route answers {"error":"envelope_missing", …} with a worked example. Everything goes under data.

Next