/api/v1. The orbit command line and the PHP SDK are clients of this API and add nothing to it: every CLI command on the CLI pages is one of the endpoints in this reference.
Reach the Gateway
The API listens on HTTPS on the Gateway’s WireGuard address. Orbit private DNS resolvesgateway.orbit to that address for every WireGuard peer, so a client uses https://gateway.orbit as its base URL, or the WireGuard IP that orbit gateway:add stored in the profile.
The Gateway presents a certificate from the Orbit root certificate authority (CA). GET /api/v1/ca/root returns that certificate and its fingerprint without authentication so a new client can pin it; orbit gateway:add and orbit gateway:trust do this for you and install the certificate in the operating-system trust store. A client that already trusts the certificate passes it as its CA bundle, for example with curl --cacert.
Redirects are never followed. The Gateway answers JSON for every endpoint except the two deployment streams, which answer newline-delimited JSON, and the runtime activation page, which answers HTML for Caddy.
Identify the caller
There is no token. The Gateway identifies a caller by the WireGuard address that delivered the request and requires that address to belong to an active Node. A request from any other source receives HTTP 403 with the codepeer.identity_unknown. Two endpoints are public: GET /api/v1/gateway/status and GET /api/v1/ca/root.
The identified Node must also hold Node access to the Node that the request acts on. A Node always has access to itself; an operator grants other edges with node:access:add. A request without the required edge receives HTTP 403 with node_access.required and, in details, the consumer and serving Node identities. Collection endpoints such as GET /api/v1/nodes return only the records the caller may address.
Three endpoints are called by Nodes and Caddy rather than operators: the Schedule completion callback, the Grafana authorization check, and the app-dev runtime activation page. The API sidebar groups them under Node callbacks.
Send a request
Send JSON withContent-Type: application/json and Accept: application/json. The body must be one JSON object with unique top-level keys; the Gateway rejects duplicate or unknown members and a non-object body with HTTP 422 before it changes anything.
Two request headers are optional.
Path parameters are numeric IDs except where noted: Schedules use their UUID, Database connections use their slug, deploy steps and definitions use their name, and the environment endpoints accept either a positive Instance ID or the exact Route domain of the Instance.
Read a response
Every JSON success response is one envelope.data is the record, or an array of records for a collection. meta.request_id repeats the request ID, which the Gateway also returns in the X-Orbit-Request-Id response header. Creation endpoints answer HTTP 201; everything else answers 200, and the two Node callbacks that carry no body answer 204.
Responses never contain a password, an environment value, a certificate private key, a command line, or raw remote output. Credential-shaped values are replaced with [REDACTED].
Read an error
Every refusal is one error envelope with a stable code.
Codes are namespaced by family. The CLI pages and the reference pages list each family’s codes and what the Gateway leaves in place after a refusal.
Follow a request
The Gateway records an Activity for every authorized request that can change something and for every failed request, with the operation, caller, target, result, and request ID, and never the body. It records a successfulGET once per operation, caller, and target in any 60 seconds, and always for Schedule operations and credential reads. Read it with GET /api/v1/activities or orbit activity:list --request-id=<uuid>.
The list is newest first. before_id returns older rows. status, command, caller_node_id, and target_node_id narrow it. The activity command documents each option, and activity explains why.
Set an unmanaged Node’s WireGuard public key
PATCH /api/v1/nodes/{node}/wireguard-key serves node:wireguard-key. The path takes a positive numeric Node ID; name resolution belongs to the CLI. The caller must be an active WireGuard peer with access to the target Node, under the usual identity and access rules.
Send exactly one required member, wireguard_public_key, without a wrapper:
=). The Gateway rejects missing, empty, null, non-string, whitespace-containing, malformed, and noncanonical values, unknown members, and duplicate JSON members with HTTP 422 validation.failed. There is no key removal or private-key input. Validation finishes before any write or hub projection.
The Gateway refuses a Node holding the gateway role, including a non-active assignment, with HTTP 409 node.wireguard_key_gateway. It refuses verified SSH-managed Linux and macOS Nodes (both a nonempty WireGuard address and a pinned SSH host fingerprint) with HTTP 409 node.wireguard_key_managed, regardless of Node status. Check the Gateway guard first when both guards match. A target without a WireGuard address fails with HTTP 422 node.wireguard_ip_required. These refusals leave the registry and hub unchanged. The operation holds the target’s existing Node-name lifecycle lock through the key write and projection; a competing operation on the same Node fails with HTTP 409 node.provisioning_busy before mutation.
Before saving or projecting, the Gateway refuses a public key assigned to any other Node, regardless of lifecycle status or the caller’s access to that Node, or equal to the configured WireGuard hub public key, with HTTP 409 node.wireguard_key_conflict. The refusal leaves the registry and hub unchanged and does not disclose the conflicting Node’s identity. WireGuard identifies peers by public key, so target-only authorization and name locking cannot make duplicate keys safe. Collision checking and assignment must be atomic across all concurrent peer-key assignments, including provisioning; two different Nodes cannot both claim the same key. The target’s own stored key is not a collision unless another Node or the hub also uses it. A valid identical-key retry must still succeed and project. Recovery is to configure a distinct key pair on the unmanaged target and submit its public key, not to remove or change another peer.
On success the Gateway returns HTTP 200 with the standard Node record in data, including wireguard_public_key, and meta.request_id. It records Activity with command node:wireguard-key, caller, target, status, and request ID, without recording the request body. Authorized refusals and projection failures also record Activity under that command. The updated Node uses the normal node.updated broadcast contract.
The request stores the key and synchronously reconverges the existing WireGuard hub on the vpn Node. It does not change the target’s tunnel, keys on disk, address, SSH identity, roles, or lifecycle status. A repeated identical key still projects the hub. A projection failure returns an error rather than a successful Node response; the stored key remains so the caller can repair the hub and retry the same request. vpn.configuration_invalid, vpn.server_lock_failed, vpn.server_config_invalid, vpn.server_config_install_failed, and vpn.server_start_failed use HTTP 422 with the failed step in error.details.step, and never raw remote output. The CLI’s WireGuard key error table owns the recovery instructions. Unknown IDs return the standard HTTP 404 response, and identity or access failures use the standard HTTP 403 codes.
Registry save, hub publication, and Activity completion are not one atomic operation. A crash after save but before publication can leave the new stored key and old live hub configuration. A crash after publication but before completion can leave the new live configuration while Activity later ends with activity.interrupted. Activity alone cannot distinguish those states; the code describes an interrupted request, not an HTTP refusal or proof of rollback. There is no guaranteed rollback or automatic reconciliation after a crash. Restore an authorized management path to the Gateway, using another active peer with access to the target if necessary, then submit the same desired key again. Replay rechecks collisions and republishes even if the stored value already matches. The operator still owns the unmanaged target’s local tunnel configuration.
WireGuard key acceptance cases
Implementation tests must cover collisions with another Node (including one the caller cannot access), the hub public key, and concurrent assignments of one key to different Nodes. Each refused attempt must leave the registry and live hub unchanged without revealing an inaccessible Node’s identity. Identical-key retries must remain valid when no collision exists. A rendered-configuration test must compare the configuration before and after a successful key replacement and prove that every non-target peer’s public key, allowed addresses, and other settings are preserved; only the target peer’s public key changes. Injected-failure tests must stop the operation after the registry save but before publication, and after publication but before completion. They must establish the corresponding old or new hub configuration, the new stored key, eventualactivity.interrupted, and the absence of assumed rollback or automatic reconciliation. For each window, replay the same desired key through a restored authorized management path and prove successful hub convergence with non-target peers preserved. Also retain ordinary projection-failure and same-key retry coverage.
This entry is the docs-first endpoint contract. The generated API sidebar and OpenAPI operation are added from the route, request, SDK, and CLI definitions when the command is implemented; generated files are not hand-edited to advertise a route that does not yet exist.
Stream a deployment
POST /api/v1/instances/{instance}/deploy and POST /api/v1/instances/{instance}/rollback answer application/x-ndjson. Each line is one event with type, a monotonically increasing sequence, and the request_id: phase events name the phase and step, output events carry at most 16 KiB of base64-encoded stdout or stderr, and exactly one final result event reports succeeded or failed with the selected release. A client closes the connection to cancel; the Gateway never replays events. The production release layout describes the phases and limits.