Docs/API/Reference
v0 ▾
API Reference·REST · v0·last updated 2 days ago

API Reference

The Scalable REST API lets you programmatically manage your web apps, databases, and account resources — the same surface the dashboard and CLI use, with stable contracts and webhooks.

Base URL · https://api.scalable.systems/v0 Format · application/json Auth · Bearer token IDs · UUID

Overview

All request and response bodies are application/json. Successful responses return 200 OK or 201 Created. Deletions return 204 No Content. Errors return a JSON body with an error field.

Generate API tokens from Settings → API Tokens in your account dashboard. Tokens are scoped per account.

!Conventions — All IDs are UUIDs. Timestamps are RFC 3339. Errors include a human-readable error field; rate-limit hits return 429 with a Retry-After header.

Authentication

The API uses Bearer token authentication. Include your API token in the Authorization header of every request — see the example to the right.

Create and revoke tokens under Settings → API Tokens in the dashboard. Each token is scoped to a single account.

scalable.yaml

Define multiple services in a single repository using a scalable.yaml configuration file. Place it at the repo root to declare web apps and workers with per-service Dockerfiles, build contexts, change-detection filters, and build-time secrets. When you connect the repo via "Deploy from GitHub", the file is auto-detected and all services are created.

On subsequent pushes, each service only rebuilds when files matching its filter pattern have changed — saving build time in monorepo setups. See the full schema on the right.

MCP Server

The platform exposes a Model Context Protocol (MCP) server, letting AI agents and tools manage your web apps directly — no copy-pasting curl commands. Endpoint: https://api.scalable.systems/v0/mcp. Pass your API token as a Bearer header on the SSE connection.

Drop-in for Claude Desktop, Cursor, Windsurf, CodexAdd the endpoint + token under mcpServers in your client config. Available tools include list_web_apps, create_web_app, update_web_app, get_web_app_logs, run_web_app_command, and the equivalent cron job tools. Sample config on the right.

Web Apps

Deploy and manage containerised web applications. Each web app runs in a chosen region and can be scaled within your plan limits.

GET List web apps

/accounts/{account_id}/web-apps

Returns all web apps belonging to the account.

Path param
account_idrequiredstring (uuid)UUID of the account.

POST Create web app

/accounts/{account_id}/web-apps

Deploy a new web app from a container image. The app starts deploying immediately (status SCALING).

Body
namerequiredstringA unique name for the web app within the account.
imagerequiredstringContainer image reference, e.g. nginx:latest.
portrequiredintegerPort your container listens on.
protocolstring"http" (default) or "grpc". gRPC apps are served over TLS externally and h2c internally.
!gRPC apps — Externally still served over TLS on port 443. Internally other services reach it at the grpc:// internal URL using cleartext HTTP/2 (h2c) on port 80.

GET Get web app

/accounts/{account_id}/web-apps/{web_app_id}

Returns a single web app with its current status, configuration, and metrics time series.

Query
rangestringTime range for metrics as a Go duration string, e.g. "1h", "24h", "7d". Defaults to "24h".
!The metrics object contains time-series arrays for cpu and memory. Values are percentages (0–100). Each point has an RFC 3339 timestamp and a numeric value.

PATCH Update web app

/accounts/{account_id}/web-apps/{web_app_id}

At least one field must be provided; omitted fields are left unchanged. Changing the image triggers a new deployment. Setting env_vars to {} clears all variables.

Body
imagestringNew container image to deploy.
portintegerContainer port.
env_varsobjectFull replacement of environment variables. Pass {} to clear all variables.
volume_idstring (uuid)UUID of a volume to attach, or "" to detach the currently attached one.

DELETE Delete web app

/accounts/{account_id}/web-apps/{web_app_id}

Deletes the web app and all associated Kubernetes resources and registry images. This action cannot be undone. Returns 204 No Content.

POST Restart

/accounts/{account_id}/web-apps/{web_app_id}/restart

Triggers a rolling restart of all pods without changing the image or configuration. The app continues serving traffic during the restart.

POST Suspend

/accounts/{account_id}/web-apps/{web_app_id}/suspend

Scales the deployment to zero replicas and removes its HPA, freeing namespace quota. The app stops serving traffic immediately. Use resume to restore it.

POST Resume

/accounts/{account_id}/web-apps/{web_app_id}/resume

Restores a suspended web app to its previous replica count and re-creates its HPA.

POST Configure scaling

/accounts/{account_id}/web-apps/{web_app_id}/scaling

Adjusts the HPA's min/max replica bounds and which metric drives scale-out. Cannot be called on suspended apps or apps with a volume attached.

Body
min_replicasintegerMinimum number of replicas. Must be ≥ plan minimum.
max_replicasintegerMaximum number of replicas. Must be ≥ min_replicas and ≥ 1.
scaling_modestringWhich metric drives scale-out: 'cpu', 'memory', or 'both' (default).

POST Configure resources per replica

/accounts/{account_id}/web-apps/{web_app_id}/resources

Sets the memory and CPU request for each pod. memory_mi sets both request and limit (MiB); cpu_milli sets only the CPU request in millicores (the plan ceiling remains the limit). Set either to 0 to reset it to the plan default.

!Changes are applied immediately via a rolling update — pods are replaced one at a time so the app continues serving traffic. Constraint: memory_mi × max_replicas ≤ account memory quota.

GET Get logs

/accounts/{account_id}/web-apps/{web_app_id}/logs

Returns log lines from all pods. When follow=true the response is a plain-text stream (Content-Type: text/plain) using chunked transfer-encoding.

Query
tailintegerMax lines to return (1–10000). Default 1000.
sinceintegerSeconds in the past (max 604800 / 7 days). Default 86400. Ignored when from is set.
fromdate-timeReturn logs from this RFC 3339 timestamp onwards.
todate-timeReturn logs up to this RFC 3339 timestamp.
followbooleanStream logs in real time. Default false.

GET List registry images

/accounts/{account_id}/web-apps/{web_app_id}/images

Returns all container images pushed to this web app's registry repository, newest first.

DELETE Delete registry image

/accounts/{account_id}/web-apps/{web_app_id}/images/{digest}

Deletes a specific image from the registry by its digest, e.g. sha256:abc123....

GET Get run context

/accounts/{account_id}/web-apps/{web_app_id}/run

Returns the working directory of a running pod. Returns 409 if no running pod is available — the app may be stopped or still starting.

POST Run command

/accounts/{account_id}/web-apps/{web_app_id}/run

Executes a one-off command inside a running pod and returns the combined stdout/stderr output. Tokens are joined and passed to sh -c. Times out after 10 minutes.

Body
commandrequiredstring[]Command tokens to execute, e.g. ["rails", "db:migrate"].
!Returns 409 if no running pod is available. A non-zero exit_code means the command failed. Arguments containing password=, secret=, token=, key=, or credential= are redacted in server logs.

Custom Domains

Attach one or more custom domains to a web app. Both apex (root) domains and CNAME subdomains are supported. Ownership must be verified by adding DNS records before a domain becomes active.

GET List custom domains

/accounts/{account_id}/web-apps/{web_app_id}/domains

Returns all custom domains attached to the web app, including verification status and required DNS records for each.

POST Add custom domain

/accounts/{account_id}/web-apps/{web_app_id}/domains

Registers a new custom domain and returns the DNS records required for verification. Subdomains require a CNAME record; apex (root) domains require an A record. The domain is not active until verified.

Body
domainrequiredstringThe custom domain to attach, e.g. app.example.com or example.com.

POST Verify custom domain

/accounts/{account_id}/web-apps/{web_app_id}/domains/{domain_id}/verify

Checks DNS records for the specified domain. On success, the TLS certificate is provisioned and the domain becomes active.

DELETE Delete custom domain

/accounts/{account_id}/web-apps/{web_app_id}/domains/{domain_id}

Removes a custom domain from the web app and revokes its TLS certificate.

Preview Environments

Ephemeral single-replica deployments scoped to a parent web app. Each preview gets its own Ingress, Service, and Deployment. Resource limits are inherited from the account plan. Status is always derived live from Kubernetes and is never stored in the database. Previews are automatically deleted after 7 days.

GET List previews

/accounts/{account_id}/web-apps/{web_app_id}/previews

Returns all active previews for the web app. Returns an empty array when none exist.

POST Create preview

/accounts/{account_id}/web-apps/{web_app_id}/previews

Deploys a new ephemeral preview environment. The deployment scales up immediately (status SCALING) and becomes READY within seconds. Returns 403 on Free plan accounts. Returns 409 if the namespace has reached the plan's pod quota.

Body
imagerequiredstringFull image reference to deploy.
slugstringURL-safe label. Defaults to the first 8 characters of the preview UUID.
env_overridesobjectKey-value pairs merged on top of the parent web app's env vars.
!env_overrides in the response is what was stored at creation — not the full merged env var set. To resolve the full set, fetch the parent web app's env_vars and merge client-side.

GET Get preview

/accounts/{account_id}/web-apps/{web_app_id}/previews/{preview_id}

Returns a single preview with its live status derived from Kubernetes.

DELETE Delete preview

/accounts/{account_id}/web-apps/{web_app_id}/previews/{preview_id}

Best-effort deletes the Ingress, Service, and Deployment from Kubernetes, then soft-deletes the database record. Never returns an error for missing Kubernetes resources.

Workers

Deploy and manage long-running background processes — queue consumers, scheduled jobs, data pipelines — without an exposed HTTP port. Workers run as Kubernetes Deployments and support rolling restarts and replica scaling.

GET List workers

/accounts/{account_id}/workers

POST Create worker

/accounts/{account_id}/workers

If image is omitted the worker record is created without a Kubernetes Deployment — supply an image via PATCH or push one via the registry to start the deployment.

Body
namerequiredstringA unique name for the worker within the account.
imagestringContainer image reference. Can be omitted and supplied later.
commandstringOverride the image CMD/ENTRYPOINT, e.g. "bin/jobs".
replicasintegerNumber of replicas (1–5). Defaults to 1.

GET Get worker

/accounts/{account_id}/workers/{worker_id}

Returns a single worker with its current status, restart count, and last deployed timestamp.

PATCH Update worker

/accounts/{account_id}/workers/{worker_id}

Update image, command, replica count, environment variables, or attached volume. Changing the image or command triggers a rolling restart.

!Due to ReadWriteOnce access mode, a volume can only be mounted on one worker at a time.

DELETE Delete worker

/accounts/{account_id}/workers/{worker_id}

Deletes the worker and its Kubernetes Deployment. This action cannot be undone.

POST Restart worker

/accounts/{account_id}/workers/{worker_id}/restart

Triggers a rolling restart of all worker pods without changing the image or configuration.

GET Get logs

/accounts/{account_id}/workers/{worker_id}/logs

Returns log lines from all pods. When follow=true the response is a plain-text stream. Log lines are formatted as HH:MM:SS.mmm [pod_name] message.

GET Get images

/accounts/{account_id}/workers/{worker_id}/images

Returns registry images pushed to this worker's repository, newest first.

POST Run command

/accounts/{account_id}/workers/{worker_id}/run

Executes a one-off command inside a running worker pod and returns combined stdout/stderr output. Times out after 10 minutes. Secret-like arg values are redacted in server logs.

Cron Jobs

Scheduled tasks that run on a cron expression. Two modes: container (runs a pod with a container image) and http (sends a request to any URL via curl). concurrencyPolicy is Forbid — only one instance runs at a time. Run history is stored in the platform database and available via the runs endpoint.

GET List cron jobs

/accounts/{account_id}/cron-jobs

POST Create cron job

/accounts/{account_id}/cron-jobs

Creates a Kubernetes CronJob in the account namespace. If the previous run has not finished, the next scheduled run is skipped.

Body
namerequiredstringUnique name for the cron job within the account.
moderequiredstring"container" — runs a pod with a container image. "http" — sends a request to a URL using curl.
schedulerequiredstringStandard 5-field cron expression, e.g. "0 9 * * 1".
timezonestringIANA timezone name. Defaults to "UTC".
imagestringContainer image. Required for container mode.
commandstringCommand override for container mode (space-split tokens).
urlstringTarget URL. Required for http mode.
http_methodstring"GET" or "POST". Default "POST".
http_bodystringOptional JSON body string for http mode POST requests.
env_varsobjectEnvironment variables. Supports secret:// references.
timeout_secondsintegerMax run duration. Default 3600. Pod is killed if it exceeds this.

GET Get cron job

/accounts/{account_id}/cron-jobs/{cron_job_id}

status values: PENDING (no runs yet), RUNNING, IDLE (last run succeeded), FAILED (last run failed), ERROR (CronJob resource missing from cluster).

PATCH Update cron job

/accounts/{account_id}/cron-jobs/{cron_job_id}

The Kubernetes CronJob is updated in-place — the next scheduled run will use the new values. Any running job is unaffected.

DELETE Delete cron job

/accounts/{account_id}/cron-jobs/{cron_job_id}

Deletes the CronJob and any owned Jobs from the cluster (background propagation). Run history is also deleted.

POST Trigger (manual run)

/accounts/{account_id}/cron-jobs/{cron_job_id}/trigger

Creates a Kubernetes Job from the CronJob's template immediately, outside the schedule. concurrencyPolicy Forbid is not applied — a manual job can run in parallel with a scheduled one.

GET List run history

/accounts/{account_id}/cron-jobs/{cron_job_id}/runs

Returns the 50 most recent runs, newest first. The platform reconciler writes run records from Kubernetes Job completions every 60 seconds, so there may be a short delay before a completed run appears.

triggerscheduledfired by cron expression
triggermanualfired via /trigger endpoint
statussucceededexit_code 0

Postgres

Managed PostgreSQL databases with automated backups, connection credentials, and private networking within the account namespace.

GET List databases

/v0/accounts/{account_id}/databases

POST Create database

/v0/accounts/{account_id}/databases

Create a new PostgreSQL database. A dedicated user and password are generated automatically. Body: name (required), size (small | medium | large, plan-dependent), engine ("postgres").

!Status progresses PROVISIONING → STARTING → HEALTHY. Poll GET until HEALTHY before connecting. The host is only reachable within the account namespace. Free plan does not allow any databases. Returns 403 if size not allowed for plan; 409 on name conflict or quota reached.

GET Get database

/v0/accounts/{account_id}/databases/{database_id}

Status values: PROVISIONING, STARTING, HEALTHY, RESTARTING, RESTORING, FAILED, TERMINATING, UNKNOWN. Credentials are always returned — there is no separate credentials endpoint.

PATCH Resize database

/v0/accounts/{account_id}/databases/{database_id}

Change size to small | medium | large. The database remains available during the resize. Returns 403 if the new size is not allowed for your plan tier.

DELETE Delete database

/v0/accounts/{account_id}/databases/{database_id}

Data cannot be recovered.

GET Get logs

/v0/accounts/{account_id}/databases/{database_id}/logs

Query: ?tail=100 (max 1000), ?from=<rfc3339>, ?to=<rfc3339>. Returns 400 if tail exceeds 1000.

GET POST Backups & restore

/v0/accounts/{account_id}/databases/{database_id}/backups · /restore

List completed backups. POST to trigger a manual backup (returns 202). Restore via POST /restore with {"backup_id": "..."}. All data written after the backup point will be lost.

!During restore the database status transitions to RESTORING, then back to HEALTHY on success. Automatic backups are re-enabled after a successful restore.

GET POST Extensions

/v0/accounts/{account_id}/databases/{database_id}/extensions[/available]

List currently-enabled extensions. Available lists what you can enable. POST { "extension": "pgvector" } enables one — poll until status reaches applied. Returns 409 if already enabled (or pending). Currently supported: pgvector.

Redis

Managed Redis instances with private networking. Credentials are returned on every read — store them securely. Instances are only accessible within the account namespace.

GET List instances

/v0/accounts/{account_id}/redis

POST Create instance

/v0/accounts/{account_id}/redis

Body: { "name": "cache" }. A secure password is generated automatically. Status will be SCALING immediately after creation, transitioning to READY once available. The connection_string uses the redis:// URI scheme and can be passed directly to most clients.

GET Get instance

/v0/accounts/{account_id}/redis/{redis_id}

Status values: PENDING, SCALING, READY, DEGRADED, FAILED. The password is always returned.

DELETE Delete instance

/v0/accounts/{account_id}/redis/{redis_id}

Data cannot be recovered after deletion.

Volumes

Persistent volumes are backed by PersistentVolumeClaims on SeaweedFS. Create a volume first, then attach it to a web app or worker via the volume_id field on PATCH /web-apps/:id or PATCH /workers/:id. Max size depends on your tier (Small: 5 GB, Medium/Large: 10 GB).

GET List volumes

/accounts/{account_id}/volumes

POST Create volume

/accounts/{account_id}/volumes

Provisions a PersistentVolumeClaim. Status starts as PENDING until bound by the storage class.

Body
namerequiredstringUnique name within the account. Must be a valid DNS label.
size_gbrequiredintegerSize in gigabytes. Min 1, max depends on tier.
mount_pathrequiredstringAbsolute path where the volume will be mounted, e.g. /data.

GET Get volume

/accounts/{account_id}/volumes/{volume_id}

status is one of PENDING, BOUND, or ERROR. attached_to is the name of the worker currently mounting this volume, or omitted if unattached.

PATCH Update volume

/accounts/{account_id}/volumes/{volume_id}

Updates the mount path metadata. To attach or detach from a worker, use PATCH /workers/:id instead.

DELETE Delete volume

/accounts/{account_id}/volumes/{volume_id}

Permanently deletes the PVC and its data. The pvc-protection finalizer is force-removed so the PVC terminates immediately. Returns 204 No Content.

Secrets

Store encrypted values and reference them in web app and worker environment variables using the secret:// scheme. Secret values are never returned by the API — only metadata (id, name, timestamps) is exposed.

GET List secrets

/accounts/{account_id}/secrets

POST Create secret

/accounts/{account_id}/secrets

Body: { "name": "DATABASE_PASSWORD", "value": "..." }. The value is encrypted at rest and never returned.

$
Reference a secret in env varsSet the variable value to secret://<secret_id> when creating or updating a web app or worker. The platform resolves the reference to the plaintext value before injecting it into the container.

PUT Update secret

/accounts/{account_id}/secrets/{secret_id}

Replaces the encrypted value. The name cannot be changed. All resources referencing this secret will pick up the new value on their next deployment or restart.

DELETE Delete secret

/accounts/{account_id}/secrets/{secret_id}

Soft-deletes the secret. Any web app or worker env vars referencing it via secret:// will fail to resolve on the next deployment.

GET List access logs

/accounts/{account_id}/secrets/{secret_id}/access-logs

Audit log of create, update, and delete actions performed on the secret, ordered newest first. user_id is omitted when the action was performed via an API token with no associated user session.

Users

Endpoints for the authenticated user's own profile.

GET Get current user

/users/me

Returns the authenticated user's profile. When called with an API token the response also includes a token object containing the token's name and the account it is scoped to — useful for resolving an account ID in CI/CD without visiting the dashboard.

!The token field is only present when authenticated with an API token. It is omitted for browser sessions and JWT auth.

Registry

The platform includes a built-in private container registry. Each web app gets its own repository. Push images to deploy new versions of your app.

CI/CD readyUse the API token as the registry password from GitHub Actions, GitLab CI, or any other runner. After pushing, a single PATCH call rolls the new image into production.
Was this page helpful?
Open in dashboard →