Skip to content

Platform

peppy platform login authenticates the CLI against the Peppy backend. Peppy is a public OAuth client of the project’s identity provider (Zitadel): the CLI obtains a bearer token through the browser and sends it to the backend, which validates it. The CLI never sees your Google/passkey credentials. Those stay in the browser.

CommandWhat it does
peppy platform login [--api-url <url>] [--no-browser] [--yes]Logs in via the OAuth 2.0 Device Authorization Grant, caches the tokens, and federates a managed router to your workspace’s cloud router.
peppy platform whoami (alias status)Shows the current identity, backend, and token validity. --json for machine-readable output.
peppy platform logout [--yes]Revokes the access token on the backend (across replicas), deletes the local credentials, and de-federates a managed router.

--yes (-y) skips the daemon-restart confirmation prompt described under Workspace federation.

Terminal window
peppy platform login

On a terminal this prints a verification URL and a user code, then opens your browser at that URL (with the code pre-filled). Approve the request in the browser and the CLI stores the tokens. Subsequent commands reuse them: you log in once and stay logged in until the refresh token expires; expired access tokens are refreshed automatically.

Over SSH or anywhere without a browser, use --no-browser: the CLI prints the URL and code and waits for you to approve them on another device.

Terminal window
peppy platform login --no-browser
  1. The CLI fetches the public GET {api_url}/cli/config, which returns the issuer, the client_id, and the exact scopes to request.
  2. It runs OIDC discovery against the issuer ({issuer}/.well-known/openid-configuration) to find the device and token endpoints.
  3. It starts the device flow, opens the browser, and polls until you approve.
  4. It caches the tokens (and the issuer/client_id, so refresh works offline) under ~/.peppy/conf/credentials.json5.

Logging in does more than cache a token: it stamps this machine with your workspace namespace, using the stable workspace_id returned by the backend. The namespace is messaging routing context only. It is not proof of workspace membership, a tenant-isolation boundary, or an authorization policy, and it does not enforce ACLs. Under zenoh.managed, the daemon also federates its local messaging router to the workspace cloud router. Sessions using the same workspace namespace can interoperate across that federation, while different namespaces take separate routing paths. Logged out, the machine falls back to the local namespace, which never reaches the cloud router; two logged-out machines on the same LAN still discover each other, but nothing leaves the local network.

A session’s namespace is fixed once the daemon opens it. Under zenoh.managed, changing that namespace by logging in or out restarts the messaging daemon and wipes the running node stack. When a managed daemon is running with user nodes, login and logout confirm first:

Logging in changes this machine's workspace namespace, which restarts the messaging daemon and wipes the running node stack.
Continue? [y/N]

Pass --yes (-y) to skip the prompt. It is also skipped automatically when stdin is not a terminal (so scripts and CI are never blocked), when no daemon is running, or when the stack holds no user nodes; in each case the restart wipes nothing.

Under zenoh.managed, login is strict about federation: after your credentials are saved it waits for the daemon to establish the federation link and exits non-zero if it cannot (no daemon running, the cloud router is unreachable or untrusted, or it times out). You stay authenticated in that case (only the command fails), so re-run it once the daemon is reachable. logout is best-effort and never fails on the de-federation step. How long the daemon waits to resolve the cloud router is bounded by zenoh.managed.federation.connect_timeout_secs.

With zenoh.external, login and logout skip federation and print a note instead; the daemon applies the resolved namespace to its sessions on its next manual restart. See Federation outside the Peppy platform.

You do not need the Peppy platform to run nodes across several machines. Point every machine at a Zenoh router you run yourself with zenoh.external in ~/.peppy/conf/peppy_config.json5:

zenoh: {
external: {
endpoint: "tcp/router.internal:7448",
},
},

endpoint is a dial address, so start the router first (for example zenohd -l tcp/0.0.0.0:7448) and make sure every machine, and every node environment on it, can reach that host and port. Peppy dials this router, adopts it, and never starts, restarts, reconfigures, or federates it: its lifecycle is yours. No login, no backend, and no cloud router is involved.

Every session carries a namespace, and it is applied under zenoh.external exactly as it is under zenoh.managed. It is local when logged out and the workspace namespace when logged in (see Workspace federation). External mode changes which router Peppy dials, not how sessions are namespaced: the daemon stamps its own session, and every node it spawns, with whatever namespace the local credentials resolve to. A shared router forwards traffic between namespaces without matching it, so a machine on a workspace namespace and a machine on local discover nothing from each other even though both are connected to your router.

So keep all the machines on the same namespace, and the only namespace you can count on without the platform is local:

Terminal window
peppy platform logout

Then restart the daemon on that machine (see below).

Being signed in is not by itself what changes the namespace. A workspace namespace is cached in ~/.peppy/conf/credentials.json5 under a router block, and only managed-mode federation ever writes it there. peppy platform login clears that block, and external mode never re-pulls it, so a fresh login under zenoh.external still resolves to local.

The case that bites is a machine that previously ran in managed mode while logged in and was then switched to zenoh.external. Its credentials still carry the cached router block, the daemon reads it with no staleness check, and that machine silently comes up on the workspace namespace while its logged-out peers sit on local. Nothing warns about the mismatch. Logging out removes the block; you can also confirm it is gone by checking that credentials.json5 has no router key.

External mode has no federation control socket, so login and logout cannot poke the daemon and never restart it. A namespace change, like any other peppy_config.json5 change, reaches sessions only when the daemon is restarted by hand: stop it (peppy service stop, or systemctl stop your unit) and start it again. A session’s namespace is fixed once it is open, so nothing changes until that restart happens.

  1. Start your Zenoh router and make it reachable from every machine.
  2. On each machine, set zenoh.external.endpoint to that router’s dial address.
  3. On each machine, run peppy platform logout.
  4. Restart the daemon on each machine.

By default the CLI talks to the prod backend, https://api.peppy.bot. That URL is stored in the resource_servers block of ~/.peppy/conf/peppy_config.json5 (see Daemon configuration), seeded on first run and editable in place. To point at a different backend without editing the file, use --api-url or PEPPY_API_URL. The URL is resolved in precedence order: --api-url, then PEPPY_API_URL, then resource_servers.api. Plain http is allowed only for local backends (loopback / *.localhost); anything else must be https.

For non-interactive use, set PEPPY_API_KEY to a Zitadel service-user personal access token (PAT). It is used directly as the bearer: no browser, no refresh, and it is never written to disk. A PAT short-circuits every other credential source, so CI never opens a browser. If the PAT is revoked, requests start failing with 401 and you must rotate it. A PAT principal shows up as kind: "machine" under peppy platform whoami.

Tokens live at ~/.peppy/conf/credentials.json5, written owner-only (0600). The root honors PEPPY_HOME.

Tokens are never printed and Authorization headers are redacted in verbose output.

Terminal window
peppy platform logout

This calls POST {api_url}/logout, which denylists the presented access token across all backend replicas (sub-second), then deletes the local credentials. The effect is near-immediate for the logged-out token. It revokes only the token you presented; a session on another device keeps working.

Logout also returns this machine to the local namespace. Under zenoh.managed, it de-federates the router, restarts the daemon, and wipes the running node stack, with the same confirmation prompt and --yes bypass (see Workspace federation). Under zenoh.external, the operator’s federation is left untouched and the namespace changes on the next manual daemon restart (see Federation outside the Peppy platform).

VariablePurpose
PEPPY_API_KEYPAT for non-interactive auth (highest-priority credential).
PEPPY_API_URLOverride the backend base URL.
PEPPY_HOMEOverride the ~/.peppy data root (also moves the credentials file).
NO_COLORDisable colored output.