Skip to content

Platform

peppy platform connects this machine to the Peppy platform. Signing in gives the CLI a session for the platform API. Enrolling joins this machine to a project’s cloud messaging router: the CLI mints a private key and a certificate signing request, the platform signs it, and the daemon dials the project’s router with that certificate from then on. peppy platform login does both, one after the other: it signs you in, then enrolls this machine in the project you select, unless you decline. peppy platform enroll enrolls a machine that already has a session.

The session is yours; the enrollment is the machine’s. Logging out does not unenroll, and the daemon stays connected with no session for as long as its certificate is valid. The daemon needs the session for one thing: to renew that certificate.

Peppy is a public OAuth client of the platform’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] [--workspace <id|name>] [--project <id|name>] [--no-enroll] [--yes]Signs in via the OAuth 2.0 Device Authorization Grant, caches the tokens, then enrolls this machine in the project you select.
peppy platform logoutRevokes the session’s tokens at the identity provider and deletes them locally, with the selection. The enrollment stays.
peppy platform workspace show [--json]Shows the selected workspace.
peppy platform workspace list [--json]Lists the workspaces you belong to, the selected one marked.
peppy platform workspace use [<id|name>]Selects the workspace the commands act on.
peppy platform workspace clearRemoves the selected workspace and its project.
peppy platform project show [--json]Shows the selected project.
peppy platform project list [--workspace <id|name>] [--json]Lists the projects of a workspace, the selected one marked.
peppy platform project use [<id|name>] [--workspace <id|name>]Selects the project the commands act on.
peppy platform project clearRemoves the selected project; its workspace stays selected.
peppy platform whoami [--json]Shows the signed-in identity, backend, and token validity.
peppy platform enroll [--workspace <id|name>] [--project <id|name>] [--replace] [--yes]Enrolls this machine as a peer of a project’s cloud router and restarts the daemon under the new identity.
peppy platform unenroll [--local-only] [--yes]Removes this machine’s peer from the platform, deletes the local enrollment, and restarts the daemon standalone.
peppy platform status [--json]Shows the enrollment, whether the daemon runs under it with a verified link, and what the platform reports about the router and this peer.
peppy platform peers [--workspace <id|name>] [--project <id|name>] [--json]Lists the peers of a project’s cloud router, this machine’s project by default.
peppy platform router restart [--workspace <id|name>] [--project <id|name>] [--yes]Restarts a project’s cloud router, which applies every pending change.
peppy platform router start [--workspace <id|name>] [--project <id|name>]Starts a stopped router.

--api-url overrides the backend on any command. --yes (-y) skips the confirmation prompt of the command: the daemon restart described under Enrolling, or the router restart described under The project router.

login and enroll need the peppy daemon to run on this machine: the machine enrolls under the name of its core node, and the daemon is what joins the project router. Start it with peppy service serve, or install it as a background service with peppy service install.

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 sign in once and stay signed in until the refresh token expires; expired access tokens are refreshed automatically. Then login enrolls this machine; see Enrolling at sign-in.

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/auth-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, token and revocation 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.

Every later call to the platform API sends the cached access token as a bearer. When it is about to expire, or the backend answers 401, the CLI exchanges the refresh token for a new one and persists the rotation.

After the sign-in, login enrolls this machine in a project, as enroll does:

  • --workspace and --project name the project, by id or by exact name.
  • With no flag, the only workspace, and the only project of the workspace, are selected with no question. Archived projects do not count. An account with one project is enrolled in it at once.
  • With more than one, a menu asks for the workspace, then for the project. Move with the arrow keys (or k and j) and select with Enter. The last entry of each menu, Do not enroll this machine (sign in only), declines the enrollment, and so do Esc, q and Ctrl-C.
Logged in as [email protected] (dev)
✔ Workspace to enroll this machine in: Robotics lab (team)
? Project of Robotics lab to enroll this machine in ↑/↓ to move, enter to select, esc to cancel
Lab
❯ Field
Do not enroll this machine (sign in only)

The selected workspace and project become the selection, and the enrollment follows:

✔ Project of Robotics lab to enroll this machine in: Field
Enrolling cn-faithful-hare in project Field (ef2288ee-81d8-402e-922b-dbe4a4edbf25) in workspace Robotics lab.
The platform signed the peer certificate. It expires on 2027-01-01, and the daemon renews it from 2026-12-02, while this machine has a session.
Router link verified (tls/rtr-p.us-east-1.robocloud.dev.peppy.bot:7447).

--no-enroll signs in only. A machine that is enrolled already keeps its enrollment, and login prints the project it is enrolled in; peppy platform enroll --replace moves it.

login succeeds when the machine is enrolled, or when you declined the enrollment. Every other way the enrollment does not happen fails the command, with a message that says the session is kept and the command to run next:

  • No daemon runs. login stops before the device flow starts, so you never approve a code for nothing. Start the daemon, or pass --no-enroll.
  • --workspace or --project on an enrolled machine. login stops before the device flow starts: it does not move an enrolled machine.
  • More than one choice and no terminal to show a menu on. login lists the choices and names peppy platform enroll with the flag to pass.
  • The platform refuses the enrollment. See Enrolling.

A stored selection of a different identity or backend is removed at sign-in: its ids mean nothing to the new account.

The selection is the workspace, and the project of that workspace, that you selected. It is the default target of the platform commands, so you name a project one time and not on each command. It lives in ~/.peppy/conf/platform_selection.json5 and belongs to one backend and one signed-in identity. login selects the project it enrolls this machine in.

Terminal window
peppy platform workspace use # select from a menu
peppy platform workspace use "Robotics lab"
peppy platform project use # select from a menu
peppy platform project use Field
peppy platform project use Arm --workspace "Robotics lab"

Each use command picks one candidate:

  • The argument names it, by id or by exact name.
  • With no argument, the only candidate is selected with no question. Archived projects do not count.
  • With more than one, a menu asks on a terminal. It starts on the selected candidate, and Esc cancels it.
  • With more than one and no terminal, the command stops, lists the choices, and names the argument to pass.

project use looks for the project in the workspace that --workspace names, else in the selected workspace, else in the only one; with none of them it asks for the workspace first. workspace use keeps the selected project when it selects the workspace of that project again. A different workspace starts with no project: run project use next.

Terminal window
peppy platform workspace list
peppy platform project list
peppy platform workspace show
peppy platform project show
peppy platform project clear
peppy platform workspace clear

workspace list lists your workspaces, and project list the projects of the selected workspace (or of --workspace); a * marks the selected ones. project clear removes the selected project and keeps its workspace; workspace clear removes both.

CommandOrder
loginThe flags, then the only choice, then the menu.
enroll, project listThe flags, then the selection, then the only choice.
peers, router restart, router startThe flags, then the selected project, then the project this machine is enrolled in.
status, unenrollThe project this machine is enrolled in.

The selection is for the CLI only. A new selection does not move this machine to a different project and does not restart the daemon. When this machine is enrolled in a different project than the selected one, project use, project show and status say so. To move the machine, run peppy platform enroll --replace.

The selection holds the ids and the names from the time you made it. A project can be archived, or you can lose access to it, after that. The platform then refuses the call, and the command adds that the selected project can be out of date. Run peppy platform project use to select again.

Terminal window
peppy platform enroll

Enrollment joins this machine to one project’s cloud router: the selected project, or the one that --workspace and --project name (an id or the exact name). login runs the same enrollment after a sign-in.

The daemon must run. The peer takes the name of the daemon’s core node, so the platform and the other machines of the project know this machine by the name peppy stack list shows. Set that name with core_node_name in ~/.peppy/conf/peppy_config.json5, or with peppy service serve --core-node-name <name>; when neither is set, the daemon derives a name that stays the same for the machine. With no daemon running, enroll stops before it calls the platform.

What happens:

  1. The CLI reads the core-node name of the running daemon, and resolves the workspace and project.
  2. It mints a P-256 key pair and a certificate signing request whose only subject is the peer name. The private key never leaves the machine.
  3. It posts the request to the project’s router/peers endpoint. The platform signs it and answers with the certificate, the project CA, the address of the project router, the routing namespace (the project id), and the zenoh identity the daemon’s router must run under.
  4. It writes the enrollment bundle under ~/.peppy/conf/peer/: enrollment.json5 (the record), peer.key (the private key, owner-only), peer.crt (the signed certificate alone), ca.crt (the project CA), chain.crt (the chain above the certificate, for openssl verify), and zenohd.platform.json5 (the peer config the platform rendered, kept for inspection). The daemon presents peer.crt and nothing above it, so the cloud router closes the link when that certificate expires.
  5. Under zenoh.managed, it pokes the running daemon over its control socket. The daemon’s router identity and session namespace cannot change while live, so the daemon restarts its generation under the new identity and the CLI waits for it to come back. A second poke then verifies the mutual-TLS link to the cloud router with a real handshake; a link that does not validate is reported with its reason, and the enrollment is kept.

The restart wipes the running node stack. When a daemon is running with user nodes on a terminal, the command asks for confirmation first; --yes skips the prompt, and a script (no TTY) is never prompted.

Under zenoh.external the daemon is left alone and the command prints where the material is for the operator’s router to use.

When the platform refuses the enrollment, the command prints the platform’s reason. Two refusals come with a remedy:

  • The router admits no more peers. A removed peer keeps its slot until the router restarts. When the platform says how many slots a restart frees, the command prints the router restart command for a number above zero, and tells you to remove a peer first for zero. When the platform does not say, the command lists the peers of the router: each one that reads pending_restart is a slot that a restart frees.
  • The platform could not sign the certificate in time. The command prints the delay the platform asks for. Run it again after that delay.

The managed router listens on all interfaces in plain TCP, so that container nodes and the other machines of a LAN stack can reach it. On an enrolled machine this also means that a machine on the same network can open a session to this router and, through it, reach the project under this peer’s certificate. Where that is not acceptable, close the messaging port to other machines with the firewall of the host.

The peer certificate is valid for 90 days, and the daemon renews it. The renewal becomes due after two thirds of the lifetime of the certificate, which is day 60 of 90. The lifetime is the one the certificate in peer.crt states, so the schedule follows the certificate and not a clock of this machine. The daemon then asks the platform for a new certificate for the same key and writes it over peer.crt. The peer keeps its id, its zenoh identity and its slot on the router. The daemon does not restart, and the router does not restart.

The running link keeps the certificate it opened with. The cloud router closes that link when that certificate expires, and the daemon’s router opens it again at once with the renewed certificate.

The platform accepts a renewal from a signed-in person only, so the daemon uses the session of this machine:

ConditionResult
A session is on the machine and the daemon runsThe daemon renews the certificate when the renewal is due.
No session (you ran logout, or the refresh token expired)The renewal fails. The daemon tries again each hour. Run peppy platform login.
The session is one of a different backend than the enrollmentThe renewal fails, and the daemon log names the backend to sign in to.
The daemon does not runNo renewal. The daemon renews when it starts, also when the certificate expired.
The platform asks for a delayThe daemon tries again after that delay.
The platform cannot renew the peer (the peer was removed)The daemon stops the renewal and the log names the remedy: peppy platform enroll --replace.

peppy platform status shows the expiry of the certificate, the date from which the daemon renews it, and what the daemon needs for the renewal and does not have:

cert : expires on 2027-01-01 (in 80 days)
renewal : automatic, from 2026-12-02
needs a session: run `peppy platform login`

To mint a new certificate by hand, run:

Terminal window
peppy platform enroll --replace

This enrolls anew (a new key, a new peer and a new zenoh identity), restarts the daemon under it, and removes the previous peer from the platform. The previous peer keeps its slot until the router restarts; see The project router.

An enrolled daemon opens its own session, and every node it spawns, under the project’s routing namespace. Two machines enrolled in the same project share a namespace and address each other across the cloud router, which is the foundation for running one stack across both; see Federation. An unenrolled daemon runs under local. The namespace is routing context only. It is not proof of project membership, a tenant-isolation boundary, or an authorization policy: isolation comes from each project having its own router and its own certificate authority.

Terminal window
peppy platform status

The report has these parts: the enrollment on disk (project, peer, router, certificate expiry and renewal, files), the daemon (running, under the enrolled identity, and whether a fresh handshake to the cloud router validates), the selection, and, when you are signed in, the platform’s view (the router’s phase, the changes that wait for a restart, and this peer’s connection status). peppy platform peers lists every peer of the project as the platform sees them, marking this machine.

A peer reads connected, pending_restart, or “not reported by the platform”. The last one is not a failure: the platform could not read the sessions of the router, so it has nothing to say about the peer. The daemon part of status answers from this machine: it makes a TLS handshake with the router and says if the link is good.

Terminal window
peppy platform unenroll

This removes the peer on the platform, deletes ~/.peppy/conf/peer/, and pokes the daemon, which restarts standalone under local. Removing the peer needs a session; a machine that cannot sign in can delete only its local enrollment with --local-only and have the peer removed from the web app. The same restart prompt and --yes bypass apply as for enroll.

The cloud router reads its trust anchors when it starts. It refuses the removed peer, and frees its slot, from its next restart. The platform does not restart a router by itself: the command prints each change that waits and the router restart command that applies it.

One project has one cloud router, so a project names its router.

Terminal window
peppy platform router restart
peppy platform router start

Which router a command acts on:

FlagsSelected projectThis machineRouter
--project, --workspaceset or notenrolled or notThe project the flags name, by id or exact name.
nonesetenrolled or notThe selected project.
nonenot setenrolledThe project this machine is enrolled in.
nonenot setnot enrolledNone. The command stops and lists your projects.

The command prints the router it found, its phase, its peers and the changes that wait, before it acts.

A restart applies every pending change: a removed peer, a new size. It drops each peer link for a moment, and the peers connect again on their own. The command asks for confirmation on a terminal; --yes skips the prompt.

start starts a router that was stopped. peppy platform status shows the phase of the router and names this command when the router is stopped.

A stopped router takes two commands to apply its changes. The platform refuses the restart of a stopped router, and a start alone does not apply a removal. So router restart on a stopped router stops with the two commands to run, in order: router start, then router restart. unenroll prints the same two commands when the router is stopped.

Both commands need the permission to manage the infrastructure of the project. This is a different permission from the one an enrollment needs, so a person who can enroll a machine cannot always restart the router.

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 sign-in, no backend, and no cloud router is involved.

Every session carries a namespace under zenoh.external exactly as it does under zenoh.managed: local when the machine is not enrolled, the project namespace when it is. A shared router forwards traffic between namespaces without matching it, so an enrolled machine and an unenrolled one discover nothing from each other even though both are connected to your router. Keep every machine on the same namespace; without the platform that is local, so unenroll each machine (peppy platform unenroll --local-only when it has no session) and restart its daemon. External mode has no federation control socket, so enroll and unenroll never restart the daemon: a namespace change reaches sessions only when you restart it by hand (peppy service stop, then start it again).

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. In release builds, plain http is allowed only for local backends (loopback / *.localhost); anything else must be https. Debug builds accept remote plain http with a warning.

Tokens live at ~/.peppy/conf/credentials.json5, written owner-only (0600). The selection lives beside it in platform_selection.json5; it holds ids and names, no secret. The enrollment lives under ~/.peppy/conf/peer/, with the private key and the record owner-only. The root honors PEPPY_HOME.

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

Terminal window
peppy platform logout

This revokes the refresh token and then the access token at the identity provider, then deletes the local session. The backend checks every bearer with the identity provider, so a revoked token stops working at once. It revokes only the tokens this machine holds; a session on another device keeps working.

Signing out removes the selection with the session. It leaves the enrollment in place: the daemon keeps its certificate and its link to the project router. The CLI’s platform calls need a new peppy platform login, and so does the renewal of the certificate: with no session the daemon cannot renew it, and the link stops when the certificate expires. On an enrolled machine, logout prints that expiry date.

VariablePurpose
PEPPY_API_URLOverride the backend base URL.
PEPPY_HOMEOverride the ~/.peppy data root (also moves the credentials file and the enrollment).
NO_COLORDisable colored output.

Running one stack across your project’s machines

Section titled “Running one stack across your project’s machines”

Once two machines are enrolled in the same project they share a namespace and can address each other through the project’s cloud router, which is the foundation for running a single Peppy stack across both. See Federation.