# API

> The 5050cloud REST API serves everything the console and the CLI do, with the same roles and audit log.

The 5050cloud REST API serves everything the console and the CLI do, with the same roles and audit log.

## Basics

- Base URL: `https://<your console domain>/api/v1`. Health endpoints `/healthz` and `/readyz` sit at the root.
- Authentication: `Authorization: Bearer 5050_...` with an API token from **Account > Tokens** or `5050 login`. Browser sessions use the `5050_session` cookie and must send the header `X-5050-CSRF: 1` on every request that changes state.
- Scopes: an `account` token acts as its user. An `environment` token can deploy, read logs and set variables in one environment.
- Roles: Public needs no login. Viewer, Member, Admin and Owner are the install roles; a project grant can replace the install role for one project.
- Errors are JSON `{code, message, field?}` with the codes `unauthorized`, `forbidden`, `not_found`, `conflict`, `validation`, `rate_limited`, `unavailable` (also read-only mode) and `internal`.
- Request bodies are capped at 1 MiB, except uploads. Unknown JSON fields are rejected.
- Every request that is not a GET writes an audit log entry. The **Audit** column names its action.
- Types named in the tables are the Go types in `pkg/api/types.go` (MIT licensed), with camelCase JSON names. TypeScript types are generated from them.


## System, auth, users

| Method | Path | Body and response | Role | Audit |
|---|---|---|---|---|
| GET | `/healthz` | 200 "ok" when the process runs | Public |  |
| GET | `/readyz` | `{version, ready, readOnly, checks{db,migrations,hub,agents}}` | Public |  |
| GET | `/api/v1/version` | `{version, commit, date}` | Public |  |
| GET | `/api/v1/setup` | `SetupStatus` | Public |  |
| POST | `/api/v1/setup` | `SetupRequest` -> `Me` (sets cookie) | Public | `setup.complete` |
| POST | `/api/v1/auth/login` | `LoginRequest` -> `Me` or `{needTotp: true, ticket}` | Public | `auth.login` |
| POST | `/api/v1/auth/login/totp` | `{ticket, code}` -> `Me` | Public | `auth.login_totp` |
| POST | `/api/v1/auth/logout` | -> 204 | Viewer | `auth.logout` |
| POST | `/api/v1/auth/forgot` | `{email}` -> 204 (always) | Public | `auth.forgot` |
| POST | `/api/v1/auth/reset` | `{token, password}` -> `Me` | Public | `auth.reset` |
| POST | `/api/v1/auth/verify-email` | `{token}` -> 204 | Public | `auth.verify_email` |
| GET | `/api/v1/me` | `Me` | Viewer |  |
| PATCH | `/api/v1/me` | `{name?, password?, currentPassword?}` -> `User` | Viewer | `me.update` |
| POST | `/api/v1/me/2fa/setup` | -> `{secret, otpauthUrl}` / `{code}` -> `{recoveryCodes}` / `{password, code}` -> 204 | Viewer | `me.2fa_setup` |
| POST | `/api/v1/me/2fa/enable` | `TOTPEnableRequest` -> `TOTPEnableResponse` | Viewer | `me.2fa_enable` |
| POST | `/api/v1/me/2fa/disable` | `TOTPDisableRequest` -> 204 | Viewer | `me.2fa_disable` |
| GET | `/api/v1/me/notification-prefs` | `[]NotificationPref` | Viewer |  |
| PUT | `/api/v1/me/notification-prefs` | `[]NotificationPref` -> `[]NotificationPref` | Viewer | `me.notification_prefs` |
| GET | `/api/v1/me/notifications` | `Page[Notification]` (query `before=`) | Viewer |  |
| POST | `/api/v1/me/notifications/read` | `{ids[] or all}` -> 204 | Viewer |  |
| POST | `/api/v1/cli/auth/start` | -> `CLIAuthStart` | Public | `cli_auth.start` |
| POST | `/api/v1/cli/auth/approve` | `{code}` -> 204 | Viewer | `cli_auth.approve` |
| POST | `/api/v1/cli/auth/poll` | `CLIAuthPoll` -> `CLIAuthPollResponse` | Public |  |
| GET | `/api/v1/tokens` | `[]APIToken` | Viewer |  |
| POST | `/api/v1/tokens` | `CreateTokenRequest` -> `CreateTokenResponse` | Viewer | `token.create` |
| DELETE | `/api/v1/tokens/{id}` | -> 204 | Viewer | `token.delete` |
| GET | `/api/v1/members` | `[]User` | Viewer |  |
| GET | `/api/v1/invites` | `[]Invite` | Admin |  |
| POST | `/api/v1/invites` | `CreateInviteRequest` -> `Invite` | Admin | `invite.create` |
| DELETE | `/api/v1/invites/{id}` | -> 204 | Admin | `invite.delete` |
| GET | `/api/v1/invites/{id}` | `InvitePreview` | Public |  |
| POST | `/api/v1/invites/accept` | `{token, email, name, password}` -> `Me` | Public | `invite.accept` |
| PATCH | `/api/v1/members/{id}` | `UpdateMemberRequest` -> `User` | Admin | `member.update` |
| DELETE | `/api/v1/members/{id}` | 204 | Admin | `member.delete` |
| POST | `/api/v1/members/{id}/transfer-ownership` | `{password, code?}` -> 204 | Owner | `member.transfer_ownership` |
| GET | `/api/v1/audit` | `Page[AuditEntry]` (query `before=&limit=`) | Admin |  |

## Install, settings, hosts, upgrades

| Method | Path | Body and response | Role | Audit |
|---|---|---|---|---|
| GET | `/api/v1/install` | `Install` | Viewer |  |
| PATCH | `/api/v1/install` | `UpdateInstallRequest` -> `Install` (name, base domain, console domain, ACME email, trusted proxies, registry cap, build timeout default) | Admin | `install.update` |
| GET | `/api/v1/settings/backups` | `BackupSettings` | Admin |  |
| PUT | `/api/v1/settings/backups` | `BackupSettings` -> `BackupSettings` | Admin | `settings.backups_update` |
| POST | `/api/v1/settings/backups/test` | -> `{ok, error?}` | Admin | `settings.backups_test` |
| GET | `/api/v1/settings/smtp` | `SMTPSettings` | Admin |  |
| PUT | `/api/v1/settings/smtp` | `SMTPSettings` -> `SMTPSettings` | Admin | `settings.smtp_update` |
| POST | `/api/v1/settings/smtp/test` | `{to}` -> `{ok, error?}` | Admin | `settings.smtp_test` |
| GET | `/api/v1/settings/alerts` | `InstallNotifications` | Admin |  |
| PUT | `/api/v1/settings/alerts` | `InstallNotifications` -> `InstallNotifications` | Admin | `settings.alerts_update` |
| POST | `/api/v1/settings/recovery-key` | `{password}` -> `{recoveryKey}` (audited) | Owner | `settings.recovery_key_reveal` |
| GET | `/api/v1/hosts` | `[]Host` (with conflicts, reachability, usage) | Viewer |  |
| POST | `/api/v1/hosts/join-tokens` | `CreateJoinTokenRequest` -> `JoinToken` | Admin | `host.join_token` |
| PATCH | `/api/v1/hosts/{id}` | `UpdateHostRequest` -> `Host` (name, roles, drain, reserve) | Admin | `host.update` |
| POST | `/api/v1/hosts/{id}/maintenance` | `{on}` -> `Host` | Admin | `host.maintenance` |
| POST | `/api/v1/hosts/{id}/readopt` | -> `Host` | Admin | `host.readopt` |
| DELETE | `/api/v1/hosts/{id}/conflicts` | `Host` | Admin | `host.clear_conflicts` |
| DELETE | `/api/v1/hosts/{id}` | -> 204 (refused while it runs replicas, unless `?force=1`) (query `force=1`) | Admin | `host.delete` |
| GET | `/api/v1/hosts/{id}/metrics` | `MetricsResponse` (query `range=&from=&until=`) | Viewer |  |
| POST | `/api/v1/upgrade` | `UpgradeRequest` -> `UpgradeStatus` | Admin | `install.upgrade` |
| GET | `/api/v1/upgrade` | `UpgradeStatus` | Viewer |  |
| GET | `/api/v1/registry-credentials` | `[]RegistryCredential` | Member |  |
| POST | `/api/v1/registry-credentials` | `CreateRegistryCredentialRequest` -> `RegistryCredential` | Member | `registry_credential.create` |
| DELETE | `/api/v1/registry-credentials/{id}` | -> 204 (refused while a service uses it) | Member | `registry_credential.delete` |
| GET | `/api/v1/restore/unknown-replicas` | `[]UnknownReplica` | Admin |  |
| POST | `/api/v1/restore/unknown-replicas/{id}/adopt` | -> 204 | Admin | `restore.adopt` |
| POST | `/api/v1/restore/unknown-replicas/{id}/remove` | 204 | Admin | `restore.remove` |
| POST | `/api/v1/restore/finish` | 204 | Admin | `restore.finish` |

## Projects, environments, canvas, changes

| Method | Path | Body and response | Role | Audit |
|---|---|---|---|---|
| GET | `/api/v1/projects` | `[]Project` (query `sort=activity\|created\|name`) | Viewer |  |
| POST | `/api/v1/projects` | `CreateProjectRequest` (name optional: a two-word name is generated) -> `Project` (with `production`) | Member | `project.create` |
| GET | `/api/v1/projects/{pid}` | `Project` | Viewer |  |
| PATCH | `/api/v1/projects/{pid}` | `UpdateProjectRequest` -> `Project` | Member | `project.update` |
| DELETE | `/api/v1/projects/{pid}` | `{confirm: "<name>"}` -> 204 (stops everything, deletes volumes after 48 h) | Admin | `project.delete` |
| GET | `/api/v1/projects/{pid}/members` | `[]ProjectGrant` | Viewer |  |
| PUT | `/api/v1/projects/{pid}/members` | `[]ProjectGrant` -> `[]ProjectGrant` | Admin | `project.grants_update` |
| GET | `/api/v1/projects/{pid}/usage` | `UsageResponse` (query `from=&until=`) | Viewer |  |
| POST | `/api/v1/projects/{pid}/environments` | `CreateEnvironmentRequest` -> `Environment` | Member | `environment.create` |
| PATCH | `/api/v1/environments/{eid}` | `UpdateEnvironmentRequest` (name, egress) -> `Environment` | Member | `environment.update` |
| DELETE | `/api/v1/environments/{eid}` | `{confirm}` -> 204 (not the last one) | Admin | `environment.delete` |
| POST | `/api/v1/environments/{eid}/sync` | `{from: eid}` -> `ChangesResponse` (staged, with tags) | Member | `environment.sync` |
| GET | `/api/v1/environments/{eid}/canvas` | `Canvas` | Viewer |  |
| PUT | `/api/v1/environments/{eid}/positions` | `SavePositionsRequest` -> 204 (broadcast) | Member |  |
| PUT | `/api/v1/environments/{eid}/groups` | `[]Group` -> `[]Group` | Member | `canvas.groups_update` |
| POST | `/api/v1/environments/{eid}/presence` | `{cursor?}` -> 204 | Viewer |  |
| GET | `/api/v1/environments/{eid}/changes` | `ChangesResponse` | Viewer |  |
| DELETE | `/api/v1/environments/{eid}/changes` | -> 204 | Member | `changes.discard_all` |
| DELETE | `/api/v1/environments/{eid}/changes/{cid}` | 204 | Member | `changes.discard` |
| POST | `/api/v1/environments/{eid}/changes/commit` | `CommitRequest` -> `CommitResponse` | Member | `changes.commit` |
| GET | `/api/v1/environments/{eid}/activity` | `Page[Activity]` (query `service=&kind=&before=`) | Viewer |  |
| GET | `/api/v1/commits/{id}` | `CommitDetail` (message, author, full diff, secrets masked) | Viewer |  |
| GET | `/api/v1/events` | `Event` (Server-Sent Events, query `environment=`) | Viewer |  |
| GET | `/api/v1/environments/{eid}/shared-variables` | `VariablesResponse` | Viewer |  |
| PUT | `/api/v1/environments/{eid}/shared-variables` | `SetVariablesRequest` -> `VariablesResponse` | Member | `shared_variables.stage` |
| POST | `/api/v1/environments/{eid}/shared-variables/reveal` | `RevealRequest` -> `RevealResponse` | Member | `shared_variables.reveal` |
| POST | `/api/v1/environments/{eid}/shared-variables/share` | `{name, services[]}` -> 204 (staged) | Member | `shared_variables.share` |
| GET | `/api/v1/environments/{eid}/export` | `5050.yaml` text (`manifest.ProjectFile`) | Viewer |  |
| POST | `/api/v1/imports/railway` | `{token, projectName?}` -> `Import` (creates a project with staged changes) | Member | `import.railway` |
| POST | `/api/v1/environments/{eid}/imports/compose` | YAML body -> `Import` (staged) | Member | `import.compose` |
| POST | `/api/v1/environments/{eid}/services/{sid}/imports/heroku-env` | text body -> `Import` (staged) | Member | `import.heroku_env` |
| GET | `/api/v1/imports/{id}` | `Import` with report | Member |  |
| GET | `/api/v1/search` | `SearchResult` (query `q=`) | Viewer |  |

## Services

| Method | Path | Body and response | Role | Audit |
|---|---|---|---|---|
| POST | `/api/v1/environments/{eid}/services` | `CreateServiceRequest` -> `CreateServiceResponse` | Member | `service.create` |
| GET | `/api/v1/environments/{eid}/services/{sid}` | `Service` (config with `source` map for file-set fields) | Viewer |  |
| PATCH | `/api/v1/environments/{eid}/services/{sid}/config` | `UpdateServiceConfigRequest` -> `Service` (staged) | Member | `service.config_stage` |
| PATCH | `/api/v1/environments/{eid}/services/{sid}/info` | `{name?, icon?}` -> `Service` (rename staged, icon immediate) | Member | `service.info_update` |
| POST | `/api/v1/environments/{eid}/services/{sid}/duplicate` | -> `Service` (staged) | Member | `service.duplicate` |
| DELETE | `/api/v1/environments/{eid}/services/{sid}` | `{confirm}` -> 204 (staged) | Admin | `service.delete` |
| GET | `/api/v1/environments/{eid}/services/{sid}/variables` | `VariablesResponse` | Viewer |  |
| PUT | `/api/v1/environments/{eid}/services/{sid}/variables` | `SetVariablesRequest` -> `VariablesResponse` | Member | `variables.stage` |
| POST | `/api/v1/environments/{eid}/services/{sid}/variables/reveal` | `RevealRequest` -> `RevealResponse` | Member | `variables.reveal` |
| GET | `/api/v1/environments/{eid}/services/{sid}/deployments` | `[]Deployment` (query `limit=&q=`) | Viewer |  |
| POST | `/api/v1/environments/{eid}/services/{sid}/deploy` | `DeployRequest` -> `Deployment` | Member | `deployment.deploy` |
| POST | `/api/v1/environments/{eid}/services/{sid}/uploads` | `application/gzip` -> `UploadResponse` (query `message=`) | Member | `deployment.upload` |
| GET | `/api/v1/deployments/{did}` | `DeploymentDetail` | Viewer |  |
| POST | `/api/v1/deployments/{did}/redeploy` | -> `Deployment` | Member | `deployment.redeploy` |
| POST | `/api/v1/deployments/{did}/rollback` | `Deployment` | Member | `deployment.rollback` |
| POST | `/api/v1/deployments/{did}/restart` | `Deployment` | Member | `deployment.restart` |
| POST | `/api/v1/deployments/{did}/abort` | `Deployment` | Member | `deployment.abort` |
| POST | `/api/v1/deployments/{did}/remove` | `Deployment` | Member | `deployment.remove` |
| POST | `/api/v1/replicas/{rid}/move` | `{hostId?}` -> 204 (singleton move after confirm, 7.1) | Admin | `replica.move` |
| GET | `/api/v1/deployments/{did}/build-logs` | `LogsResponse` (query `follow=1 (SSE of []LogLine)`) | Viewer |  |
| GET | `/api/v1/logs` | `LogsResponse` (query `environment=&services=&deployment=&type=deploy,build,http&q=&since=&until=&limit=&around=`) | Viewer |  |
| GET | `/api/v1/logs/stream` | `[]LogLine` (Server-Sent Events, query `same as /logs`) | Viewer |  |
| GET | `/api/v1/environments/{eid}/services/{sid}/metrics` | `MetricsResponse` (query `range=&from=&until=&view=sum\|replica`) | Viewer |  |
| GET | `/api/v1/environments/{eid}/services/{sid}/db-metrics` | `DBMetricsResponse` (query `range=`) | Viewer |  |
| GET | `/api/v1/environments/{eid}/services/{sid}/monitors` | `[]Monitor` | Viewer |  |
| POST | `/api/v1/environments/{eid}/services/{sid}/monitors` | `CreateMonitorRequest` -> `Monitor` | Member | `monitor.create` |
| PATCH | `/api/v1/monitors/{id}` | `UpdateMonitorRequest` -> `Monitor` | Member | `monitor.update` |
| DELETE | `/api/v1/monitors/{id}` | 204 | Member | `monitor.delete` |
| GET | `/api/v1/environments/{eid}/services/{sid}/domains` | `[]Domain` | Viewer |  |
| POST | `/api/v1/environments/{eid}/services/{sid}/domains` | `CreateDomainRequest` -> `Domain` | Member | `domain.create` |
| PATCH | `/api/v1/domains/{id}` | `UpdateDomainRequest` -> `Domain` | Member | `domain.update` |
| DELETE | `/api/v1/domains/{id}` | 204 | Member | `domain.delete` |
| POST | `/api/v1/domains/{id}/check` | -> `Domain` | Member | `domain.check` |
| POST | `/api/v1/domains/{id}/retry-certificate` | `Domain` | Member | `domain.retry_certificate` |
| POST | `/api/v1/environments/{eid}/services/{sid}/tcp-proxies` | `CreateTCPProxyRequest` -> `TCPProxy` | Member | `tcp_proxy.create` |
| DELETE | `/api/v1/environments/{eid}/services/{sid}/tcp-proxies/{id}` | 204 | Member | `tcp_proxy.delete` |
| POST | `/api/v1/environments/{eid}/services/{sid}/volume` | `CreateVolumeRequest` -> `Volume` | Member | `volume.create` |
| GET | `/api/v1/environments/{eid}/services/{sid}/volume` | `VolumeDetail` | Viewer |  |
| PATCH | `/api/v1/environments/{eid}/services/{sid}/volume` | `UpdateVolumeRequest` -> `Volume` | Member | `volume.update` |
| DELETE | `/api/v1/environments/{eid}/services/{sid}/volume` | `ConfirmRequest` -> 204 | Admin | `volume.delete` |
| POST | `/api/v1/environments/{eid}/services/{sid}/volume/wipe` | `ConfirmRequest` -> 204 | Admin | `volume.wipe` |
| GET | `/api/v1/environments/{eid}/services/{sid}/backups` | `BackupsResponse` | Viewer |  |
| POST | `/api/v1/environments/{eid}/services/{sid}/backups` | `Backup` | Member | `backup.create` |
| PUT | `/api/v1/environments/{eid}/services/{sid}/backups/schedule` | `BackupSchedule` -> `BackupSchedule` | Member | `backup.schedule_update` |
| POST | `/api/v1/backups/{id}/lock` | `{locked}` | Member | `backup.lock` |
| DELETE | `/api/v1/backups/{id}` | 204 | Admin | `backup.delete` |
| POST | `/api/v1/environments/{eid}/services/{sid}/restore` | `RestoreRequest` -> `RestoreResponse` | Admin | `backup.restore` |
| POST | `/api/v1/environments/{eid}/services/{sid}/promote` | `PromoteRequest` -> 204 | Admin | `backup.promote` |
| POST | `/api/v1/environments/{eid}/services/{sid}/major-upgrade` | `MajorUpgradeRequest` -> `RestoreResponse` | Admin | `database.major_upgrade` |
| GET | `/api/v1/environments/{eid}/services/{sid}/connect-info` | `ConnectInfo` | Member |  |
| POST | `/api/v1/environments/{eid}/services/{sid}/regenerate-credentials` | `CommitResponse` | Admin | `database.regenerate_credentials` |
| GET | `/api/v1/environments/{eid}/services/{sid}/data` | `DataResponse` | Member |  |
| GET | `/api/v1/environments/{eid}/services/{sid}/extensions` | `[]Extension` | Member |  |
| POST | `/api/v1/environments/{eid}/services/{sid}/extensions` | `EnableExtensionRequest` -> `Extension` | Admin | `database.extension_enable` |
| GET | `/api/v1/environments/{eid}/services/{sid}/redis/keys` | `RedisKeysPage` (query `match=&cursor=`) | Member |  |
| POST | `/api/v1/environments/{eid}/services/{sid}/redis/keys/reveal` | `RedisRevealRequest` -> `RedisRevealResponse` | Member | `database.redis_reveal` |
| GET | `/api/v1/environments/{eid}/services/{sid}/cron-runs` | `[]CronRun` | Viewer |  |
| POST | `/api/v1/environments/{eid}/services/{sid}/cron-runs` | `CronRun` | Member | `cron.run_now` |
| GET | `/api/v1/environments/{eid}/services/{sid}/shell` | 204 (WebSocket, query `replica=&cols=&rows=&session=`) | Member | `service.shell` |
| GET | `/api/v1/environments/{eid}/services/{sid}/tunnel` | 204 (WebSocket, query `port=`) | Member | `service.tunnel` |

## Templates, GitHub, webhooks

| Method | Path | Body and response | Role | Audit |
|---|---|---|---|---|
| GET | `/api/v1/templates` | `[]TemplateSummary` (query `q=`) | Viewer |  |
| GET | `/api/v1/templates/{id}` | `TemplateDetail` (services, inputs with generated defaults) | Viewer |  |
| POST | `/api/v1/environments/{eid}/templates/{id}/deploy` | `{inputs}` -> `TemplateDeployResponse` | Member | `template.deploy` |
| GET | `/api/v1/icons/{name}` | SVG/PNG icons for templates, sources and the devicon picker | Public |  |
| GET | `/api/v1/github` | `GitHubStatus` | Viewer |  |
| POST | `/api/v1/github/app` | `{owner?: "org", returnTo?}` -> `{formUrl, manifest}` | Admin | `github.app_create` |
| GET | `/api/v1/github/app/callback` | 204 (302 redirect, query `code=`) | Admin | `github.app_callback` |
| GET | `/api/v1/github/repos` | `[]GitHubRepo` (query `q=&refresh=1`) | Member |  |
| GET | `/api/v1/github/branches` | `[]string` (query `repo=`) | Member |  |
| POST | `/api/v1/github/scan` | `ScanRequest` -> `ScanResult` (builder, start command, config files, packages, variable names, port) | Member | `github.scan` |
| POST | `/api/v1/github/webhook` | GitHub delivery (signature checked) | Public | `github.webhook` |
| GET | `/api/v1/projects/{pid}/webhooks` | `[]Webhook` | Admin |  |
| POST | `/api/v1/projects/{pid}/webhooks` | `Webhook` -> `Webhook` | Admin | `webhook.create` |
| PATCH | `/api/v1/webhooks/{id}` | `Webhook` -> `Webhook` | Admin | `webhook.update` |
| DELETE | `/api/v1/webhooks/{id}` | 204 | Admin | `webhook.delete` |
| POST | `/api/v1/webhooks/{id}/test` | -> `{status, responseCode}` | Admin | `webhook.test` |
| POST | `/api/v1/join` | 204 | Public | `agent.join` |
| GET | `/api/v1/connect` | 204 (WebSocket) | Public | `agent.connect` |
| GET | `/api/v1/uploads/{id}` | `application/gzip` | Public |  |

## Agent endpoints

Agents call these. They are also served on `agents.<console domain>` so you can firewall them separately. See [Agent protocol](https://5050cloud.com/docs/reference/agent-protocol).

| Method | Path | Auth |
|---|---|---|
| POST | `/api/agent/v1/join` | join token in body (rate limited 10 per minute per IP) |
| GET (WebSocket) | `/api/agent/v1/connect` | host token bound to machine-id and WireGuard key |
| GET | `/api/agent/v1/uploads/{id}` | host token; serves a `5050 up` tarball to the build host |

## Next steps

- [CLI overview](https://5050cloud.com/docs/cli/overview): the CLI calls these endpoints for you.
- [Security model](https://5050cloud.com/docs/reference/security-model): tokens, roles and the audit log.
