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:verb — chat: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:
| Field | What it holds |
|---|---|
receiver | the callable name |
summary | one line describing the capability |
request / response | Zod schemas, published as JSON Schema |
effect | ask or signal |
auth | the declared auth label, or nothing |
cost | free, variable, or a fixed number |
reversible, idempotent, settles, simulatable | risk 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:
| Measure | Count |
|---|---|
| Receivers | 330 |
| Families | 78 |
effect: "ask" | 310 |
effect: "signal" | 20 |
Declare an auth label | 265 |
| Declare none | 65 |
| Distinct label strings in use | 29 |
The ten largest families:
| Family | Receivers | Family | Receivers |
|---|---|---|---|
world | 46 | pages | 10 |
tasks | 24 | booking | 8 |
workflow | 18 | broadcast | 8 |
market | 16 | foundation | 7 |
video | 14 | wallet | 7 |
The world count is 45 world:* names plus the bare world door below.
Four doors, one name
| Door | Call |
|---|---|
| API | POST https://one.ie/api/ask/<receiver> with {"data":{…}} |
| SDK | client.ask("<receiver>", data) |
| CLI | npx oneie ask <receiver> --data '{json}' |
| MCP | the 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:
| Name | What it is |
|---|---|
world | the universal router |
notify | reach any actor across whatever channel they use; the target uid goes in the receiver field |
grant-capability | issue 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.
outcome | What produced it |
|---|---|
result | the handler ran and returned |
dissolved | the name is declared but has no handler yet (reason: "no_handler"), the name is unknown (reason: "unknown_receiver"), or the entry tag is toxic |
failure | the route’s auth-label gate refused, or the handler threw |
timeout | the 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:catalogreturns one row per receiver — name, summary, effect, auth label, cost, reversibility, settlement, idempotency, whether it is simulatable.meta:schematakes{ 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:
| Label | Effect on the caller |
|---|---|
public, none, open | no attested caller required |
| any other label | an attested caller is required — a session, a resolved world key, or a verified service credential |
| no label | nothing 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: truechecks the payload, not the permission. It returns{"outcome":"result","simulated":true,"wouldAccept":{…}}before the auth gate runs. An anonymous simulate ofgroups:invitereturnssimulated: true; the same call for real returnsoutcome: "failure"withrequired: "manage_members". A green simulate is not an authorisation check. Two receivers are the exception —workflow:createandworkflow:apply-diffrun their own validation, so the flag is passed through to the handler and those calls do reach the auth gate.timeoutis reached by one path only. It comes from the legacy external-processor path, which runs only whenNANOCLAW_URLis configured. The route’s own comment records that production no longer sets it, so in practice a call closes asresult,dissolvedorfailure./api/signal/<receiver>is not a public door. It authenticates against a server secret and returns 401 to a developer key. The 20 receivers declaredeffect: "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:schemais 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 underdata.
Next
- Quickstart — a key and a first call
- API — the HTTP door in full
- Authority and access — who may call what
- Errors and limits — the error shapes and rate limits