Connect a network in four commands
Everything Baton Cloud does is reachable from the CLI. There is no operation here that needs a browser, and the panel never calls this service directly — it asks your local CLI, and the CLI talks to us.
1 · Point Baton at a deployment
The endpoint is configuration with a default, not a constant. That is what makes Baton Cloud a provider rather than the provider.
export BATON_CLOUD_URL=https://dev.batoncloud.org baton cloud status --output json
Status is three values, never two: connected, not_registered, unreachable. Unreachable is not the same as unregistered — the first says we do not know, the second would send you to register something you may already have.
2 · Register the network
Two credentials are involved and they prove different things. A browser sign-in proves who may claim a name; a signature from your control plane proves which network is being registered. Neither substitutes for the other.
baton cloud register # identity only, no hosted name baton cloud register my-team # identity + claim a hosted name
The name is optional on purpose. Registering an identity and claiming a name are separable acts — a network that already has nike-agents.dev resolves through DNS and never needs a hosted name, but still registers so that later signatures are checkable.
3 · How a hosted name is written
A hosted network name is a label, not a hostname. It becomes the first half of an address local-part:
hosted network nike@batoncloud.org
agent nike.coder@batoncloud.org network folds into the local-part
own domain network agents.nike.com the network IS the domain
agent coder@agents.nike.comSo the same word network lives in two different places depending on which tier you are in. In the second tier we are not involved in your naming at all — you prove control of the domain with a TXT record and Baton Cloud never learns the name. Three rules follow from the address being something a person has to say out loud and write down:
- No dots in a network name. The dot separates network from agent; a dot inside the name would make
a.b.c@batoncloud.orgambiguous with no rule to choose a reading. - Case carries no meaning. Names are lowercased, so two names differing only by case are one name.
- No
+. It already means sub-addressing in an email address.
4 · Authorise a provider
Two keypairs. Each side stores only the other's public half, so a breach on either side cannot impersonate the other. There is no shared secret anywhere in this model and no route that accepts one.
Your network Baton Cloud SK_A / PK_A SK_C / PK_C A → Cloud SK_A signs ─────────> verify PK_A "I am Network A" Cloud → A verify PK_C <───────── SK_C signs "I am the Cloud A authorised"
Create a binding in the dashboard, copy the passkey it mints, and install it locally:
baton integrations add --passkey-file ./passkey.pem \ --provider baton-cloud --scopes skill.install
5 · Authenticate the CLI with a network key
For reads that act as the network rather than as your account, exchange a signed attestation for a short-lived token:
POST /api/auth/network/token
{ "network_id": "net_…", "payload": "<base64>", "signature": "<base64>" }
payload, newline-separated:
cloud-token
<network_id>
<audience> e.g. https://dev.batoncloud.org
<RFC3339 UTC timestamp>
<uuid nonce>
→ { access_token, token_type: "Bearer", expires_in: 900, scope: "network" }This token cannot claim a name or touch billing. That limit is mechanical rather than a matter of good behaviour: the token carries no account identity at all, so the routes that need one cannot load it. A network key proves what a network is — never what its owner is entitled to claim.
Errors
Every failure has the same three fields, and the third is written for a person.
{ "code": "CLOCK_SKEW",
"message": "the timestamp is 612s away from this service's clock",
"remediation": "Check this machine's clock — this is not a key problem." }CLOCK_SKEW is deliberately its own code and never shares one with a bad signature. When a clock has drifted every request fails, and an operator told "invalid signature" will spend the outage investigating a key that was never broken.
The full route list, request shapes and status codes are in the OpenAPI document.