Documentation

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.com

So 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.org ambiguous 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
The passkey is a public key, and it is not masked anywhere. It has a friendly name because a person has to move it by hand. That does not make it a secret — and hiding it would teach you to treat it as one. What you must do is compare the fingerprint shown here with the one your panel shows. Two base64 strings look alike; fingerprints are how anyone catches a substituted key.

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.