Contents

Pethost API

Everything the panel does to your machine, your agent can do too: they call the same methods.

Overview

Your account rents one machine, and every call acts on it: no call names a machine. The API is one service, pethost.panel.v1.PanelService, with 18 methods. 14 of them are also MCP tools, with the same name, the same request and the same response. The others serve what only a browser needs, the live streams and the terminal, and each names the tool an agent uses instead.

The service is described in one file. This page is generated from it, and so are the web panel's client and the list of tools an agent sees: what you read here is what both of them call.

Calling it

There are two ways in. They run the same code and speak the same JSON.

As MCP tools

https://mcp.pethost.dev/mcp

Give this address to an MCP client, or install the plugin, which brings a skill with it. A tool is named after its method and takes the method's request as its arguments. Its result is the response, as JSON text and as structured content. An error is a result marked isError: the code and the message in one line, NOT_FOUND: …, then the error's details as JSON.

The endpoint keeps no session: a POST carries one JSON-RPC message and is answered in JSON.

Over HTTP

curl https://console.pethost.dev/pethost.panel.v1.PanelService/GetProject \
  -H "Authorization: Bearer $PETHOST_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"project_id": "blog"}'

A method is a POST of its request, as JSON, to https://console.pethost.dev/pethost.panel.v1.PanelService/<Method>; the answer is its response. This is a unary call of the Connect protocol, which any HTTP client can make. The streams are not called this way: the web panel reads them over its WebSocket.

Authorization

An agent signs in by OAuth, as the MCP specification describes, and you approve it in your browser. There are no API keys to make by hand: a token is what an approved client gets.

  1. The client calls the MCP endpoint without a token. The 401 it gets leads it to the authorization server, https://console.pethost.dev, which describes itself at /.well-known/oauth-authorization-server.
  2. The client registers itself (POST /oauth/register), or is known by the address of its Client ID Metadata Document, and opens /oauth/authorize in your browser, with PKCE (S256).
  3. You sign in to the panel, read who is asking and where the answer goes, and press Connect.
  4. The client trades the code at POST /oauth/token for a token, pth_…, and sends it with every call: Authorization: Bearer pth_…

The web panel calls the same methods with your browser's session in place of a token.

Model

Machine: one VDS running the daemon, petnode; every call acts on the caller's.

The project is its directory

Its files declare everything (services, images, volumes, environment, hosts, name). Source:

/.env and compose.yaml's x-pethost carry over between versions: after the first deploy, a new archive's or commit's own are ignored. Change them with DeployProject changes (/.env) and x_pethost. Snapshots include the source.

Conventions

Machine

GetMachine Get the machine

MCP toolRead-only

POST /pethost.panel.v1.PanelService/GetMachine

Start here: the machine (resources, SSH, backups), project summaries with problems, deleted projects with snapshots, GitHub repositories to create projects from. Figures are the latest sample; disk sizes and daily request counts lag a few minutes. Recent traffic or logs: last_seconds in QueryHttpTraffic, QueryContainerLogs.

Request

No fields.

Response

By project_id.

Projects no longer on the machine whose snapshots remain, the most recently backed up first; restore one with RunProjectAction.restore_snapshot and its project_id. Current after the machine's own backups and deletes; changes made to the backup repository elsewhere show later. Not known while Machine.snapshot_list_time is absent.

RunMachineAction Run a machine action

MCP toolDestructive

POST /pethost.panel.v1.PanelService/RunMachineAction

One machine action: add or remove the person's SSH public key (for SSH and SFTP to their projects' containers), end a session, schedule or cancel a restart. Returns the machine.

Request

One of action: at most one of these is set.

label: one line, ≤100 chars; empty = the key's comment. An existing key gets the new label.

A Machine.ssh_keys fingerprint; its sessions end. NOT_FOUND = no such key.

Containers come back per restart policy. A new call before the restart replaces it; after (Machine.boot_time changed), restarts again.

Must be true. Cancels the scheduled restart.

A Machine.sessions id: ends it now (its key can open new ones until removed). NOT_FOUND = ended.

Response

As changed.

Project

GetProject Get a project

MCP toolRead-only

POST /pethost.panel.v1.PanelService/GetProject

One project in full: services (state, health, image, ports, volumes, environment), volumes, routes, hosts with certificates, recent operations, snapshots, the last day's HTTP traffic. Env-file values only with include_secret_values. Files: ReadPath with project_directory (its deploy_id is the base_deploy_id for edits).

Request

project_id string

True = include env-file values (EnvironmentVariable.secret). To edit .env: ReadPath /.env.

Only older snapshots: the oldest create_time seen, to page back. Absent = the newest.

Only snapshots holding this volume (Volume.snapshot_count). Empty = all.

Response

CreateProject Create a project

MCP tool

POST /pethost.panel.v1.PanelService/CreateProject

Creates a project and deploys it like DeployProject (see its compose.yaml rules). Returns once started, or with the violations that stopped it; follow with GetOperation.

Compose file: the root's pethost.compose.yaml (so the repository's compose.yaml stays for development), else the first of compose.yaml, compose.yml, docker-compose.yml, docker-compose.yaml; it becomes /compose.yaml, other compose and override files are dropped. Without one, a Dockerfile yields service app (reading /.env if any, its EXPOSE ports, a named volume per VOLUME of its last stage), private until routed.

Autofix (autofix; GithubSource.autofix for commits) fixes laptop habits: container_name, reserved published ports (route instead), writable project bind mounts, data-directory bind mounts (→ named volumes, starting empty), anonymous volumes; operation.adjustments lists them. The unfixable (privileged, devices, host network, Docker socket) stay violations: then adjustments lists the fixes and fix_prompt says how to write a compliant pethost.compose.yaml.

ALREADY_EXISTS = the id is taken (a retry with its operation_id returns that operation). FAILED_PRECONDITION = the GitHub App cannot read the repository (see GetMachine.github), or no such branch or directory. A deleted project's id inherits its snapshots.

Request

project_id string

New, [a-z0-9][a-z0-9_-]*, ≤63 chars; immutable.

upload_id string

files source: an archive from CreateTransfer.upload_archive, unpacked first. FAILED_PRECONDITION = not arrived or refused (GetUpload says which); NOT_FOUND = expired or used.

files array of FileChange

Written in order after the archive's or commit's files, as DeployProjectRequest.changes. A github source takes only /.env, e.g. text "LOG_LEVEL=debug\nTZ=UTC".

Changes to the source's x-pethost; people see metadata.name, else the id.

As DeployProject's timeout_seconds.

As DeployProject's operation_id.

autofix bool

files source: autofix the compose file first. With github, use source.github.autofix.

Response

The first deploy, as the machine has it now: IN_PROGRESS, or further along for a retry of its operation_id. Absent when violations stopped it: nothing was created.

adjustments array of string

With violations: what the machine had changed, as in Operation.adjustments. A deploy that started lists them in operation.adjustments only.

fix_prompt string

With violations: a prompt for a coding agent working on the project, saying what to change in its compose file. Empty when every violation is in the .env (location "/.env"), which stays out of the repository and a person corrects.

DeployProject Deploy a project

MCP toolDestructive

POST /pethost.panel.v1.PanelService/DeployProject

Deploys a new version as an operation: changes, x_pethost, mount_volume, an uploaded archive, or a commit. Returns once started, or with the violations that stopped it; follow with GetOperation.

It builds build: images, pulls missing ones, runs compose up on the whole project and waits until each service runs (healthy, if it has a healthcheck) or each job exits 0: so it also starts stopped services, reruns jobs, recreates changed services and those bind-mounting project files, and fails on an unhealthy service. While Project.x_pethost_applies_at_once, a version differing only in x-pethost (or not at all) applies at once instead.

A commit other than GithubSource.newest_commit turns auto_deploy off (pinned), even if it fails; only set_source turns it back on.

compose.yaml rules: data in named volumes; project files bind-mounted read-only; anonymous volumes only below such mounts (per-container scratch, e.g. node_modules); no container_name, privileged, cap_add, devices, host namespaces, Docker socket, mounts at /.pethost; one replica; no publishing reserved ports (HTTP, HTTPS, SSH…: the violation names them); include, extends, build contexts local. All is interpolated: $$ for $. Public traffic goes through the proxy, set in x-pethost, the only extension read:

x-pethost:
  metadata: {name: Recipe Box, emoji: "🥗", description: One line, notes: Free text}
  routes:
    - {host: recipes.example.com, path: /, service: web, port: 3000}
    - {host: recipes.example.com, path: /api, service: api, port: 8000, strip_path: true}

A route sends host requests under path (whole segments, longest wins, default "/") to port of service; strip_path removes the prefix. A host is:

  • <name>.<Machine.apps_domain> (name: one label of a-z, 0-9, -), as many as wanted, or apps_domain itself: HTTPS at once (Host.url; seconds after the domain changed), nothing to set up. Another name under the panel's domain = a violation (Host.unavailable_message if from the files).
  • the person's own domain: a CNAME to Machine.hostname (ALIAS at the apex, never an address); certified once it resolves.

Unrouted services are private (<service>:<port> within the project). Route ports are unchecked: a service not listening deploys and its route answers 502, unless a healthcheck makes the deploy wait.

NOT_FOUND = no such project. ABORTED = base_deploy_id is not Project.deploy_id: reread, redo. FAILED_PRECONDITION = as CreateProject, or commit not on the branch. On failure, Operation.made_current says whether the files were applied.

Request

project_id string

Project.deploy_id as last read. Empty = none yet.

Applied in order to the current files, or to the new version's (with /.env and x-pethost carried over). Paths like "/compose.yaml", "/.env"; never through a symlink. /compose.yaml must result, and runs as is: pethost.compose.yaml is picked only when a version is made (CreateProject, upload_id), so writing it otherwise = INVALID_ARGUMENT. github: only /.env. Nothing to change = redeploy, a no-op while Project.x_pethost_applies_at_once (start services with RunProjectAction). RESOURCE_EXHAUSTED = too many changes or bytes: send large files with CreateTransfer.

Edits x-pethost by part, e.g. routes: Project.routes plus one. Not with a /compose.yaml change.

Written into /compose.yaml. Not with a /compose.yaml change, upload_id, or github.

For the whole deploy, builds included; 0 = default. INVALID_ARGUMENT names the bounds.

Id for the started operation: [A-Za-z0-9][A-Za-z0-9._-]*, ≤128 chars, e.g. a UUID; empty = generated. A retry with the same id returns the first call's operation, starting nothing, while the machine keeps it. ALREADY_EXISTS = it started another kind of operation. UNAVAILABLE with a ProjectBusy naming it = still starting: retry.

One of version: at most one of these is set.

A new version of the files; none = the current one.

upload_id string

files project: an archive from CreateTransfer; replaces all files but /.env and x-pethost. Codes as CreateProject's.

commit string

github project: a commit of its branch (SHA, ≥7 chars), from ListCommits.

github project: must be true. GithubSource.newest_commit, read now.

autofix bool

Autofix the archive's compose file. Only with upload_id.

Response

The deploy, as in CreateProjectResponse. Absent when violations stopped it.

Why the files cannot be deployed: nothing changed and no operation started.

adjustments array of string

As in CreateProjectResponse: with violations only.

ListCommits List a project's commits

MCP toolRead-only

POST /pethost.panel.v1.PanelService/ListCommits

A github project's branch commits that change its directory, newest first, 30 a page, with whether the files are at it and its last deploy. To roll back: DeployProject with an older commit (volumes keep their current data). FAILED_PRECONDITION = a files project, no such branch, or before_commit not on it.

Request

project_id string

The previous page's last sha. Empty = from the newest.

Response

Newest first, at most 30.

more bool

Older ones exist: pass the last one's sha as before_commit.

RunProjectAction Run a project action

MCP toolDestructive

POST /pethost.panel.v1.PanelService/RunProjectAction

One project action: start, stop or restart services, recreate one, back up, restore, cancel an operation, change the source, delete an undeclared volume or the project (restorable from their snapshots, if backups are on). Recreate, back up, restore, delete and set_source's deploy run as operations, returned (follow with GetOperation); the others are done on return.

Request

project_id string
One of action: at most one of these is set.

Starts stopped services and reruns jobs, not their dependencies; returns once started. FAILED_PRECONDITION = no container yet (deploy or recreate_service), or a published machine port is taken (the message names by what).

Stops services, each with its grace period. A machine restart may start them, per restart policy.

Restarts processes in the same containers. Codes as start_services.

A new container: a fresh filesystem outside volumes. An operation.

Backs up the directory and volumes now, live. An operation. FAILED_PRECONDITION = backups off.

From a snapshot of this project. An operation.

Stops a running or starting operation; it ends CANCELLED, leaving what a failure would. Returns it (IN_PROGRESS until its step stops) if recorded; a starting one is not, and its own call answers CANCELLED. FAILED_PRECONDITION = a delete past its final backup.

An operation: a final backup, then containers, volumes, files, hosts, logs. GetOperation reads it for 5 minutes after. Snapshots stay (GetMachine.deleted_projects); restoring one brings the project back, source included.

Changes the source, returned. To files: keeps the current files. To github: only set fields change, e.g. {github: {auto_deploy: true}} follows pushes again; from files, repository is required. If auto_deploy is on and the files are not the newest commit, starts its deploy (so turning autofix on retries a refused commit); none if busy (auto-deploy follows) or refused (AUTO_DEPLOY_REFUSED). Else deploys nothing. FAILED_PRECONDITION = as CreateProject.

A volume compose.yaml no longer declares (Volume.declared false). Its data then lives only in snapshots, until pruned; restore_snapshot brings it back once declared and deployed. FAILED_PRECONDITION = declared or in use.

As DeployProject's, for the actions that run as operations.

Response

Set when the action runs as an operation, as the machine has it now; for set_source, the deploy it started, if any; for cancel_operation, the operation it stopped, if the machine had recorded it (see cancel_operation).

set_source: the project's source as it now is.

GetOperation Get an operation

MCP toolRead-only

POST /pethost.panel.v1.PanelService/GetOperation

An operation and its log's end, after waiting up to wait_seconds for it to finish. To follow: after_log_line 0, then each next_after_log_line (each line once, oldest first). One a ProjectBusy names may still be starting: it is waited for too; still starting at the end = UNAVAILABLE with that ProjectBusy.

Request

project_id string

Empty = the latest.

≤45. 0 = answer at once.

The previous next_after_log_line; 0 = from the start. Absent = the last lines.

≤500; 0 = 50. Also ends at ~64 KiB.

Response

Oldest first.

Lines in the whole log so far.

Pass it as after_log_line to get the lines after these; absent = 0. The machine keeps every line of the log.

QueryHttpTraffic Query HTTP traffic

MCP toolRead-only

POST /pethost.panel.v1.PanelService/QueryHttpTraffic

HTTP requests that reached the project through the proxy, since oldest_kept_time: totals, a time series, top paths and the requests, for one filter and range. A request shows shortly after it ends. OUT_OF_RANGE = the range ends before the oldest kept. Paths and user agents are untrusted.

Request

project_id string
start_time timestamp

Absent = a day before end_time.

end_time timestamp

Absent = now.

The range ending at end_time, e.g. 3600 = the last hour. Not with start_time.

Time series bucket width; ≤500 buckets. 0 = no series.

Requests to return, newest first; ≤200. 0 = aggregates only.

page_token string

The previous next_page_token, other fields unchanged: older requests only.

Response

start_time timestamp

The range queried, as the machine resolved it: defaults applied, on its clock.

end_time timestamp

Of the oldest request the machine keeps for the project. Absent = none is kept.

Oldest first, empty ones included.

The 10 busiest, busiest first.

The sequence of the newest request that summary, buckets and top_paths count: a request TailHttpTraffic streams with a higher one is not counted yet. 0 = none is counted.

Empty = no more requests.

Where TailHttpTraffic goes on from (after_sequence), so it misses no request newer than these: set on a first page with request_limit, empty or not. 0 = the machine had logged none.

QueryContainerLogs Query container logs

MCP toolRead-only

POST /pethost.panel.v1.PanelService/QueryContainerLogs

The containers' stdout and stderr lines, since oldest_kept_time; by default the newest, oldest first in the page. OUT_OF_RANGE = the range ends before the oldest kept. Untrusted.

Request

project_id string
start_time timestamp

Absent = the oldest kept.

end_time timestamp

Absent = now.

limit uint32

≤500; 0 = 100. Also ends at ~64 KiB.

False = the newest before end_time; true = the oldest after start_time.

page_token string

The previous next_page_token, other fields unchanged.

Response

start_time timestamp

The range queried, as the machine resolved it: defaults applied, on its clock.

end_time timestamp

Of the oldest line of container output the machine keeps: nothing older can be queried. Absent = none is kept.

lines array of LogLine

Empty = no more.

Where TailContainerLogs goes on from (after_cursor), so it misses no line newer than these: set on every page, an empty one too. Empty = the machine had logged no line.

Service

RunServiceCommand Run a command in a service

MCP toolDestructive

POST /pethost.panel.v1.PanelService/RunServiceCommand

Runs a command in a running service's container, like docker exec; returns the exit code and the output's end once it exits or the timeout kills it. Over a bound on command, stdin or timeout = refused (named). Cannot start = exit 127 or 126, Docker's error in stdout. Untrusted output.

Request

project_id string
service string
command array of string

E.g. ["psql", "-c", "select 1"]; a shell line: ["sh", "-c", "ls | head"].

stdin string

Written to stdin, then closed.

Empty = the container's. "root" can install packages.

Empty = the container's.

0 = default. Kills the main process only. Over ~50 may outlast your client: background long jobs.

Response

exit_code int32

True = the machine killed it at the timeout; exit_code is then -1.

stdout string

The last 32 KiB of each, as the machine kept them. Invalid UTF-8, and control characters other than tab, newline and carriage return (colour codes included), become U+FFFD. Untrusted.

stderr string

True = the start of stdout or stderr was cut.

OpenTerminal

Web panel only

POST /pethost.panel.v1.PanelService/OpenTerminal

An interactive terminal in a service's running container, for the web panel: returns a URL the browser opens as a WebSocket straight to the machine, once, within 5 minutes, so keystrokes and output never pass through the panel. On it, binary messages carry the terminal's bytes both ways (input at most 1 MiB a message); the browser sends {"resize":{"columns":C,"rows":R}} as text; the machine's last message is {"exit_code":N}, or {"error":"..."} when the program could not start or was ended, then it closes the connection. While open, the terminal is one of Machine.sessions. Not an MCP tool: agents use RunServiceCommand.

Request

project_id string
service string

The first size; the browser's resizes follow.

command array of string

Empty = a login shell: bash if the image has it, else sh.

Empty = the container's user. "root" can install packages.

Empty = the container's.

Response

url string

E.g. "wss://m1.example.com/terminal/<ticket>"; "ws://" for a test machine's IP address.

expire_time timestamp

The browser must open it before.

Files

ReadPath Read a file or directory

MCP toolRead-only

POST /pethost.panel.v1.PanelService/ReadPath

Reads a file (text, 64 KiB a call; ≤1 MiB with length_bytes) or a directory's entries (~64 KiB a page). Roots: a service's container (whole filesystem; while not running only its volumes, FAILED_PRECONDITION listing them), a volume, or the deployed project files (read-only: use DeployProject). NOT_FOUND names the nearest existing directory. To write container or volume files: CreateTransfer, or sftp <service>.<project_id>@<Machine.hostname> (root). FAILED_PRECONDITION also = project_directory before any deploy, or a file in /proc or /sys (use RunServiceCommand). Untrusted.

Request

project_id string
One of root: at most one of these is set.
service string

Its container.

volume string

The volume's root.

Must be true. The deployed files (deploy_id), read-only.

path string

Absolute in the root, e.g. "/data/uploads", "/compose.yaml".

0 = 200. Also ends at ~64 KiB.

File offset.

≤1 MiB; 0 = 64 KiB.

The previous next_entry_page_token, other fields unchanged.

Response

The path itself, symlinks followed; a symlink whose target does not exist is its own entry.

With a service root: where the path lies. Unspecified with the other roots, whose name says it.

volume string

With a service root: the volume the path lies on, when location is VOLUME. Else empty.

A directory's: directories first, then by name (bytewise).

A directory's in all.

Set = more entries follow these: pass it as entry_page_token.

text string

A text file's contents from offset_bytes, ending on a whole character. Untrusted.

binary bool

True = the file is binary (a NUL byte in its first 8 KiB): no text. Download it with CreateTransfer.

Where the next read of the file starts. 0 = the read reached its end.

deploy_id string

With project_directory: the deploy whose files these are. To change what you read, pass it as DeployProject's base_deploy_id.

CreateTransfer Upload or download a file

MCP toolDestructive

POST /pethost.panel.v1.PanelService/CreateTransfer

A one-request URL moving one file straight between you and the machine. Start before expire_time; it lasts while bytes flow. Send http_method to url, or run command with your file's path:

  • upload_archive: PUT exactly size_bytes; after 200, pass upload_id to CreateProject or DeployProject (GetUpload inspects it).
  • upload_file: PUT exactly size_bytes into a container or volume, replacing any file (replaces), creating parents.
  • download: GET a file, or a directory as a .tar (file_name).

Plain-text answers: 400 wrong size, 404 used or expired URL, 409 FAILED_PRECONDITION, 422 unsafe archive (a reason per line), 507 disk full. A failed transfer keeps nothing: get a new URL. Errors as ReadPath; FAILED_PRECONDITION also = downloading neither a file nor a directory, uploading onto a non-file, a mount point, /proc or /sys, or the hostname not yet certified. INVALID_ARGUMENT = archive too large. RESOURCE_EXHAUSTED = disk full, or too many transfers.

Request

One of transfer: at most one of these is set.

Response

url string

On the machine's hostname (Machine.hostname); it works once.

"PUT" or "GET".

expire_time timestamp

Start the request before this.

command string

Does it with curl, e.g. "curl --fail-with-body -T 'recipes.zip' '<url>'" or "curl --fail-with-body -o 'photo.jpg' '<url>'". For an upload, put your file's path in place of its name.

upload_id string

upload_archive: for CreateProject.upload_id or DeployProject.upload_id once the machine answered the PUT with 200.

upload_file: a file is at path now; the upload replaces it.

file_name string

download: the name it saves as: a file's own; a directory's ends in ".tar", and "/" is named after the project and its service or volume. Empty for an upload.

GetUpload Inspect an uploaded archive

MCP toolRead-only

POST /pethost.panel.v1.PanelService/GetUpload

What an archive from CreateTransfer holds: files, the compose file it would run, /.env; or, if refused (422), the violations. FAILED_PRECONDITION = not arrived (PUT still going, or cut: CreateTransfer again). NOT_FOUND = expired, or used by a deploy.

Request

upload_id string

From CreateTransfer.

Response

file_count uint32

Its files, as unpacked.

Of those files.

What a project of it runs: the compose file CreateProject will use ("pethost.compose.yaml", "compose.yaml", "docker-compose.yml", …), or "Dockerfile" (a compose.yaml is written for it). Empty = none of them at its root.

env_file string

Its /.env: the project's unless CreateProject.files writes /.env. Empty = none. Untrusted.

Why the machine refused it, such as an entry that leads outside it: it cannot be deployed, and the fields above are empty. Empty = the machine took it.

Streams, for the web panel

WatchOperation

Web panel onlyStream

Streams an operation's log from its start, then follows it; the last message is the finished operation. Agents: GetOperation.

Request

project_id string

Empty = the latest.

Response, each message of the stream

One of message: at most one of these is set.

Always the last message.

TailContainerLogs

Web panel onlyStream

Streams container output after a cursor, then new lines as they are written. OUT_OF_RANGE = the machine no longer keeps the cursor's line, so lines after it may be gone too: start over with QueryContainerLogs and tail after its tail_cursor. Agents: QueryContainerLogs.

Request

project_id string

Empty = only lines written from now on.

Response, each message of the stream

cursor string

To reconnect after this line.

TailHttpTraffic

Web panel onlyStream

Streams HTTP requests after a sequence number, then new ones as they finish. Agents: QueryHttpTraffic.

Request

project_id string

0 = only requests from now on.

Response, each message of the stream

Types

The messages and enums that the requests and responses are made of, in the order the API declares them. An enum's value for "not set" is left out: an absent field says it.

Machine MachineSession MachineSessionKind DiskUsage SshKey GithubConnection GithubRepository DeletedProject RestartMachineAction ProjectMetadata ProjectSummary ServiceSummary ProjectProblem ProjectProblemKind Project RunningServicesAction ServicesActionKind ProjectSource GithubSource GithubCommit Service ServiceState PublishedPort VolumeMount RestartPolicy HealthCheck HealthStatus EnvironmentVariable Volume VolumeMountedBy Host Route CertificateSource Operation OperationKind OperationStatus DeployFailureReason Snapshot HttpTrafficSummary MountVolume ProjectExtension RouteList FileChange SpecViolation BranchCommit ServicesAction RecreateServiceAction BackUpAction RestoreSnapshotAction CancelOperationAction DeleteProjectAction OperationLogLine HttpTrafficFilter HttpTrafficBucket HttpPathTraffic HttpRequest ContainerLogFilter OutputStream LogLine FileEntry FileType FileLocation ArchiveUpload FileUpload PathDownload ProjectBusy ProjectChanged MachineUnreachable NoMachine NoMachineReason TerminalSize

Machine message

name string

Its plan's name, e.g. "Starter".

location string

Where it runs, e.g. "Europe". Empty = not known.

sample_time timestamp

When the figures below were read.

By projects and builds.

Without page cache, as docker stats counts.

The machine's, less a reserve for the system.

The projects' disk. The operating system has its own partition outside it, and the machine's own files are left out: a new machine uses 0.

hostname string

The machine's name. Log in with ssh <service>.<project_id>@<hostname>: a shell in that service's container; scp, sftp, rsync and ssh -L tunnels to the project's services work too. CreateTransfer's URLs are on it as well, and no route can take it.

"SHA256:...", to check on the first connection.

ssh_keys array of SshKey

Every key logs in to every container.

True = every project is backed up nightly, and on demand.

When the machine last read the backup repository's list of snapshots whole. Absent = not yet (snapshot_list_failure_message says why, once a read failed), or backups are off: snapshot counts, Volume.last_backup_time, Project.snapshots and GetMachine.deleted_projects are then not known, rather than none.

Why the machine could not read that list, while backups are on and snapshot_list_time is absent: the backup repository's error, e.g. a wrong password or a host that does not answer. The machine keeps trying. Empty = no read has failed, or the list is known, or backups are off.

Snapshots are pruned once they are this many hours older than their project's newest. 0 = kept forever: nothing deletes them.

True = a host that no certificate installed on the machine covers gets one from Let's Encrypt once it resolves to the machine. False = such a host serves plain HTTP only.

The account's own domain, e.g. "xawro7ze.pethost.app": a route's host <name>.<apps_domain> works at once, with no DNS record of the person's (see DeployProject). Only the person changes it, in the panel's settings. Empty = there is none: every host is the person's own domain.

boot_time timestamp

When an update started to need a machine restart. Absent = none is needed.

When RunMachineAction.restart_machine will restart it; later while what it waits for is still going on (RestartMachineAction.interrupt_operations). Absent = not scheduled.

What people have open in the containers now, oldest first: at most 100, the most the machine holds at once.

MachineSession message

Something a person has open in a container: a web terminal (OpenTerminal), or, through SSH, a program, SFTP or one connection of a tunnel. It lasts until the program exits or the connection closes; RunMachineAction.end_session_id ends it sooner.

project_id string
service string
port uint32

TUNNEL: the container's port.

start_time timestamp

The person's IP address.

Through SSH: the key they logged in with, as it was then. Empty for WEB_TERMINAL.

MachineSessionKind enum

The web panel's terminal.

A program in a terminal, such as ssh's shell.

A program without a terminal, such as ssh ... <command> or rsync.

scp, sftp or sshfs.

One connection of an ssh -L tunnel to port.

DiskUsage message

Where the disk goes; the rest of disk_used_bytes is the filesystem's own metadata and the machine's small files. Measured every few minutes.

Absent = not known: Docker cannot size the build cache.

What containers wrote outside volumes.

Project directories and operation logs.

Kept on the operating system's partition, outside disk_used_bytes; the machine bounds their age and size (QueryContainerLogsResponse.oldest_kept_time).

The HTTP request log.

SshKey message

public_key string

The public key, as in authorized_keys: "ssh-ed25519 AAAA..."; never a private key.

label string

Shown in the panel and session logs, e.g. "MacBook Pro".

"SHA256:...". Ignored on input.

GithubConnection message

The panel's GitHub App, one for every account: projects are created from the repositories of the account's own installations of it. Absent = GitHub is not set up on this panel.

app_name string

As in https://github.com/apps/<app_name>.

The panel's page that sends a signed-in person to GitHub to install the app on their repositories, or to change which.

The repositories of the account's installations, most recently pushed first, at most 200.

In all.

False = pushes reach the panel only by its check every 5 minutes, not at once.

GithubRepository message

repository string

"owner/name".

private bool
push_time timestamp

DeletedProject message

project_id string

Restore it to bring the project back as it was last backed up; GetProject then pages the older ones (snapshots_before).

In all.

RestartMachineAction message

restart_time timestamp

Absent = now, or at_maintenance_window. INVALID_ARGUMENT = in the past.

True = the next nightly window (Machine.scheduled_restart_time). Not with restart_time.

False = wait for operations and nightly backups to end; true = cut them short.

ProjectMetadata message

name string

Empty = none: people see the id.

emoji string

One line.

notes string

Free text.

ProjectSummary message

project_id string

Most urgent first. Empty = nothing wrong.

By name.

url string

The first route's host as a URL, e.g. "https://recipes.example.com". Empty = no route.

hosts array of string

Every host its routes use, in the routes' order.

deploy_time timestamp

As in Project.

Operations in progress, newest first: a backup can run beside another one.

pinned bool

A github project stays on its commit: GithubSource.pinned.

Its volumes, files, what its containers wrote outside volumes, and its images: one it shares with another project counts in both. Measured every few minutes.

ServiceSummary message

ProjectProblem message

Something that needs attention. A service has at most one, its cause first.

service string

Empty = not about one service. DEPLOY_FAILED: the service that failed it, when one did.

DEPLOY_FAILED, DEPLOY_CANCELLED, BACKUP_FAILED, RESTORE_FAILED: the operation; GetOperation gives its log.

since_time timestamp

DEPLOY_FAILED, DEPLOY_CANCELLED, BACKUP_FAILED, RESTORE_FAILED: the operation's end. SERVICE_EXITED, RESTARTING and OUT_OF_MEMORY: when its last run ended, once it did; else its last start. AUTO_DEPLOY_REFUSED: when auto-deploy tried the commit. Absent = not known, as for UNHEALTHY.

One sentence for people, without a time of day (since_time has it), e.g. "bot fails its healthcheck: 11 failures in a row". A failed or cancelled deploy's also says whether it changed the project.

ProjectProblemKind enum

The latest deploy failed. Its operation says why, and with made_current whether its files were applied (then some containers may run the old configuration).

Running; its healthcheck fails.

Docker is restarting it, or it runs and Docker restarted it recently: likely a crash loop.

Its last run was killed for exceeding its memory, or a process of it was since it started. A loop whose runs last 10 s or more shows as RESTARTING: Docker forgets the kill when it starts it again.

The latest backup failed; its operation says why.

The latest deploy was cancelled; its operation says, with made_current, whether its files were applied.

It exited on its own with a non-zero code (Service.exit_code) and stays down: its restart policy does not restart it. A service a person stopped is no problem.

A restore failed or was cancelled, or a machine restart cut it short, and its harm remains: services it stopped are still stopped (starting them ends it); unless a restore that succeeded since replaced them, its volumes may be partly restored, and its operation's undo_snapshot_id holds them as they were. Or it brought a deleted project back, and no deploy has replaced it. The newest such restore counts; one that changed nothing does not.

Auto-deploy is on, and the machine refused the commit it last tried, the branch's newest then, which the files are not; the message says why. Each commit is tried once: fix it and push, or DeployProject with newest_commit, whose refusal gives the violations and fix_prompt.

Project message

project_id string
url string

As in ProjectSummary.

deploy_id string

The operation_id of the deploy whose files the project has: pass it as base_deploy_id. For a deleted project brought back, the RESTORE that did it. Empty = no deploy yet.

How that operation went, or is going. Unspecified = no deploy yet.

deploy_time timestamp

When the current deploy made its files the project's. Absent = no deploy yet, or one that failed or was cancelled leaving no service with a container: nothing runs.

True = the current deploy succeeded and every service has a container, so a deploy whose files differ only inside x-pethost (or not at all) applies at once, with no build, pull or restart. False = the next deploy runs in full (see DeployProject).

services array of Service

By name.

volumes array of Volume

Every one the machine holds for the project, by name.

routes array of Route

x-pethost.routes as the machine holds them, in order: the list x_pethost.routes replaces, so send these back with your change to add, change or remove one.

hosts array of Host

Every host of the routes, in the order of its first route.

Newest first, every one the machine keeps: its newest, any still in progress, and the current deploy's (deploy_id) however old. Older ones are gone.

The newest 10, newest first, of those GetProjectRequest's snapshots_before and snapshots_volume select. Empty while backups are off or the list is not read yet (snapshot_list_time).

The project's, in all; see snapshot_list_time.

When the machine read the list of snapshots that snapshot_count, Volume.snapshot_count and Volume.last_backup_time come from (Machine.snapshot_list_time, as sampled with them). Absent = the list is not known: backups are off, or it is not read yet (Machine.snapshot_list_failure_message says why); a count of 0 then does not mean none.

Measured every few minutes.

The start_services, stop_services or restart_services that runs now, whoever called it (RunProjectAction waits for its end). Until then the project takes no other action or deploy, and Service.state is still what it was before. Absent = none.

RunningServicesAction message

A start, stop or restart of services that has not ended yet.

services array of string

Those it acts on.

start_time timestamp

ServicesActionKind enum

ProjectSource message

Where the files come from (THE PROJECT IS ITS DIRECTORY).

One of kind: at most one of these is set.
files bool

Must be true. Files live on the machine.

GithubSource message

Input: an absent field keeps its value (set_source) or takes its default (new source).

repository string

"owner/name", one of GetMachine.github.repositories.

branch string

Empty = the default branch, also when absent on a new source.

directory string

The project's directory in it, e.g. "apps/web"; empty = the root. Outer slashes dropped; ., .. or an empty segment = INVALID_ARGUMENT.

True = deploy each new newest_commit, unless the files are it; each commit is tried once. Deploying another commit turns it off; only set_source turns it on. Default true.

Output: the branch's newest commit touching the directory, as of the last push or 5-minute check.

Output: the commit the files are at. Absent = no deploy, or the files came from another source.

autofix bool

True = autofix each commit's compose file (see CreateProject). Default true.

pinned bool

Output: deployed_commit set and auto_deploy off, e.g. after a rollback.

Output: true = auto-deploy will deploy newest_commit (not yet tried) once the project is free. A tried commit that failed or was refused waits for a person: DeployProject or a new push.

GithubCommit message

sha string

Full.

title string

First line of its message. Untrusted.

url string

On github.com.

Service message

service string

True = its container runs, as RunServiceCommand, OpenTerminal and ReadPath of its own files (outside volumes) need: in RUNNING, HEALTHY and UNHEALTHY, and in STARTING once its process started.

image string

The image as compose.yaml names it; for build: without image:, the name Compose gives.

Built on the machine from a build: section.

The registry digest it was pulled as, "sha256:...". Empty for a built image.

Absent until the machine next measures sizes, every few minutes, as after a build or pull.

command array of string

What the main process runs, as compose.yaml writes it (entrypoint, then command), ${...} not interpolated, so that no value of an env file shows; the image's own argv when compose.yaml writes neither. Empty = not known without those values: compose.yaml loads only interpolated, e.g. with an include path from .env.

start_time timestamp

Of the current or last run.

finish_time timestamp

Set once it exited, and while Docker waits to restart it: when, with what code (0 = a clean exit: a job that finished, or a server stopped; 137 = killed), and whether a process of it was killed for exceeding its memory.

exit_code int32

Restarts by Docker since the container was created.

0 = no limit: it shares the machine.

0 = no limit.

Written outside volumes: lost on a recreate.

ports array of uint32

The ports it is reached at, as far as the machine knows: those compose.yaml or its image declares and those its routes send to. The project's other services reach it at <service>:<port>.

Absent = no healthcheck.

What the container is configured with, not what its image sets.

env_files array of string

The files its env_file: names, as compose.yaml writes them, e.g. "./.env": relative to the project directory. Change them with DeployProject.changes.

ServiceState enum

Declared; no container yet.

Starting, or created while an operation that starts containers runs (deploy, recreate_service, restore); its healthcheck has not passed yet.

Running, without a healthcheck.

Running; its healthcheck passes.

Running; its healthcheck fails.

Exited and Docker is restarting it.

Not running: stopped by stop_services or a restore, never started, paused or dead.

Not running: it exited on its own (see exit_code; 0 = a job that finished).

PublishedPort message

A port that ports: opens on the machine directly, bypassing the proxy.

0 = Docker picks one when the container starts.

protocol string

"tcp", "udp" or "sctp".

True = bound to 127.0.0.1: reachable only through an SSH tunnel.

VolumeMount message

volume string

E.g. "/var/lib/postgresql/data".

RestartPolicy enum

Docker's restart policies: what happens when the process exits, and when the machine starts.

Never restarted.

Restarted when it exits non-zero. When the machine or Docker restarts it comes back unless it exited 0 (a finished job), so one stopped with stop_services comes back too. The default when compose.yaml sets none.

Always restarted, unless stopped with RunProjectAction.stop_services.

HealthCheck message

The healthcheck Docker runs: compose.yaml's, else the image's HEALTHCHECK.

command array of string

The program and its arguments, e.g. ["curl", "-f", "http://localhost:8000/health"]; a test written as a shell line is ["/bin/sh", "-c", <line>]. Written and known as Service.command is: compose.yaml's healthcheck.test, else the image's.

How often Docker runs it.

Absent while the service is not running.

recent_results_passed array of bool

Up to the last 5 checks, oldest first.

Of the latest failed check, at most 4 KiB. Untrusted.

HealthStatus enum

EnvironmentVariable message

name string
value string

Absent when empty, when secret and GetProject had no include_secret_values, or when value_left_out.

secret bool

True = its value is not empty and comes from an env file, directly or through ${...} in compose.yaml: env files hold the secrets. Empty values, and values written in compose.yaml itself, are not secret. The machine hides these values in operation logs too.

The file to edit to change it, absolute in the project's directory: "/compose.yaml" (the service's environment:, which wins) or the env file it comes from, e.g. "/.env", "/web.env". Empty = the machine cannot tell, as when compose.yaml takes that file's path from .env.

from_env_file_variables array of string

When compose.yaml builds it from ${...}: the .env variables to edit to change it.

True = the value is left out: the project's values together hold more than about 64 KiB. Read source_file with ReadPath.

Volume message

volume string
size_bytes uint64

Measured every few minutes.

Of the newest snapshot that holds this volume. Absent = no snapshot holds it, or the list is not known (Project.snapshot_list_time).

The snapshots that hold it, in all; GetProjectRequest.snapshots_volume lists them. See Project.snapshot_list_time.

False = compose.yaml no longer declares it, so no service mounts it: it keeps its data and is in every backup until RunProjectAction.delete_volume or the project is deleted. ReadPath reads it with the volume root, restore_snapshot restores it, and declaring it again mounts it as it is.

VolumeMountedBy message

service string

Host message

A host of Project.routes, with its certificate.

host string

As x-pethost.routes[].host.

url string

"https://<host>" once its certificate is served, and plain HTTP is redirected there; until then "http://<host>", which reaches the services. For AUTOMATIC, the host must resolve to the machine first.

When the certificate served expires. Absent = none is served yet.

Why the host answers nobody: a name under the panel's domain that is not one label under Machine.apps_domain (another account's, a former apps_domain of this one, or two labels deep). Change its host. Empty = it answers, or it is the person's own domain.

Route message

An x-pethost route; field names are the YAML keys (see DeployProject).

host string

E.g. "recipes.example.com".

path string

"/" = every path. Empty on input = "/".

service string
port uint32

CertificateSource enum

No certificate will come: plain HTTP only. As when Machine.acme_enabled is off, or for an IP address or another name Let's Encrypt does not issue for.

A certificate installed on the machine covers it.

From Let's Encrypt, renewed automatically.

Operation message

start_time timestamp
finish_time timestamp

Absent while in progress.

When FAILED: why, for people. The log says more.

DEPLOY or RECREATE_SERVICE that FAILED: why, to branch on.

DEPLOY, or a RESTORE that brought a deleted project back: true = it made its files the project's. It stays true once a later deploy replaces them: Project.deploy_id names the one whose files the project has now. A deploy switches the files partway, when it starts the containers, so one that FAILED or was CANCELLED without it left the files as they were: to retry, send all its changes again with the same base_deploy_id. A project's first deploy has it from its start: its files stay however it ends, to fix and deploy again.

service string

RECREATE_SERVICE: the service. A failed DEPLOY or RECREATE_SERVICE: the service that failed.

DEPLOY or RECREATE_SERVICE that failed CONTAINER_EXITED.

BACKUP, or DELETE's final backup, that succeeded: the snapshot taken. RESTORE: the snapshot restored.

restored_volumes array of string

RESTORE: the volumes it replaced.

RESTORE: the backup taken first; restore it to undo. Empty = none was taken.

DEPLOY of a github project: the commit its files are, also when it changed only /.env or x-pethost.

DEPLOY of an uploaded archive: its file name.

adjustments array of string

DEPLOY: what the machine changed to deploy that version, for people: the compose file it used, files it left out, autofix's changes.

changed_paths array of string

DEPLOY: the paths its request changed, for people, the first 10: DeployProject.changes, or CreateProject's files; a move lists both paths. A commit's or an archive's own files are not listed: github_commit or archive_name says what they brought.

DEPLOY: the paths its request changed, in all.

OperationKind enum

OperationStatus enum

DeployFailureReason enum

A wrong name or tag, or a private image.

E.g. the command does not exist.

Includes a machine restart during the operation.

A ports: machine port is taken.

Exited, or kept restarting, after starting.

The image declares a VOLUME no mount covers: it would never be backed up. Mount a named volume there.

The running projects leave too little memory for a build; the message says how much.

Snapshot message

create_time timestamp
size_bytes uint64

Of its files, the directory and every volume. 0 = not known.

volumes array of string

The volumes it holds, besides the project's directory.

HttpTrafficSummary message

5xx responses.

Of responses: WebSockets, logged when they close, count as requests but not here.

MountVolume message

Adds <volume>:<container_path> to the service, declaring the volume if needed.

service string

Declared in compose.yaml (NOT_FOUND lists them).

volume string

New, or one of Project.volumes, e.g. an undeclared one to remount its data.

Absolute, e.g. "/data".

ProjectExtension message

x-pethost as data, written by the machine. Absent parts stay; GetProject shows them as Project.metadata and Project.routes.

Sets the fields given; an empty one is removed.

Replaces all routes.

RouteList message

routes array of Route

One per host and path.

FileChange message

path and exactly one change.

path string

Absolute in the project's directory.

One of change: at most one of these is set.
text string

Writes the file as UTF-8 text, creating parents.

data bytes

Writes the file as bytes, likewise.

Must be true. Creates it and its parents.

delete bool

Must be true. Deletes a file, symlink or empty directory, if present.

Must be true. Deletes a directory recursively, if present.

rename_to string

Moves it here. ALREADY_EXISTS = something is there.

With text or data: mode 0755, not 0644; else a replaced file keeps its mode.

SpecViolation message

service string

Empty = not about one service.

location string

In compose.yaml, down to the key it is about, e.g. "services.db.volumes[0]", "x-pethost.routes[2].host"; or a file's absolute path, e.g. "/.env".

What is wrong and what to write instead.

BranchCommit message

author string

The author's GitHub login, else their name. Untrusted.

commit_time timestamp

When it was committed.

The project's files are this commit (GithubSource.deployed_commit).

The newest deploy of it among the operations the machine keeps; its status says how it went. Absent = none.

newest bool

True = the branch's newest commit that changes the directory, as GitHub answered this call: deploying any other turns auto_deploy off (see DeployProject).

ServicesAction message

services array of string

Empty = all (start: those with a container; restart: running ones).

RecreateServiceAction message

service string

True = pull the tag's newest image, or rebuild on the newest base images.

BackUpAction message

No fields.

RestoreSnapshotAction message

volumes array of string

Existing project: the volumes to replace (≥1); it is backed up first (Operation.undo_snapshot_id), stopped and restarted; files stay. Deleted project: files, source and volumes return, then it deploys.

CancelOperationAction message

Required.

DeleteProjectAction message

True = no final backup: changes since the newest snapshot are lost. Required with backups off.

OperationLogLine message

time timestamp
text string

From Compose, BuildKit, restic or the daemon, as LogLine.text is kept; at most 2 KiB, a longer line is cut.

HttpTrafficFilter message

host string

As Host.host; a removed host still matches.

method string

E.g. "GET".

Without the query; any case.

As HttpPathTraffic.path_pattern, e.g. "/recipes/*".

1-5: 5 = 5xx.

HttpTrafficBucket message

HttpPathTraffic message

method string

The path with id-like segments as *, e.g. "/recipes/*": requests to one endpoint.

HttpRequest message

sequence uint64

Unique and increasing on the machine: TailHttpTraffic's cursor.

finish_time timestamp

When the response finished. A WebSocket is logged when it closes, with status 0.

host string
method string
path string

With the query. Untrusted.

route_path string

The route that matched.

service string

That answered. Empty = of a route the project no longer has.

port uint32

0 = a WebSocket; 499 = the client left before the response.

In total.

Of which inside the service.

user_agent string

Untrusted.

ContainerLogFilter message

service string

Empty = all, removed services too.

Absent = both.

Literal, any ASCII case, ignoring escape sequences. Length limited (INVALID_ARGUMENT).

OutputStream enum

LogLine message

time timestamp
service string
text string

A line as written, with its colour codes (SGR escape sequences); other escape sequences, and control characters but tab, are removed. A line over 4 KiB is cut (over 16 KiB in streams). Untrusted.

FileEntry message

name string

Without its directory.

size_bytes uint64

Of a regular file; 0 otherwise.

modify_time timestamp
mode string

Permission bits in octal, e.g. "0644".

owner_uid uint32

As inside the container.

owner_gid uint32

FileType enum

FileLocation enum

Where a path of a service's container lies, which says whether it lasts.

The container's own files, its image and what it wrote outside volumes, and its scratch space (an anonymous volume, a tmpfs): lost when it is recreated.

A named volume (ReadPathResponse.volume): kept across deploys and backed up.

The project's files, mounted read-only from its source as deployed: change them with DeployProject.

ArchiveUpload message

For CreateProject's or DeployProject's upload_id.

file_name string

A zip, tar or tar.gz (told by content), e.g. "recipes.zip". A single top folder is unwrapped; .git, __MACOSX, .DS_Store, ._* are dropped.

size_bytes uint64

Exactly what you will send.

FileUpload message

project_id string
One of root: at most one of these is set.
service string

Its container.

volume string

The volume's root.

path string

Absolute in the root, e.g. "/data/uploads/a.jpg".

size_bytes uint64

Exactly what you will send.

PathDownload message

A directory comes as a .tar of what ReadPath shows; symlinks kept.

project_id string
One of root: at most one of these is set.
service string

Its container.

volume string

The volume's root.

Must be true. The deployed files.

path string

Absolute in the root.

ProjectBusy message

Detail of an UNAVAILABLE error: an operation, or a short action, holds the project. The message says the same in words.

Empty = a short action, done while its caller waits: call again in a few seconds. Else wait for it with GetOperation, even while it is still starting; when it is your own operation_id, the first call with it is still starting it: call again.

Unspecified when operation_id is empty.

start_time timestamp

Since when it holds the project.

ProjectChanged message

Detail of an ABORTED error: base_deploy_id is not the current deploy. Read the project again (Project.deploy_id) and redo your change on its files.

No fields.

MachineUnreachable message

Detail of an UNAVAILABLE error: the machine does not answer now, as while it restarts. Call again later.

No fields.

NoMachine message

Detail of a FAILED_PRECONDITION error: the account has no machine to run projects on yet.

url string

Where the person goes on in the browser.

NoMachineReason enum

The person has not chosen and paid for a plan: they do it at url.

The plan is paid and a machine is being prepared for the account: Pethost emails the person when it is ready. Call again later.

TerminalSize message

columns uint32
rows uint32