---
title: "Receivers"
description: "Every capability on ONE is a named receiver with a typed contract. 330 of them, reached identically from the API, SDK, CLI and MCP."
type: explanation
audience: both
verified: 2026-09-06
canonical: https://one.ie/docs/receivers
---
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](/docs/api),
[SDK](/docs/sdk), [CLI](/docs/cli) and [MCP server](/docs/mcp) 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:

```json
{"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](/docs/agents-routing).

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

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

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

```bash
# 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](/docs/authority) 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

- [Quickstart](/docs/quickstart) — a key and a first call
- [API](/docs/api) — the HTTP door in full
- [Authority and access](/docs/authority) — who may call what
- [Errors and limits](/docs/errors) — the error shapes and rate limits