Skip to content

App resources API

Routes nested under an app. {appId} is the app’s id. Permission levels refer to the app’s team (see API overview).

Method Path Permission Body Returns
GET /apps/{appId}/domains/ Viewer {results}
POST /apps/{appId}/domains/ Developer {hostname, isPrimary?} The domain (ssl_status: "none")
POST /apps/{appId}/domains/{id}/ssl Developer {detail: "Certificate issuance queued."}
DELETE /apps/{appId}/domains/{id} Developer {detail: "Domain removed."}

Domain objects are rows: id, app_id, hostname, is_primary, is_verified, verification_token, ssl_status (none, pending, issued, expired, failed), ssl_expires_at, ssl_issuer, created_at.

When to use: add the domain once DNS points at the server, then call /ssl. Only web apps accept domains (400 otherwise). The hostname must be a valid DNS name. It’s lowercased and must not be attached to any app already (409 domain_already_attached or 409 domain_in_use). Adding and removing a domain rewrites the vhost. Issuing runs certbot --nginx through the worker. See Custom domains and TLS.

Method Path Permission Body / query Returns
GET /apps/{appId}/cron/ Viewer {results}: jobs
GET /apps/{appId}/cron/preview Authenticated Query schedule {schedule, next: [5 ISO times]}, or 400 invalid_schedule
POST /apps/{appId}/cron/ Developer {name, schedule, command, timeoutSeconds?, enabled?} The job
PATCH /apps/{appId}/cron/{jobId} Developer Any of the create fields The job
DELETE /apps/{appId}/cron/{jobId} Developer {detail}. Deletes its runs too.
POST /apps/{appId}/cron/{jobId}/run Developer The run. 409 if a run is still going.
GET /apps/{appId}/cron/{jobId}/runs Developer {results}: the last 50 runs, newest first
Field Rules
name Required, ≤ 100 characters.
schedule Required. 5-field cron expression or @hourly, @daily, @weekly, @monthly, @yearly, in UTC.
command Required, ≤ 4000 characters.
timeoutSeconds 1 to 3600, default 300.
enabled Default true.

Jobs (camelCase): id, appId, name, schedule, command, timeoutSeconds, enabled, nextRunAt, lastRunAt, lastStatus, createdAt. Runs: id, jobId, execId, trigger, status (running, succeeded, failed, timed_out, skipped), exitCode, output (last 200 lines), startedAt, finishedAt.

When to use: recurring maintenance in the app’s environment. An app can have at most 20 jobs. Stream a run’s live output on /ws/apps/{appId}/exec/{execId}. See Cron jobs.

Method Path Permission Query Returns
GET /apps/{appId}/metrics/ Viewer minutes (default 60) {app: {id, name, status}, samples}

Samples are rows, oldest first: id, app_id, cpu_percent (percent of one core), mem_bytes, rss_bytes, sampled_at. For the latest sample of every app at once, use GET /apps/metrics-summary (Apps API).

Method Path Permission Query Returns
GET /apps/{appId}/runtime-logs/ Viewer lines (1 to 2000, default 200) {lines: [...]}, plus error if the agent failed, or detail when the app was never deployed

When to use: a one-shot tail for scripts and AI assistants that can’t hold a WebSocket open. For a live stream, use /ws/apps/{appId}/logs. Times out with 504 logs_timeout after 15 seconds. Build logs are on the Deployments API.

Method Path Permission Body Returns
GET /storage/apps/{appId}/links Viewer {results}: {id, appId, bucketId, envPrefix, bucketName, providerName, providerKind, envVars, createdAt}
POST /storage/apps/{appId}/links Superuser {bucketId, envPrefix?} (default S3, ≤ 32 characters) {id, appId, bucketId, envPrefix}
DELETE /storage/apps/{appId}/links/{linkId} Developer {detail: "Bucket unlinked."}

Linking writes these env vars on the app (with your prefix): S3_ENDPOINT, S3_REGION, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY, S3_PUBLIC_URL, S3_FORCE_PATH_STYLE. Unlinking removes them. Redeploy for either to take effect. Linking is superuser-only because any developer can read an app’s env vars. Buckets themselves are on the Infrastructure API.