Try the real panel. No repo. No card. View your panel

API reference

Minimal JSON API for customer CI/CD and automation.

Authentication

Generate a token in Settings → API access. Send it on every request:

Authorization: Bearer YOUR_TOKEN

Tokens are stored as SHA-256 digests — the raw token is shown once at generation.

Endpoints

Base path: /api/v1

Account summary

GET /api/v1/account

Returns plan, usage counts, join URL (admin scope only), api_tokens_configured, integration status, alert thresholds, and retention settings.

Account stats

GET /api/v1/stats

Returns app/server counts (including deploying, unhealthy, maintenance), recent deploys, and recent events.

Regenerate team invite link

POST /api/v1/account/regenerate_join_code

Invalidates previous invite URLs. Returns updated account JSON.

Team members

GET    /api/v1/users
PATCH  /api/v1/users/:id
DELETE /api/v1/users/:id

List includes join_url and team_limit. Update role:

{ "user": { "role": "admin" } }

Roles: owner, admin, member.

Invite team member by email

POST /api/v1/users/invite
{ "email": "teammate@company.com" }

Requires SMTP (MAILBIRD_SMTP_PASSWORD). Queues the same invite email as Settings → Team. Returns { "status": "ok", "email": "..." }. Recorded in Activity under Team.

API tokens

Create tokens in Settings → API access (web UI) or via API (requires admin scope):

GET    /api/v1/api_tokens?limit=25&before_id=100
POST   /api/v1/api_tokens   { "name": "cursor", "scopes": ["read", "deploy"], "expires_in": 2592000 }
DELETE /api/v1/api_tokens/:id

List returns { items, has_more, before_id }. The raw token is returned once on create. Optional expiry: expires_in (seconds from now) or expires_at (ISO8601). Expired tokens fail authentication.

Available scopes

Scope Access
read Read account stats, events, team list
write App mutations (exec, env, port, branch) — not deploy hooks, auto-deploy, servers, or admin
admin Team invites, roles, join link, API token management
deploy Deploys, rollbacks, webhooks, maintenance
servers Servers, databases, backups, metrics, provisioning
env Environment variables (read + write)
processes App processes and cron jobs (read + write)
domains App domains, SSL verify, mail domains and mailboxes

Insufficient scope returns 403 with { "error": "Token missing scope: …" } or { "error": "Token missing read access" }.

Unknown scope names are rejected with 422 on token create. API rate limits are per token (120/min global) with additional hourly caps on sensitive actions:

Action Limit
App deploy, exec 10/minute
App destroy, rollback, redeploy 5/hour
App failover, wake, sleep, scale_web, scale_worker, deprovision_web_node, deprovision_worker_node, provision_standby, sync_hosted_dns, clone, transfer 5/hour
App regenerate_webhook_secret 5/hour
App test_hook, test_webhook 10/hour
App env_export, env import, env bulk_copy 10/hour
Bulk deploy 5/hour
Domain verify, sync_dns; mail sync_dns 10/hour
Database boot, reboot 10/hour
Database destroy 3/hour
Process sync / restart; cron sync 5/hour
Process / cron destroy 10/hour
Webhook delivery replay 10/hour
Mail domain create/destroy; mail sync_dns 5/hour
Mailbox create/destroy 10/hour
API token create 5/hour
Team invite 10/hour
Join code regenerate 3/hour
apply_manifest 5/hour
Manifest read (manifest, manifest_preview) 20/hour
Server destroy, provision, bootstrap, purge_backups, assign_floating_ip 5/hour
Server reboot 3/hour
Server collect_metrics 10/hour
Backup create 10/hour
Backup restore 3/hour
Backup download 20/hour
Pipeline promote 10/hour
Platform cert ensure 2/hour

Example CI token (deploy only):

{ "name": "github-actions", "scopes": ["read", "deploy"] }

List apps

GET /api/v1/projects?server_id=1&health_status=unhealthy&limit=25&before_id=100

Returns { items, has_more, before_id }. limit max 100.

Bulk deploy

POST /api/v1/projects/bulk_deploy
{ "project_ids": [1, 2, 3], "branch": "main" }

Queues deploys for up to 10 apps. Returns { queued: [...], skipped: [...] } with per-app reasons when blocked. Requires deploy scope. Rate limited to 5/hour per token.

Show app

GET /api/v1/projects/:id

Includes latest_deployment summary.

Create app

POST /api/v1/projects

Body:

{
  "project": {
    "name": "my-app",
    "server_id": 1,
    "git_url": "https://github.com/org/app.git",
    "branch": "main",
    "framework": "rails",
    "deploy_strategy": "kamal",
    "port": 3000
  }
}

Returns 201 Created or 422 with validation errors.

No git OAuth required — use a public HTTPS git_url, or embed a read-only token in the URL for private repos. Set "auto_deploy": false unless git is connected in the panel.

Create app from upload

POST /api/v1/projects/upload

Requires write scope. Send multipart/form-data (not JSON):

Field Required Description
source_archive yes .zip or .tar.gz of your project
name no App name (inferred from filename if omitted)
deploy no Queue first deploy (default true)

Example:

curl -X POST "https://tycoonbox.io/api/v1/projects/upload" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "source_archive=@app.zip" \
  -F "name=my-app" \
  -F "deploy=true"

Returns 201 Created with project JSON (source_kind: "upload", upload_filename, optional latest_deployment). Requires deploy scope when deploy=true.

Replace upload archive

POST /api/v1/projects/:id/replace_upload

Requires write scope; add deploy scope when deploy=true (default). Multipart field source_archive (.zip or .tar.gz). Replaces the stored archive and optionally queues a deploy.

curl -X POST "https://tycoonbox.io/api/v1/projects/42/replace_upload" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "source_archive=@app-v2.zip" \
  -F "deploy=true"

Git create — deploy after create

POST /api/v1/projects accepts optional query/body flag deploy_now=true (default). Queues a first deploy when the token includes deploy scope — no git OAuth required for public HTTPS URLs.

Trigger deploy

POST /api/v1/projects/:id/deploy

Optional preview branch (does not change app config):

{ "branch": "feature/checkout" }

Returns 202 Accepted with deployment JSON, or 409 if a deploy is already running.

Cancel queued deploy

POST /api/v1/projects/:id/cancel_deploy

Cancels the active queued or running deploy.

Or cancel by deployment id:

POST /api/v1/deployments/:id/cancel

Rollback

POST /api/v1/projects/:id/rollback

Runs managed or native rollback synchronously. Returns { "status": "ok" }.

Config redeploy (managed apps)

POST /api/v1/projects/:id/redeploy

Pushes updated env vars and secrets without rebuilding the app image.

Run command

POST /api/v1/projects/:id/exec

Body:

{ "command": "bin/rails db:migrate" }

Returns { "project_id": 1, "output": "...", "duration_ms": 842, "ran_at": "2026-09-15T00:00:00Z" }. Records a command_executed event with token audit metadata.

Command execution history

GET /api/v1/projects/:id/command_executions?limit=25&before_id=100

Returns { items, has_more, before_id }. Each item includes command, duration_ms, ran_at, and optional api_token_id / api_token_name. Requires deploy scope.

App logs

GET /api/v1/projects/:id/logs?lines=200&since=10m&drain=true
GET /api/v1/projects/:id/logs_stream?lines=200

`logs_stream` returns `text/event-stream` with `event: log` payloads (JSON-encoded line text). The panel logs page uses this instead of JSON polling.

Query params:

Param Description
lines Max lines to tail (default 200, max 500)
since Runtime log time filter (e.g. 10m, 1h, 2026-09-01T12:00:00)
drain When true, forward the tailed output to the app's log_drain_url (if configured)

Returns { "project_id": 1, "lines": "...", "captured_at": "2026-09-15T00:00:00Z", "drained": true }. drained is true only when drain=true and a log drain URL is set.

Health check

GET /api/v1/projects/:id/health

Runs the configured health probe (HTTP to health_check_path) and returns:

{
  "healthy": true,
  "health_status": "healthy",
  "health_error": null,
  "checked_at": "2026-09-09T12:00:00Z",
  "auto_failover": false,
  "health_failure_count": 0,
  "failover_threshold": 3
}

When auto_failover is enabled and a standby server is configured, three consecutive failed checks (~15 minutes via the cron job) trigger automatic promotion via Projects::FailoverPromoter.

Failover to standby

POST /api/v1/projects/:id/failover

Promotes the configured standby server to primary and queues a deploy. Returns { "status": "ok", "new_server_id": 2, "deployment_id": 42 }. Rate limited to 5/hour per token.

Sync hosted DNS

POST /api/v1/projects/:id/sync_hosted_dns

Syncs Route53 records for all hosted domains on the app. Returns per-domain results:

{
  "status": "ok",
  "synced": ["myapp.tycoonbox.app"],
  "failed": [],
  "domains": []
}

status is ok (all synced), partial (some failed), or failed (none synced). Returns 422 when every domain fails. Records a hosted_dns_synced event with token audit metadata. Rate limited to 5/hour per token. Requires servers scope.

Test deploy webhook

POST /api/v1/projects/:id/test_webhook

Sends a sample GitHub/GitLab/Bitbucket payload to the app's webhook URL.

Deployment status + logs

GET /api/v1/deployments/:id

Returns deploy log text for polling from CI. Log output is capped at 64KB in API responses; live deploy logs are retained up to 512KB server-side.

List deploy history

GET /api/v1/projects/:id/deployments?limit=25&before_id=100

Returns { items, has_more, before_id } for cursor pagination (newest first). limit max 100.

List servers

GET /api/v1/servers?limit=25&before_id=100

Returns { items, has_more, before_id } for cursor pagination (newest first). limit max 100.

Show server

GET /api/v1/servers/:id

Includes linked apps.

Create server

POST /api/v1/servers
{
  "server": {
    "name": "production",
    "provider": "hetzner",
    "region": "nbg1",
    "server_type": "cx22"
  }
}

For manual servers include "provider": "manual" and "ip_address".

Update server

PATCH /api/v1/servers/:id
{ "server": { "name": "prod-1", "ssh_port": 22 } }

Server firewall

GET /api/v1/servers/:id/firewall

Returns UFW status via SSH.

Collect metrics snapshot

POST /api/v1/servers/:id/collect_metrics

SSH probe + stores snapshot + runs alert checks.

Server metrics

GET /api/v1/servers/:id/metrics

Returns load average, memory, and disk usage via SSH.

Server metrics history

GET /api/v1/servers/:id/metrics_history?limit=48&before_id=100

Returns { items, has_more, before_id } for cursor pagination. limit defaults to 48, max 168 (7 days at 15-minute intervals). Snapshots are retained 7 days.

Provision server

POST /api/v1/servers/:id/provision

Returns 202 Accepted for cloud servers or marks manual servers active.

Bootstrap server

POST /api/v1/servers/:id/bootstrap

Returns 202 Accepted. Requires SSH key in account settings.

Reboot server

POST /api/v1/servers/:id/reboot

Sends reboot over SSH. Requires SSH key in account settings.

Delete server

DELETE /api/v1/servers/:id?confirm=true

Requires confirm=true. Removes the server record and deprovisions cloud VMs when applicable. Blocked when apps (including cluster nodes), databases, or standby assignments remain. On app blockers, the 422 response includes blocking_apps with each app's id, name, role (primary, web, or worker), and optional process_name. Rate limited to 5/hour per token.

Purge old backups

POST /api/v1/servers/:id/purge_backups

Deletes backup files on the server older than the account's backup_retention_days setting.

Assign floating IP

POST /api/v1/servers/:id/assign_floating_ip

Creates or reassigns a floating IP on the server and copies it to linked apps' load_balancer_ip when unset. Requires a cloud API token and a server provisioned via cloud.

Returns { "status": "ok", "floating_ip": "203.0.113.10", "cloud_floating_ip_id": "12345" }.

Update app

PATCH /api/v1/projects/:id

Body (any of):

{
  "project": {
    "branch": "main",
    "port": 3000,
    "health_check_path": "/up",
    "release_command": "bundle exec rails db:migrate",
    "auto_deploy": true,
    "load_balancer_ip": "203.0.113.10",
    "standby_server_id": 2,
    "auto_failover": true,
    "auto_floating_ip": true
  }
}

When auto_floating_ip is enabled, Tycoonbox provisions or transfers a floating IP to the primary server on save and during failover.

When auto_load_balancer is enabled (requires 2+ cloud web nodes in the same cluster), Tycoonbox creates a cloud load balancer, syncs targets on cluster edits, and sets load_balancer_ip to the LB public IP. After a custom domain is DNS-verified (ssl_status: active), the load balancer gets HTTPS termination on port 443 with HTTP→HTTPS redirect. Mutually exclusive with auto_floating_ip.

Delete app

DELETE /api/v1/projects/:id?confirm=true

Requires confirm=true. Tears down review apps, git webhooks, remote managed/native resources, then deletes the app record. Rate limited to 5/hour per token.

Activity feed

GET /api/v1/events?limit=25&before_id=100&event_action=deploy_succeeded&eventable_type=Project&since=2026-09-01T00:00:00Z

Recent account events (deploys, backups, alerts, config changes). Filter by event_action, eventable_type, or since (ISO8601). Returns { items, has_more, before_id } for cursor pagination. limit max 100.

App domains

GET    /api/v1/projects/:project_id/domains?limit=25&before_id=100
POST   /api/v1/projects/:project_id/domains
POST   /api/v1/projects/:project_id/domains/:id/verify
DELETE /api/v1/projects/:project_id/domains/:id?confirm=true

List returns { items, has_more, before_id }. Delete requires confirm=true and records API token audit.

Create body:

{ "domain": { "hostname": "app.example.com", "primary": true } }

List server databases

GET /api/v1/servers/:server_id/databases?limit=25&before_id=100

Returns { items, has_more, before_id } for cursor pagination. limit max 100.

connection_url is omitted unless the token has write scope; read-only tokens get credentials_redacted: true on each database record.

Create database

POST /api/v1/servers/:server_id/databases
{ "database": { "name": "myapp_production", "engine": "postgresql", "project_id": 1, "backup_schedule": "daily" } }

SQLite uses a host volume mounted at /rails/storage — assign to an app and deploy; no accessory boot step. DATABASE_URL is injected automatically (e.g. sqlite3:/rails/storage/myapp_production.sqlite3).

Update database

PATCH /api/v1/servers/:server_id/databases/:id
{ "database": { "backup_schedule": "weekly", "project_id": 2 } }

Delete database

DELETE /api/v1/servers/:server_id/databases/:id?confirm=true

Requires confirm=true (safety gate). Removes the database record and deprovisions managed database services when booted. Rate limited to 3/hour per token.

Boot / reboot database accessory

POST /api/v1/servers/:server_id/databases/:id/boot
POST /api/v1/servers/:server_id/databases/:id/reboot

Requires the database assigned to a managed (container) app.

List backups

GET /api/v1/servers/:server_id/databases/:database_id/backups?limit=25&before_id=100

Returns { items, has_more, before_id }. limit max 100.

Create backup

POST /api/v1/servers/:server_id/databases/:database_id/backups

Returns 202 Accepted with backup JSON. Supported engines: postgresql, mysql, redis, sqlite.

Backup status

GET /api/v1/backups/:id

Download backup

GET /api/v1/backups/:id/download

Streams the .sql.gz file from the server.

Restore backup

POST /api/v1/backups/:id/restore

Requires confirm=true (safety gate). Optional target database on the same server:

{ "confirm": true, "target_database_id": 2 }

Returns 202 Accepted. Overwrites live data in the target database. Rate limited to 3/hour per token.

Transfer app to another account

POST /api/v1/projects/:id/transfer?confirm=true
{
  "target_join_code": "abc123",
  "target_server_id": 2
}

Moves the app (env vars, processes, domains) to another account. Requires admin scope and confirm=true. Target is identified by team join code (Settings → Team). The target server must belong to that account. Blocked while deploying, when databases remain on other servers, or when the app is in a pipeline. Clears cluster assignments, webhooks, and standby config — redeploy required on the target server. Rate limited to 5/hour per token.

Clone app

POST /api/v1/projects/:id/clone

Body:

{ "name": "my-app-staging", "server_id": 1 }

Copies env vars and process definitions from the source app. Requires write + env scope (clone duplicates secrets). Respects account app limits. Rate limited to 5/hour per token. Records app_cloned with token audit metadata.

Maintenance mode

POST /api/v1/projects/:id/maintenance

Body:

{ "enabled": true }

Environment variables

GET    /api/v1/projects/:project_id/environment_variables?limit=25&before_id=100&q=SECRET
POST   /api/v1/projects/:project_id/environment_variables
PATCH  /api/v1/projects/:project_id/environment_variables/:id
DELETE /api/v1/projects/:project_id/environment_variables/:id

List returns { items, has_more, before_id } for cursor pagination. Filter keys with q (case-insensitive substring). limit max 100. Values are never returned — only whether a value is set.

Create body:

{ "environment_variable": { "key": "RAILS_MASTER_KEY", "value": "..." } }

Update body:

{ "environment_variable": { "value": "..." } }

Bulk import (.env format — comments and quoted values supported):

POST /api/v1/projects/:project_id/environment_variables/import

Body (either form):

{ "env": "RAILS_MASTER_KEY=abc\nDATABASE_URL=postgres://..." }

Or:

{
  "environment_variables": [
    { "key": "RAILS_MASTER_KEY", "value": "abc" },
    { "key": "DATABASE_URL", "value": "postgres://..." }
  ]
}

Returns { "imported": 2, "skipped": 0, "errors": [] }. Payload limits: 64KB, 500 lines, 200 keys (oversize returns 413 or 422).

Formation (Heroku ps:scale)

GET   /api/v1/projects/:project_id/formation
PATCH /api/v1/projects/:project_id/formation?sync=true

Show returns { "project_id", "formation": { "web": 1, "worker": 2 }, "dyno_count": 3 } — dyno counts per non-cron process. Requires read plus processes or deploy scope.

Update accepts a flat JSON body (Heroku-style) or a wrapped formation object:

{ "web": 2, "worker": 1 }

Or { "formation": { "web": { "quantity": 2 } } }. Optional sync query param (default true) enqueues a process sync after scaling. Returns { "project_id", "formation", "changed", "sync_enqueued" }. Requires processes write scope. Rate limited to 20/hour.

Scaling up on cloud primaries auto-queues scale_web / scale_worker jobs when host capacity is short; scaling down deprovisions surplus nodes.

CLI equivalent: tycoonbox ps:scale APP web=2 worker=1

App processes

GET    /api/v1/projects/:project_id/app_processes?limit=25&before_id=100
POST   /api/v1/projects/:project_id/app_processes
PATCH  /api/v1/projects/:project_id/app_processes/:id
DELETE /api/v1/projects/:project_id/app_processes/:id?confirm=true
POST   /api/v1/projects/:project_id/app_processes/sync
POST   /api/v1/projects/:project_id/app_processes/:id/restart

List returns { items, has_more, before_id } (cron jobs excluded). Each process supports instances (1–10, default 1) for web/worker dyno-style scaling. Managed deploys run one instance per host — when host_shortfall > 0, Tycoonbox auto-queues scale_web jobs on cloud primaries (web and worker processes). Cron jobs always use 1 instance.

Scale web nodes

POST /api/v1/projects/:id/scale_web?count=2

Optional count (1–10, default 1) provisions multiple cloud web nodes in one request. Returns { "status": "queued", "message": "...", "count": 2 }. Requires servers scope. Rate limited to 5/hour per token.

Lowering web instances on a cloud app auto-queues deprovision of surplus extra web nodes.

Scale worker nodes

POST /api/v1/projects/:id/scale_worker?process_name=worker&count=2

Required process_name (must match a worker process). Optional count (1–10, default 1) provisions a dedicated worker node when missing, then queues extra web nodes for overflow capacity. Returns { "status": "queued", "process_name": "worker", "count": 2, "worker_nodes": 1, "web_nodes": 1 }. Requires servers scope.

Deprovision cluster nodes

POST /api/v1/projects/:id/deprovision_web_node?server_id=123
POST /api/v1/projects/:id/deprovision_worker_node?process_name=worker&server_id=456

Manually remove an extra web node or dedicated worker node (cannot remove the primary server). Queues deprovision and redeploy. Returns { "status": "queued", "server_id": … }. Requires servers scope. Scaling formation or instances down also auto-queues surplus deprovisions.

Process autoscale

Web and worker processes support optional metric autoscaling (evaluated after each metrics collection cycle, ~15 min):

Field Default Description
autoscale_enabled false Enable CPU/memory-based scaling
autoscale_min_instances 1 Floor
autoscale_max_instances 10 Ceiling
autoscale_up_pct 70 Scale up when avg CPU or memory exceeds this
autoscale_down_pct 30 Scale down when avg CPU and memory are below this
autoscale_requests_up_rpm 0 Scale up when avg requests/min exceeds this (0 = off)
autoscale_requests_down_rpm 0 Scale down when avg requests/min is below this (0 = off)

Included in process create/update JSON. Records process_autoscaled events (includes requests_rpm when request thresholds are set).

First-party resources (marketplace)

GET  /api/v1/resource_catalog
GET  /api/v1/projects/:project_id/resources
POST /api/v1/projects/:project_id/resources
PATCH /api/v1/projects/:project_id/resources/:id
DELETE /api/v1/projects/:project_id/resources/:id?confirm=true

Catalog returns { items: [{ key, engine, kind, label, description, env_key, backup_schedule, manifest_keys, plans? }] } for Postgres, MySQL, Redis, and Mail — all first-party, no third-party add-ons.

Database entries include plan tiers (hobby, standard) with memory limits. Free accounts are limited to hobby.

Attach body: { "engine": "mysql", "plan": "standard" } or { "engine": "mail" }. Resize body: { "plan": "standard" } — updates memory limits and queues a database service reboot when booted. List returns databases and attached mailboxes (kind: database | mail). Create/update/destroy require deploy scope. Connection URLs and SMTP secrets are injected on deploy.

Manifest plan changes on existing resources are applied via Apply manifest (preview shows plan diffs).

Delete requires confirm=true. Rate limited to 10/hour per token.

Bulk env copy (requires write scope — env-only tokens cannot copy secrets):

POST /api/v1/projects/:project_id/environment_variables/bulk_copy
{ "source_project_id": 2, "overwrite": false, "keys": ["RAILS_MASTER_KEY"] }

Rate limited to 10/hour per token. Max 200 keys per copy (same as env import).

Process sync and restart are rate limited to 5/hour per token.

Create body:

{
  "app_process": {
    "name": "worker",
    "kind": "worker",
    "command": "bundle exec sidekiq"
  }
}

Kinds: web, worker (cron jobs use the dedicated endpoint below).

Cron jobs

Hatchbox-compatible scheduled tasks. Backed by AppProcess records with kind: cron.

GET    /api/v1/projects/:project_id/cron_jobs?limit=25&before_id=100
POST   /api/v1/projects/:project_id/cron_jobs
PATCH  /api/v1/projects/:project_id/cron_jobs/:id
DELETE /api/v1/projects/:project_id/cron_jobs/:id?confirm=true
POST   /api/v1/projects/:project_id/cron_jobs/sync

List returns { items, has_more, before_id }. Delete requires confirm=true. Rate limited to 10/hour per token.

Create body:

{
  "cron_job": {
    "name": "nightly-report",
    "schedule": "0 2 * * *",
    "command": "bin/rails reports:nightly"
  }
}

Response includes schedule_label (human-readable preset when matched).

Export environment

GET /api/v1/projects/:id/env_export

Returns { "env": "KEY=value\n..." } with secrets included. Requires write scope (read+env tokens cannot export). Rate limited to 10/hour per token.

App metrics and usage

GET /api/v1/projects/:id/metrics?history_limit=96
GET /api/v1/projects/:id/usage

metrics returns live runtime stats, request/error counts, and recent snapshots (managed apps). history_limit defaults to 96, max 168. usage returns { formation, dyno_count, container_hours, requests_24h, bandwidth_mb_24h, period_start, period_end }. Requires read plus deploy, processes, or domains scope.

Repo manifest

GET  /api/v1/projects/:id/manifest
GET  /api/v1/projects/:id/manifest_preview
POST /api/v1/projects/:id/apply_manifest

Read and preview tycoonbox.json from the connected git repo. apply_manifest validates formation/env/resources, applies changes, records manifest_applied, and returns { status, warnings }. Requires deploy scope. Git-heavy reads rate limited to 20/hour; apply to 5/hour per token.

Deploy-config PATCH fields (auto_deploy, release_command, hooks, build_script) require deploy scope even when the token has write.

Renaming (name) is blocked while the app is deploying (409). Successful renames record app_renamed and return rename_notice for managed apps (slug/service change — redeploy recommended).

Activity feed

GET /api/v1/activity?project_id=1&limit=25&before=2026-09-14T12:00:00.000Z&deployments=true

Unified timeline of deploys and account events. Filter with event_action, eventable_type, or pass before (ISO8601) to page older items. Returns { items, has_more, before }. Requires read. limit max 100.

Pipelines

GET   /api/v1/pipelines?limit=25&before_id=100
GET   /api/v1/pipelines/:id
POST  /api/v1/pipelines        { "project_id": 1 }
PATCH /api/v1/pipelines/:id    { "auto_promote": true }
POST  /api/v1/pipelines/:id/promote?confirm=true  { "stage_id": 2 }

Index returns { items, has_more, before_id }. show includes per-stage promotion metadata: promotable, blocked_reason, source_commit_sha, target_latest_commit_sha, and already_on_target.

Staging → production promotion workflows. promote requires confirm=true and records API token audit on pipeline_promote_queued. Requires deploy scope for mutations.

Platform TLS

GET  /api/v1/platform
POST /api/v1/platform/ensure_letsencrypt_certificate
POST /api/v1/platform/ensure_wildcard_certificate

Hosted-domain TLS status and certificate provisioning (Route53/ACM). Requires servers scope to read; admin + servers for cert mutations (2/hour per token).

Webhook delivery log

GET  /api/v1/projects/:id/webhook_deliveries?limit=50&before_id=100
POST /api/v1/projects/:id/webhook_deliveries/:delivery_id/replay

Recent git webhook attempts (accepted, branch mismatch, duplicate, etc.). Returns { items, has_more, before_id }; each item includes replayable for statuses disabled, branch_mismatch, blocked, and conflict.

Replay re-queues a deploy on the app's configured branch (useful after fixing auto-deploy settings or branch config). Requires deploy scope. Rate limited to 10/hour per token.

Cluster assignment

Requires servers scope.

PATCH /api/v1/projects/:id/web_hosts
{ "web_server_ids": [2, 3] }

Adds extra web nodes (excluding the primary server). Returns web_hosts, web_server_ids, and worker_assignments.

PATCH /api/v1/projects/:id/worker_hosts
{ "worker_server_assignments": { "sidekiq": 4, "jobs": 5 } }

Maps worker process names to dedicated servers. Omit or set 0 to run on the primary server.

Deploy config export

GET /api/v1/projects/:id/kamal_config

Returns generated deploy.yml for managed (container) apps.

Regenerate git webhook secret

POST /api/v1/projects/:id/regenerate_webhook_secret

Returns new webhook_url for GitHub/GitLab/Bitbucket auto-deploy.

Deploy hooks

Set via PATCH /api/v1/projects/:id:

{
  "project": {
    "pre_deploy_hook": "bin/rails assets:precompile",
    "post_deploy_hook": "bin/rails db:migrate"
  }
}

Managed apps only. Panel scripts override repo .tycoonbox/hooks/ when set. Hooks notify the panel via deploy callback URL.

Build scripts and tool versions

Native deploys install mise during server bootstrap. When the repo contains .tool-versions, .ruby-version, or .node-version, deploy runs mise install before building.

Set a custom build via PATCH /api/v1/projects/:id:

{
  "project": {
    "build_script": "bundle install\nnpm run build"
  }
}

Priority: repo .tycoonbox/build → panel build_script → framework defaults (Rails/Hanami/Node).

Managed deploys log detected tool versions and apply Ruby/Node version hints from the repo when present.

Frameworks: rails, hanami, node. Auto-detect also recognizes Astro (Node SSR) and Sidekiq workers.

Test deploy hook

POST /api/v1/projects/:id/test_hook

Body:

{ "phase": "pre-deploy" }

Runs the hook script over SSH (without deploying). Phase: pre-deploy or post-deploy. Returns { "output": "..." }. Requires SSH key in account settings.

Deployment detail (GET /api/v1/deployments/:id) includes hook_callback_url for hook scripts to report progress.

Mail domains

GET    /api/v1/mail_domains?limit=25&before_id=100
GET    /api/v1/mail_domains/:id
POST   /api/v1/mail_domains
DELETE /api/v1/mail_domains/:id
POST   /api/v1/mail_domains/:id/sync_dns

List returns { items, has_more, before_id }.

Create body:

{ "mail_domain": { "name": "example.com" } }

Show includes DNS record hints and mailboxes. Delete requires confirm=true and deprovisions mailcow + all mailboxes.

Mailboxes

GET    /api/v1/mail_domains/:mail_domain_id/mailboxes?limit=25&before_id=100
POST   /api/v1/mail_domains/:mail_domain_id/mailboxes
DELETE /api/v1/mail_domains/:mail_domain_id/mailboxes/:id?confirm=true

List returns { items, has_more, before_id }. Mailbox delete requires confirm=true.

{
  "mailbox": {
    "local_part": "hello",
    "display_name": "Hello",
    "purpose": "app",
    "quota_mb": 1024,
    "project_id": 1
  }
}

Examples

TOKEN="your-token"
HOST="https://panel.example.com"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/projects"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"project":{"name":"api-app","server_id":1,"git_url":"https://github.com/org/app.git"}}' \
  "$HOST/api/v1/projects"

curl -s -X POST -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/projects/1/deploy"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"branch":"staging"}' \
  "$HOST/api/v1/projects/1/deploy"

curl -s -X POST -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/projects/1/rollback"

curl -s -X POST -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/projects/1/redeploy"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"command":"bin/rails db:migrate"}' \
  "$HOST/api/v1/projects/1/exec"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/servers/1/metrics"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/servers/1/databases"

curl -s -X POST -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/servers/1/databases/2/backups"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"target_database_id":2}' \
  "$HOST/api/v1/backups/5/restore"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"api-staging"}' \
  "$HOST/api/v1/projects/1/clone"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"enabled":true}' \
  "$HOST/api/v1/projects/1/maintenance"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/deployments/42"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/projects/1/logs"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/servers"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/projects/1/environment_variables"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"environment_variable":{"key":"API_SECRET","value":"abc"}}' \
  "$HOST/api/v1/projects/1/environment_variables"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/projects/1/health"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"env":"FOO=bar\nBAZ=qux"}' \
  "$HOST/api/v1/projects/1/environment_variables/import"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/projects/1/app_processes"

curl -s -X POST -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/servers/1/reboot"

curl -s -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"project":{"branch":"staging"}}' \
  "$HOST/api/v1/projects/1"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"database":{"name":"myapp_production","engine":"postgresql","backup_schedule":"daily"}}' \
  "$HOST/api/v1/servers/1/databases"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/events?event_action=deploy_succeeded&limit=10"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/account"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"server":{"name":"staging","provider":"manual","ip_address":"203.0.113.10"}}' \
  "$HOST/api/v1/servers"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/projects/1/env_export"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/mail_domains"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/users"

curl -s -X POST -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/account/regenerate_join_code"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/projects?health_status=unhealthy"

curl -s -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/stats"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"phase":"pre-deploy"}' \
  "$HOST/api/v1/projects/1/test_hook"

curl -s -X POST -H "Authorization: Bearer $TOKEN" "$HOST/api/v1/servers/1/purge_backups"

Database accessories

Use the panel (Server → database → Boot / Reboot / Remove) or wait for the next app deploy to boot new databases automatically.

Successful backups can be downloaded from the server page or via the panel backup show URL. PostgreSQL, MySQL, and SQLite backups can be restored from the server page (overwrites live data). Redis backups are download-only.