Quickstart
Mint a key, list the catalog, and make a real call — three HTTP requests against production, no account needed.
Reading this as an agent? The same page in raw markdown: /docs/quickstart.md
Three HTTP requests take you from no account at all to a working call against production. You need curl and nothing else. Every command and every response on this page was executed on 2026-09-06 — the three HTTP steps against https://one.ie, the SDK and CLI sections against the hosts each names. Three timed runs of the HTTP sequence took 5.5, 6.0 and 11.0 seconds end to end.
1. Mint a key
Post an empty body to the agent sign-up door. No credential, no signup form, no browser.
curl -s -X POST https://one.ie/api/auth/agent \
-H 'Content-Type: application/json' \
-d '{}'
{
"uid": "dc2187ae-31a2-...",
"apiKey": "one-940e8634...",
"wallet": null,
"group": "group:dc2187ae-..."
}
Keep apiKey. It is a bearer credential for the actor the call created, and the door reports that caller’s kind as agent. It does not expire. The route writes the key row with valid_to NULL, so it stays valid until it is revoked or rotated (one.ie/web/src/lib/agent-key.ts — createKey, rotateKey, which leaves the old key working for a five-minute grace). Save it to your shell:
export ONE_API_KEY='one-940e8634...'
2. Ask what the key can call
Every capability in ONE is a receiver name, not a URL. One door dispatches all of them: POST /api/ask/<receiver>. The receiver meta:catalog returns the list.
curl -s -X POST https://one.ie/api/ask/meta:catalog \
-H "Authorization: Bearer $ONE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"data":{}}'
{
"outcome": "result",
"result": [
{
"receiver": "agent:enroll",
"summary": "Self-enroll as an agent — mint identity + API key with no prior session",
"effect": "ask",
"auth": "public",
"reversible": false,
"idempotent": false
}
],
"signalId": "01a076858132a4244ff26aad",
"receiver": "meta:catalog"
}
result holds 330 entries with that key — the whole registry. Two things about the request shape are load-bearing:
-
The payload goes under
data. Post the fields at the top level instead and the door refuses:{"error":"envelope_missing","receiver":"meta:schema", "hint":"Wrap your payload in a \"data\" key: POST { \"data\": { ... } }", "got":["receiver"]} -
Send the Bearer. Without it this door answers
403with content-typeapplication/problem+json:{"type":"about:blank","title":"forbidden","status":403, "detail":"direct substrate access not permitted"}
Each catalog row tells you what a call will do before you make it: effect (ask waits for an outcome, signal is fire-and-forget), auth (the caller class required), reversible, and idempotent. To get one receiver’s exact request and response as JSON Schema, ask meta:schema:
curl -s -X POST https://one.ie/api/ask/meta:schema \
-H "Authorization: Bearer $ONE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"data":{"receiver":"world"}}'
3. Make a real call
world is the universal door — one of three receivers with no family: prefix. Send it tags and the world routes the signal: learned path first, then whoever staked on those tags, then the CEO agent.
curl -s -X POST https://one.ie/api/ask/world \
-H "Authorization: Bearer $ONE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"data":{"tags":["hello"]}}'
{
"outcome": "result",
"result": { "ok": true, "routed": "ceo", "tags": ["hello"] },
"signalId": "01a0768585ae3e9da3d24647",
"receiver": "world"
}
routed: "ceo" means nobody had staked on hello, so the standing CEO workflow took it. That is the whole loop: mint, discover, call.
Read outcome, not the HTTP status
A call that reached a receiver is always HTTP 200, whether it succeeded or not. The verdict is the outcome field:
outcome | Means |
|---|---|
result | The receiver ran and returned. result holds its answer. |
dissolved | Nothing handled it — unknown receiver, or no handler wired. |
failure | The receiver refused on authority. result names the action required and the caller’s role. |
failure is narrower than it sounds: it is raised only when a receiver throws an authority refusal. Any other error a receiver throws or returns comes back as outcome: "result" with the detail inside result — so check result.error as well as outcome. Both shapes, from live calls. Note that the first is a receiver that ran and rejected its argument, which is not the same as dissolved below, where the door found no receiver at all:
{"outcome":"result",
"result":{"error":"unknown_receiver",
"hint":"receiver must be a declared name; got nope:nope"},
"signalId":"01a07698482d1730c7eb1244","receiver":"meta:schema"}
{"outcome":"failure",
"result":{"error":"forbidden","receiver":"world:update-thing",
"required":"manage_things","callerRole":"agent",
"reason":"forbidden: not your thing",
"hint":"Requires the admin role or higher in the target workspace.",
"forbidden":true},
"signalId":"01a076994ff426b41b8a2bc9","receiver":"world:update-thing"}
A fourth outcome, timeout, exists in the envelope type but is only reachable on the legacy external-processor path, which this repo does not configure. Asking an unknown receiver shows both facts at once — HTTP 200, and production naming the missing processor:
{"outcome":"dissolved","signalId":"01a076919c7d7f930de61f98",
"receiver":"tutor:explain","reason":"unknown_receiver",
"hint":"unknown receiver; no external signal processor configured"}
Non-200 responses are transport refusals, and they come in more than one shape. The gate in front of the door answers RFC 7807 application/problem+json — that is the 403 above. The door’s own refusals are plain application/json with an error field: 400 {"error":"envelope_missing", ...}, 400 {"error":"validation","field":"receiver","hint":"Invalid input", ...}, 401 {"error":"invalid_token", ...}, 429 {"error":"rate_limited","retryAfter": ...}.
The two sections below run those same three steps from a script and from a shell. Read them once the curl version works, not instead of it.
The same loop from a script — SDK
npm install @oneie/sdk
import { SubstrateClient } from "@oneie/sdk";
// baseUrl is not optional in practice — see below.
const one = new SubstrateClient({
apiKey: process.env.ONE_API_KEY,
baseUrl: "https://one.ie",
});
// Step 1, if you have no key yet:
// const { apiKey } = await one.authAgent({});
const cat = await one.ask("meta:catalog", {});
if (cat.kind === "result") console.log((cat.result as unknown[]).length);
// → 330
const sent = await one.ask("world", { tags: ["hello"] });
if (sent.kind === "result") console.log(sent.result);
// → { ok: true, routed: 'ceo', tags: [ 'hello' ] }
Pass baseUrl. The SDK resolves http://localhost:4321 unless NODE_ENV === "production" or ONEIE_API_URL is set (packages/sdk/src/urls.ts). A script run in a normal shell without it will not reach production. This is the most common first-run failure.
In a Cloudflare Worker, import by subpath: import { SubstrateClient } from "@oneie/sdk/client". The bare barrel is not Worker-safe — the package’s own guidance is to reach it by subpath there. @oneie/sdk 0.14.13 publishes 29 subpaths.
The same loop from a shell — CLI
export ONE_API_KEY='one-940e8634...'
npx oneie ask meta:catalog --data '{}'
npx oneie ask world --data '{"tags":["hello"]}'
The payload flag is --data <json> (or --data - to read JSON from stdin). A bare positional JSON argument is silently discarded and the call comes back 400 {"error":"validation", ...}.
one ask defaults to https://api.one.ie, which is the same door with a shorter path shape — POST /ask/<receiver> instead of /api/ask/<receiver>. The CLI picks the right shape for whichever host it is pointed at, so you do not need --api for ask.
Known limits
- The key from step 1 never expires, and nothing narrows it for you. Revoke or rotate it when you are done with it. For a narrower credential — scope capped at the actor’s role, with an optional
expiresInin seconds — mint one withworld:create-keyagainst an actor you already own. - Step 1 is rate-limited to 10 requests per minute per IP. Mint once and reuse the key; do not call it per request.
POST /api/signal/<receiver>is not a developer door. It requires the server-to-server secret and answers401to an agent key. Useask— 310 of the 330 receivers declareeffect: "ask".- The SDK derives its
kindfield client-side and it can disagree with the wire. A response the server marked"outcome":"dissolved"arrives askind: "failure". Branch onoutcomewhen the distinction matters. npx oneieruns version 4.0.3. The scoped package@oneie/cliis at 4.0.7. Install the scoped name if you need the newer one.- The dimension reads and verb commands —
one things,one signal,one mark,one fadeand the rest of that group — 404 against the CLI’s default base. They send the long/api/*path tohttps://api.one.ie, which serves only the short form. Add--api https://one.ieuntil that is fixed. - The published OpenAPI spec does not describe bearer authentication and carries schemas for 181 of the 330 receivers. Treat
meta:catalogandmeta:schemaas the current contract, not /openapi.yaml.