# x0x API Reference
Complete REST and WebSocket reference for the `x0xd` daemon and the matching `x0x` CLI.
- Default daemon base URL: `http://127.0.0.1:12700`
- Override from the CLI with: `x0x --api 127.0.0.1:12700 ...`
- Named instances use their own auto-discovered local API port: `x0x --name alice ...`
## Response shape
Most successful endpoints return a **flattened** JSON object with `ok: true` plus resource-specific fields.
There is **not** a single universal `data` wrapper.
Examples:
```json
{"ok":true,"status":"healthy","version":"0.16.0","peers":4,"uptime_secs":300}
```
```json
{"ok":true,"agent_id":"...","machine_id":"...","user_id":null}
```
Errors use:
```json
{"ok":false,"error":"description"}
```
## System
| GET | `/health` | `x0x health` | Health probe |
| GET | `/status` | `x0x status` | Runtime status, bound API address, connectivity, peers, warnings |
| POST | `/shutdown` | `x0x stop` | Gracefully stop the daemon |
### Example: health
```bash
curl http://127.0.0.1:12700/health
# {"ok":true,"status":"healthy","version":"0.16.0","peers":4,"uptime_secs":300}
```
### Example: status
```bash
curl http://127.0.0.1:12700/status
# {
# "ok": true,
# "status": "connected",
# "version": "0.16.0",
# "uptime_secs": 300,
# "api_address": "127.0.0.1:12700",
# "external_addrs": ["203.0.113.5:5483"],
# "agent_id": "8a3f...",
# "peers": 4,
# "warnings": []
# }
```
## Identity
| GET | `/agent` | `x0x agent` | Local agent identity |
| POST | `/announce` | `x0x announce` | Re-announce identity to the network |
| GET | `/agent/user-id` | `x0x agent user-id` | Current user ID if configured |
| GET | `/agent/card` | `x0x agent card` | Generate a shareable identity card |
| POST | `/agent/card/import` | `x0x agent import` | Import a card into contacts |
### Announce request body
```json
{
"include_user_identity": false,
"human_consent": false
}
```
Notes:
- Set `include_user_identity: true` only when the daemon has a configured user key.
- Set `human_consent: true` when intentionally sharing human identity.
### Agent card query params
`GET /agent/card?display_name=Alice&include_groups=true`
## Network
| GET | `/peers` | `x0x peers` | Connected gossip peers |
| GET | `/presence` | `x0x presence` | Presence view of online agents |
| GET | `/network/status` | `x0x network status` | NAT and connectivity diagnostics |
| GET | `/network/bootstrap-cache` | `x0x network cache` | Bootstrap cache stats |
## Gossip messaging
| POST | `/publish` | `x0x publish <topic> <payload>` | Publish a base64 payload to a topic |
| POST | `/subscribe` | `x0x subscribe <topic>` | Create a topic subscription |
| DELETE | `/subscribe/:id` | `x0x unsubscribe <id>` | Remove a subscription |
| GET | `/events` | `x0x events` | SSE stream of subscribed messages |
### Publish request body
```json
{
"topic": "updates",
"payload": "aGVsbG8="
}
```
## Discovery
| GET | `/agents/discovered` | `x0x agents list` | List discovered agents |
| GET | `/agents/discovered/:agent_id` | `x0x agents get <agent_id>` | Get one discovered agent |
| POST | `/agents/find/:agent_id` | `x0x agents find <agent_id>` | Actively look up an agent |
| GET | `/agents/reachability/:agent_id` | `x0x agents reachability <agent_id>` | Reachability heuristics |
| GET | `/users/:user_id/agents` | `x0x agents by-user <user_id>` | List agents linked to a user |
Query params:
- `/agents/discovered?unfiltered=true`
- `/agents/discovered/:agent_id?wait=<seconds>`
## Contacts, machines, and trust
### Contacts
| GET | `/contacts` | `x0x contacts list` | List contacts |
| POST | `/contacts` | `x0x contacts add ...` | Add a contact |
| POST | `/contacts/trust` | `x0x trust set ...` | Quick trust update |
| PATCH | `/contacts/:agent_id` | `x0x contacts update ...` | Update trust or identity type |
| DELETE | `/contacts/:agent_id` | `x0x contacts remove <agent_id>` | Remove a contact |
| POST | `/contacts/:agent_id/revoke` | `x0x contacts revoke ...` | Revoke a contact |
| GET | `/contacts/:agent_id/revocations` | `x0x contacts revocations <agent_id>` | List revocations |
### Machines
| GET | `/contacts/:agent_id/machines` | `x0x machines list <agent_id>` | List machine records |
| POST | `/contacts/:agent_id/machines` | `x0x machines add <agent_id> <machine_id> [--pin]` | Add a machine record |
| DELETE | `/contacts/:agent_id/machines/:machine_id` | `x0x machines remove <agent_id> <machine_id>` | Remove a machine record |
| POST | `/contacts/:agent_id/machines/:machine_id/pin` | `x0x machines pin <agent_id> <machine_id>` | Pin a machine |
| DELETE | `/contacts/:agent_id/machines/:machine_id/pin` | `x0x machines unpin <agent_id> <machine_id>` | Unpin a machine |
### Trust evaluation
| POST | `/trust/evaluate` | `x0x trust evaluate <agent_id> <machine_id>` | Evaluate trust decision for a pair |
### Example: add machine
```bash
curl -X POST http://127.0.0.1:12700/contacts/<agent_id>/machines \
-H "Content-Type: application/json" \
-d '{"machine_id":"<hex>","pinned":true}'
```
Trust levels: `blocked`, `unknown`, `known`, `trusted`
Identity types: `anonymous`, `known`, `trusted`, `pinned`
## Direct messaging
| POST | `/agents/connect` | `x0x direct connect <agent_id>` | Establish a direct connection |
| POST | `/direct/send` | `x0x direct send <agent_id> <message>` | Send a direct base64 payload |
| GET | `/direct/connections` | `x0x direct connections` | List active direct connections |
| GET | `/direct/events` | `x0x direct events` | SSE stream of direct messages |
### Direct send request body
```json
{
"agent_id": "8a3f...",
"payload": "aGVsbG8="
}
```
## MLS encrypted groups
| POST | `/mls/groups` | `x0x groups create` | Create an encrypted group |
| GET | `/mls/groups` | `x0x groups list` | List groups |
| GET | `/mls/groups/:id` | `x0x groups get <group_id>` | Group details |
| POST | `/mls/groups/:id/members` | `x0x groups add-member ...` | Add a member |
| DELETE | `/mls/groups/:id/members/:agent_id` | `x0x groups remove-member ...` | Remove a member |
| POST | `/mls/groups/:id/encrypt` | `x0x groups encrypt <group_id> <payload>` | Encrypt plaintext for the group |
| POST | `/mls/groups/:id/decrypt` | `x0x groups decrypt ... --epoch <n>` | Decrypt ciphertext |
| POST | `/mls/groups/:id/welcome` | `x0x groups welcome <group_id> <agent_id>` | Create a welcome message |
### Encrypt request body
```json
{
"payload": "c2VjcmV0"
}
```
### Decrypt request body
```json
{
"ciphertext": "...base64...",
"epoch": 1
}
```
## Named groups
| POST | `/groups` | `x0x group create <name>` | Create a named group |
| GET | `/groups` | `x0x group list` | List named groups |
| GET | `/groups/:id` | `x0x group info <group_id>` | Get group info |
| GET | `/groups/:id/members` | `x0x group members <group_id>` | List named-group members |
| POST | `/groups/:id/members` | `x0x group add-member <group_id> <agent_id>` | Creator-authored member add (propagates to subscribed peers) |
| DELETE | `/groups/:id/members/:agent_id` | `x0x group remove-member <group_id> <agent_id>` | Creator-authored member removal (propagates to subscribed peers) |
| POST | `/groups/:id/invite` | `x0x group invite <group_id>` | Generate an invite link |
| POST | `/groups/join` | `x0x group join <invite>` | Join via invite |
| PUT | `/groups/:id/display-name` | `x0x group set-name <group_id> <name>` | Set your display name |
| GET | `/groups/:id/state` | `x0x group state <group_id>` | **Phase D.3**: inspect the signed state-commit chain |
| POST | `/groups/:id/state/seal` | `x0x group state-seal <group_id>` | **Phase D.3**: advance the chain + republish signed card |
| POST | `/groups/:id/state/withdraw` | `x0x group state-withdraw <group_id>` | **Phase D.3**: seal terminal withdrawal; evicts public card on peers |
| POST | `/groups/:id/send` | `x0x group send` | **Phase E**: publish a signed message to a SignedPublic group |
| GET | `/groups/:id/messages` | `x0x group messages` | **Phase E**: retrieve cached public messages (non-members on Public read) |
| GET | `/groups/discover/nearby` | `x0x group discover-nearby` | **Phase C.2**: presence-social browse of PublicDirectory groups |
| GET | `/groups/discover/subscriptions` | `x0x group discover-subscriptions` | **Phase C.2**: list active shard subscriptions |
| POST | `/groups/discover/subscribe` | `x0x group discover-subscribe` | **Phase C.2**: subscribe to a tag/name/id shard |
| DELETE | `/groups/discover/subscribe/:kind/:shard` | `x0x group discover-unsubscribe` | **Phase C.2**: unsubscribe from a shard |
| DELETE | `/groups/:id` | `x0x group leave <group_id>` | Leave or delete the group |
### Phase C.2 — distributed shard discovery
x0x indexes `PublicDirectory` groups across **tag / name / exact-id
shards** over PlumTree gossip. No DHT, no special node roles.
Topic format: `x0x.directory.{tag|name|id}.{shard}` where
`shard = BLAKE3(domain || lowercase(key)) % 65536`.
- A group's tags fan out to tag shards (one per tag).
- The group name fans out to name shards (one per whitespace-delimited word).
- The `group_id` fans out to exactly one id shard.
Peers subscribe to shards of interest via
`POST /groups/discover/subscribe {"kind":"tag","key":"ai"}`. Subscriptions
persist to `~/.x0x/directory-subscriptions.json` and are restored on
restart with random jitter (0–30s) to avoid anti-entropy storms.
Messages on shard topics are `DirectoryMessage::{Card, Digest, Pull}`:
- `Card` — signed `GroupCard` (data plane). Receivers verify the
authority signature before caching; unsigned or bad-sig cards are
dropped. A defensive check drops any non-PublicDirectory card that
leaks onto a public shard.
- `Digest` — periodic AE summary of known entries
`(group_id, revision, state_hash, expires_at)`.
- `Pull` — peer asks the authority to re-broadcast specific group_ids
it's missing or has at a stale revision.
**Privacy contract (hard guarantees):**
- `Hidden` — never published to any topic.
- `ListedToContacts` — never published to public shards; delivered
pairwise to Trusted/Known contacts via direct-message framing
(`X0X-LTC-CARD-V1\n<card-json>`).
- `PublicDirectory` — published to tag + name + id shards.
### Phase E — public-group messaging
For groups whose `confidentiality == SignedPublic` (the `public_open`
and `public_announce` presets), messages are signed ML-DSA-65 artefacts
on `x0x.groups.public.{group_id}`:
```rust
GroupPublicMessage {
group_id, state_hash_at_send, revision_at_send,
author_agent_id, author_public_key, author_user_id,
kind: Chat | Announcement,
body, timestamp, signature,
}
```
Write-access is enforced at both endpoint and ingest:
- `MembersOnly` — only active members may send.
- `ModeratedPublic` — any non-banned author may send (moderators
remove inappropriate content later).
- `AdminOnly` — only `Admin` or `Owner` may send.
Banned authors are rejected in **every** write-access mode. `POST
/groups/:id/send` also rejects `MlsEncrypted` groups (route to
`/secure/encrypt` instead). `GET /groups/:id/messages` returns the
cached history:
- `read_access == Public` — open to any caller with a valid API token.
- `read_access == MembersOnly` — requires active membership.
- `MlsEncrypted` — returns 400 (encrypted history belongs elsewhere).
### Phase D.3 — state-commit chain
Each named group maintains a signed commit chain:
- `GET /groups/:id/state` returns `{ group_id (stable), genesis,
state_revision, state_hash, prev_state_hash, security_binding,
withdrawn, roster_root, policy_hash, public_meta_hash }`.
- `POST /groups/:id/state/seal` (owner/admin) advances the chain by one
revision and republishes the authority-signed public `GroupCard` to
the global discovery topic. Returns the signed `GroupStateCommit`.
- `POST /groups/:id/state/withdraw` (owner) seals a terminal
higher-revision commit with `withdrawn=true` and broadcasts the
withdrawal card. Peers evict stale listings on receipt regardless of
TTL.
Cards and commits carry ML-DSA-65 signatures. Peers verify both the
signature and the chain link (`prev_state_hash`) before accepting; stale
revisions are silently dropped.
**Honest scope — v1 secure model is GSS, not MLS TreeKEM.** See
`docs/primers/groups.md` for what GSS provides and does not provide.
## Collaborative task lists
| GET | `/task-lists` | `x0x tasks list` | List task lists |
| POST | `/task-lists` | `x0x tasks create <name> <topic>` | Create a task list |
| GET | `/task-lists/:id/tasks` | `x0x tasks show <list_id>` | List tasks |
| POST | `/task-lists/:id/tasks` | `x0x tasks add ...` | Add a task |
| PATCH | `/task-lists/:id/tasks/:tid` | `x0x tasks claim/complete ...` | Update task state |
Update task request body:
```json
{"action":"claim"}
```
or
```json
{"action":"complete"}
```
## Key-value stores
| GET | `/stores` | `x0x store list` | List stores |
| POST | `/stores` | `x0x store create <name> <topic>` | Create a store |
| POST | `/stores/:id/join` | `x0x store join <topic>` | Join an existing store |
| GET | `/stores/:id/keys` | `x0x store keys <store_id>` | List keys |
| PUT | `/stores/:id/:key` | `x0x store put <store_id> <key> <value>` | Put a base64 value |
| GET | `/stores/:id/:key` | `x0x store get <store_id> <key>` | Get a value |
| DELETE | `/stores/:id/:key` | `x0x store rm <store_id> <key>` | Remove a value |
### Store put request body
```json
{
"value": "aGVsbG8=",
"content_type": "text/plain"
}
```
## File transfers
| POST | `/files/send` | `x0x send-file <agent_id> <path>` | Create an outgoing transfer |
| GET | `/files/transfers` | `x0x transfers` | List transfers |
| GET | `/files/transfers/:id` | `x0x transfer-status <transfer_id>` | Inspect one transfer |
| POST | `/files/accept/:id` | `x0x accept-file <transfer_id>` | Accept a pending transfer |
| POST | `/files/reject/:id` | `x0x reject-file <transfer_id> [--reason ...]` | Reject a pending transfer |
### Send-file request body
```json
{
"agent_id": "8a3f...",
"filename": "notes.txt",
"size": 1234,
"sha256": "...hex..."
}
```
### Reject-file request body
```json
{"reason":"rejected by user"}
```
## Upgrade
| GET | `/upgrade` | `x0x upgrade` | Check for updates |
## WebSocket and GUI
### WebSocket endpoints
| GET | `/ws` | — | General-purpose WebSocket session |
| GET | `/ws/direct` | — | WebSocket session that auto-receives direct messages |
| GET | `/ws/sessions` | `x0x ws sessions` | Inspect active WebSocket sessions |
### WebSocket protocol
Client → server:
```json
{"type":"ping"}
{"type":"subscribe","topics":["topic-a","topic-b"]}
{"type":"unsubscribe","topics":["topic-a"]}
{"type":"publish","topic":"topic-a","payload":"aGVsbG8="}
{"type":"send_direct","agent_id":"hex64...","payload":"aGVsbG8="}
```
Server → client:
```json
{"type":"connected","session_id":"uuid","agent_id":"hex64..."}
{"type":"message","topic":"topic-a","payload":"aGVsbG8=","origin":"hex64..."}
{"type":"direct_message","sender":"hex64...","machine_id":"hex64...","payload":"aGVsbG8=","received_at":1234567890}
{"type":"subscribed","topics":["topic-a","topic-b"]}
{"type":"unsubscribed","topics":["topic-a"]}
{"type":"pong"}
{"type":"error","message":"..."}
```
### GUI
| GET | `/gui` | `x0x gui` | Open the embedded browser UI |
| GET | `/gui/` | — | Alias for `/gui` |
## Error handling
Common status codes:
| 200 | Success |
| 201 | Created |
| 400 | Bad request |
| 403 | Forbidden |
| 404 | Not found |
| 422 | Invalid JSON body / schema mismatch |
| 500 | Internal error |
| 503 | Service temporarily unavailable |
## CLI quick examples
```bash
x0x health
x0x status
x0x agent
x0x contacts list
x0x publish updates hello
x0x direct connect <agent_id>
x0x direct send <agent_id> hello
x0x groups create
x0x group create team-chat --display-name alice
x0x tasks create inbox team.tasks
x0x store create notes team.notes
x0x send-file <agent_id> ./notes.txt
x0x transfer-status <transfer_id>
x0x accept-file <transfer_id>
x0x reject-file <transfer_id> --reason "not now"
x0x ws sessions
x0x gui
```
See also: [docs/api.md](api.md), [troubleshooting.md](troubleshooting.md), [patterns.md](patterns.md)