Connecting Clients¶
A client is one agent or application talking to the node. Each client gets its own ID and its own API key (sk-...). Clients are how a team node attributes usage: every request is billed against the client that made it.
Members create and manage their own clients. They cannot see or touch anyone else's.
Two credentials, two jobs¶
The node accepts two entirely different kinds of credential, and confusing them is the most common source of 403s.
| Credential | Header | Purpose | Can do |
|---|---|---|---|
| API key | Authorization: Bearer sk-... |
Inference | Send chat/completion requests. Cannot touch wallets, clients, or npubs. |
| NIP-98 | Authorization: Nostr <base64-event> |
Management | Manage clients, npubs, wallet, node control. Signed per-request by the member's nsec. |
Your agents use the API key. The routstrd CLI uses NIP-98 automatically, which is why it needs your nsec in ~/.routstrd/config.json.
Add a client¶
From the member's own machine:
# Name it explicitly
routstrd clients add --name "My Laptop"
# Or use a one-shot integration setup
routstrd clients add --claude-code
routstrd clients add --pi-agent
routstrd clients add --opencode
routstrd clients add --openclaw
routstrd clients add --hermes
The integration flags configure the agent's own config file as well as registering the client, so you do not have to hand-edit anything. Several can be combined in one call.
On success the CLI prints the credentials and the endpoint to point at:
Client created.
ID: my-laptop
Name: My Laptop
API Key: sk-9f2a...
Access Routstr at: https://team.example.com/v1
The API key is a secret
Treat sk-... like a password. It bills inference to the team wallet. Do not commit it, and do not paste it into a chat — unlike an npub, it is not safe to share.
Adding is idempotent¶
Running clients add with a name that already exists does not create a duplicate. It looks the client up and prints the existing record — including its API key — so re-running is a safe way to recover a key you lost:
Client 'my-laptop' already exists.
ID: my-laptop
Name: My Laptop
API Key: sk-9f2a...
List and delete¶
routstrd clients list
routstrd clients delete my-laptop
clients list shows only your own clients.
Going beyond the CLI¶
The agent integrations cover the common tools, but any OpenAI-compatible client works — point it at the node and use the API key:
curl https://team.example.com/v1/chat/completions \
-H "Authorization: Bearer sk-9f2a..." \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "hello"}]
}'
The base URL is always the node host plus /v1. Discover available models without any credential at all:
curl https://team.example.com/v1/models
How client ownership works¶
This is the part that explains the odd-looking IDs on the node.
IDs are derived from the name¶
A client's ID is the name lowercased with internal whitespace collapsed to hyphens, then stripped of anything that is not alphanumeric or a hyphen. "My Laptop!" becomes my-laptop.
The node appends an owner suffix¶
So that two members can both have a client called my-laptop without colliding, the auth proxy appends the last 7 characters of the owner's npub to the ID before it reaches the daemon:
| Where you look | Client ID |
|---|---|
Member's routstrd clients list |
my-laptop |
On the node (cloudron exec, then routstrd clients list) |
my-laptop-4f2x9k7 |
The suffix is stripped again on the way back, so members always see the clean ID. On the node you deliberately see the suffixed form — the trailing characters are what tell you which member owns a client.
Ownership is recorded explicitly¶
Newly created clients store the owner's npub in an ownerNpub field, and the proxy authorises against that field. Clients created before that field existed fall back to matching the ID suffix, so older installs keep working until those clients are recreated.
Consequence: admins are not automatically superusers here¶
/clients, /clients/add, and /clients/delete are owner-scoped by the calling npub. Even an admin cannot list or delete a colleague's clients through these endpoints. Cross-member visibility comes from running the CLI on the node itself, where the daemon is unauthenticated on loopback:
cloudron exec --app routstr.example.com
routstrd clients list # all clients, all owners, suffixed IDs
That is also the only practical way to clean up a departing member's keys — see Team Members.
Refreshing models and integrations¶
The clients command carries options for the daemon's scheduled refresh job, which updates the Routstr 21 model list and re-syncs client integrations:
routstrd clients --manual-refresh # refresh now, once
routstrd clients --disable-automatic-refresh # stop the scheduled job
routstrd clients --enable-automatic-refresh # start it again
The model list matters because it is also what the model allowlist is enforced against.
Next steps¶
- Usage and Model Policy — watch what those clients are spending.
- Security Model — the exact rules applied to each credential.