Apps
Manage app deployments, networking, domains, and monitoring.
Manage app deployments on Cosmoner's managed app hosting.
List Apps
GET /v1/projects/:projectId/appsReturns all apps in the project, ordered by creation date (newest first).
Auth: Required (member)
Get App
GET /v1/projects/:projectId/apps/:appIdReturns app details. Performs a lazy status sync with the hosting platform if the app is still deploying.
Auth: Required (member, scope: apps:read)
Preview Cost
GET /v1/projects/:projectId/apps/preview?size=<slug>Returns a prorated cost preview for creating an app with the given size, including the subtotal, estimated tax, any applied credit, and the total due today.
Auth: Required (member, scope: apps:read)
Save Draft
POST /v1/projects/:projectId/apps/draftSaves app wizard configuration as a draft. Drafts expire after 24 hours.
A draft filled from a repository's configuration file may carry a config object — { path, serviceName, fileDigest } — which is recorded on the app it creates. It says which file and service the settings came from, so a later read of that file can tell a repository that has moved on from settings changed in the control panel afterwards.
Auth: Required (member, write access, scope: apps:write)
Create App
POST /v1/projects/:projectId/appsDeploys an app from a saved draft. The prorated amount is charged to the project's payment method off-session and the response is { deployed: true, appId }. If the project has no card, the request returns 402 — see Billing → Deploy Payment Errors.
Auth: Required (member, write access, scope: apps:write)
Request Body:
{
"draftId": "draft_..."
}Update App
PATCH /v1/projects/:projectId/apps/:appIdUpdates app settings (name, env vars, build settings, scaling). Triggers a redeploy. A variable the build reads takes effect on the next build, not the redeploy — see Environment variables at build time.
For an app deployed from an image in one of the project's registries,
imageDeployPolicy decides what a push to its repository does:
| Value | A push to the repository |
|---|---|
TAG (default) | Redeploys the app when the tag it uses is pushed again. Other tags, and an app pinned to a digest, are left alone. |
NEWEST | Deploys whatever tag was pushed and switches the app to that tag. |
MANUAL | Does nothing. The app moves only through Deploy Image. |
Auth: Required (member, write access, scope: apps:write)
Environment variables at build time
A git-source app's environment variables reach its build as well as its
running container, so a value a framework inlines while building —
NEXT_PUBLIC_*, VITE_* — is there.
How a variable arrives depends on whether it is marked secret:
- A plain variable is a build argument. A Dockerfile reads it with
ARG NAME; an auto-detected build and a static site's build command see it in their environment without declaring anything. - A secret is a BuildKit secret. It is mounted for the command that reads
it and is never written to an image layer, the image history or the shared
build cache. A Dockerfile opts in per step with
RUN --mount=type=secret,id=NAME,env=NAME <command>; auto-detected and static builds expose it in the command's environment the same way a plain one is.
Names must be identifiers ([A-Za-z_][A-Za-z0-9_]*). A variable named
otherwise still reaches the container but is left out of the build.
A changed variable is picked up by the next build — a push to the tracked
branch, or Redeploy. Variables prefixed RAILPACK_ also
configure an auto-detected build; see the
Railpack reference.
Check Configuration File
GET /v1/projects/:projectId/apps/:appId/configCompares the app against the configuration file it was created from, and reports what differs. Read-only in both directions: nothing about the app changes, and nothing is applied from the file.
Auth: Required (member, scope: apps:read)
An app that was never filled from a file returns { "checked": false, "reason": "not_watched" }. When the file cannot be read — a revoked git connection, a provider that is down — the reason is "unavailable" instead, which is not the same answer as the file being absent.
Response (settings differ):
{
"success": true,
"data": {
"checked": true,
"found": true,
"valid": true,
"serviceFound": true,
"path": ".cosmoner/deployment.yaml",
"serviceName": "api",
"fileChanged": true,
"entries": [
{ "field": "run_command", "status": "changed_in_file", "file": "node server.js", "current": "bun start" },
{ "field": "instances", "status": "changed_in_dashboard", "file": "2", "current": "4" }
],
"missingRefs": []
}
}status says which side moved since the file was last applied:
| Status | Meaning |
|---|---|
changed_in_file | The file changed; the app still holds what the file last applied. |
changed_in_dashboard | The app changed since the file was applied; the file has not moved. |
conflict | Both moved, to different values. |
differs | They differ, and the app has no record of what the file applied, so which one moved is unknowable. |
Values are for display, and a secret is never among them: a linked entry reads as linked secret or linked variable, and a variable marked secret in the file reads as ••••.
The file may also be gone ("found": false), no longer valid ("valid": false, with an error), or no longer define the service the app was created from ("serviceFound": false, with the serviceName it looked for). In each of those the app itself is unaffected — the repository has simply stopped describing it.
Apply Configuration File
POST /v1/projects/:projectId/apps/:appId/config/applyWrites the settings the configuration file states onto the app. Every setting the file states replaces what the app has, including settings changed in the control panel; settings the file does not state are left alone.
Auth: Required (member, write access, scope: apps:write)
Two settings are never written from a file, however the request is made: instance_size and instances move the app between plans and charge the payment method. They come back in skipped, and change through Resize App instead.
{
"success": true,
"data": {
"applied": ["source_dir", "type", "build.command", "run_command", "envs"],
"skipped": [{ "field": "instance_size", "reason": "billable" }]
}
}The settings are pushed to the app but not rebuilt: an environment variable or a port takes effect on the next deploy, and a build command applies to the next build.
Returns 400 when the file cannot be read, is no longer valid, no longer defines the app's service, or references a secret or variable the project does not have — nothing is written in any of those cases.
Set Configuration File Mode
PATCH /v1/projects/:projectId/apps/:appId/config{ "mode": "APPLY" }Auth: Required (member, write access, scope: apps:write)
| Mode | What the file does |
|---|---|
WATCH | Compared against the app and reported. Nothing is written. |
APPLY | Also written on every push to the deployed branch, before the build — including over settings changed in the control panel. Needs auto-deploy on: the file is applied when a push is seen, and pushes are only seen for an app that deploys on push. |
IGNORED | Not read at all. |
Returns 400 for an app that was not created from a configuration file, and for APPLY on an app with auto-deploy off. Turning auto-deploy off later leaves the mode set but stops the file from being applied until it is back on.
List Plans
GET /v1/projects/:projectId/apps/:appId/sizesReturns the sizes this app can be moved to, cheapest first, along with the size it runs on today. resizable is false — and sizes is empty — for a static site, or for an app whose size cannot be changed yet.
Auth: Required (member, scope: apps:read)
Response:
{
"currentSize": "shared-xs",
"resizable": true,
"sizes": [
{
"slug": "shared-xs",
"name": "Shared XS",
"tierSlug": "",
"cpuType": "shared",
"cpus": 0.5,
"memoryMb": 512,
"priceMonthly": 5
},
{
"slug": "shared-s",
"name": "Shared S",
"tierSlug": "",
"cpuType": "shared",
"cpus": 1,
"memoryMb": 1024,
"priceMonthly": 9
}
]
}Preview Resize
GET /v1/projects/:projectId/apps/:appId/resize-preview?size=<slug>Returns the prorated cost of moving the app to a different instance size, including the subtotal, estimated tax, any applied credit, the total due today, and the new monthly price. A downgrade returns a credit rather than a charge.
Returns 400 if the app is a static site (static sites do not run on a sized instance) or is already on the requested size, 404 if the size is not one returned by List Plans, and 409 if the app's size cannot be changed yet.
Auth: Required (member, scope: apps:read)
Resize App
PATCH /v1/projects/:projectId/apps/:appId/sizeMoves the app to a different instance size. The prorated amount shown by Preview Resize is charged to the project's payment method off-session, and the app is rebuilt and redeployed on the new size — it is briefly unavailable while that happens. A downgrade is credited to the account balance and applied to future invoices rather than refunded to the card.
Static sites cannot be resized, and the size must be one returned by List Plans. An app that is still deploying, or whose size cannot be changed yet, returns 409. If the card is declined the request returns 402 and the size is unchanged — see Billing → Deploy Payment Errors.
Auth: Required (member, write access, scope: apps:write)
Request Body:
{
"size": "shared-s"
}Delete App
DELETE /v1/projects/:projectId/apps/:appIdTears down the deployed app, deprovisions routing, and deletes the record.
Auth: Required (member, write access, scope: apps:write)
Redeploy
POST /v1/projects/:projectId/apps/:appId/deployStarts a fresh build of a git-source app from the tip of its tracked branch — the retry path after a failed build, or the way to roll out configuration that does not come from a push. Only git-source apps can be rebuilt this way — image-source apps use Deploy Image.
Builds run on shared capacity, so the route is bounded. It answers 409 while
a deployment is already in progress for the app (DEPLOYMENT_IN_PROGRESS) or
while the project already has its limit of builds running
(TOO_MANY_BUILDS_IN_PROGRESS) — retry once one settles. Rebuilding the same
app again in quick succession answers 429 with DEPLOY_COOLDOWN, and the
route is rate-limited per project on top of that. A 429 carries a
Retry-After header when the wait is known; back off and retry rather than
polling. Deploys started by a push to the tracked branch are not affected.
Auth: Required (member, write access, scope: apps:write)
Deploy Image
POST /v1/projects/:projectId/apps/:appId/deploymentsDeploys an image-source app from the repository it already uses. This is the call for CI: push the image, then deploy the tag or digest you just pushed.
| Field | |
|---|---|
tag | Switch the app to this tag, e.g. sha-3f9c2a1. |
digest | Pin the app to this manifest, sha256:…. A pinned app is not moved by pushes under the TAG policy. |
Pass one or neither. With neither, the tag the app uses now is looked up again and deployed, which is how to pick up a push without a deploy policy following it.
{
"tag": "sha-3f9c2a1"
}Answers 202 with the deployment. The rollout continues after the response;
poll Get Deployment until phase is ACTIVE or ERROR.
{
"success": true,
"data": {
"id": "cmf2x…",
"phase": "DEPLOYING",
"cause": "api deploy",
"imageRef": "registry.cosmoner.com/<org-id>/api:sha-3f9c2a1",
"imageDigest": "sha256:…",
"error": null,
"startedAt": "2026-09-16T10:00:00.000Z",
"finishedAt": null
}
}Deploying the same image the app is already running returns that deployment again rather than starting another, so re-running a CI job is safe.
Only apps whose image is in one of the project's registries can be deployed this
way; anything else returns 400, as does a git-source app. Returns 404 when
no image in the repository matches the tag or digest, and 409 for an app that
is stopped or has not finished its first deploy. The route is rate-limited per
project.
Auth: Required (member, write access, scope: apps:write)
Get Deployment
GET /v1/projects/:projectId/apps/:appId/deployments/:deploymentIdReturns one deployment in the shape Deploy Image answers with.
phase is DEPLOYING while the new version is rolling out, ACTIVE once it is
serving, and ERROR when it failed, with the reason in error. A deployment
overtaken by a newer one before it finished reads SUPERSEDED.
Auth: Required (member, scope: apps:read)
Logs
GET /v1/projects/:projectId/apps/:appId/logs?type=BUILD|RUNReturns build or runtime logs.
Auth: Required (member, scope: apps:read)
Metrics
GET /v1/projects/:projectId/apps/:appId/metrics?range=1h|6h|24hReturns CPU%, memory%, and restart count time series.
Auth: Required (member, scope: apps:read)
Console (Exec)
GET /v1/projects/:projectId/apps/:appId/execReturns a WebSocket URL for an interactive console session.
A console can run arbitrary commands inside the app, so it is gated as a write even though the request itself only reads: viewers are refused, and a read-only API key is not sufficient.
Auth: Required (member, non-viewer, scope: apps:write)
Activity
GET /v1/projects/:projectId/apps/:appId/activityReturns the last 10 deployments.
Auth: Required (member, scope: apps:read)
Networking
Get Network Config
GET /v1/projects/:projectId/apps/:appId/networkReturns the app's advanced networking settings.
Auth: Required (member, scope: apps:read)
Update Advanced Settings
PATCH /v1/projects/:projectId/apps/:appId/network/advancedUpdates edge caching, email obfuscation, and threat protection settings. They are applied at the edge, so the app is not redeployed.
Auth: Required (member, scope: apps:write)
Domain / Routing
Change App Domain
PATCH /v1/projects/:projectId/apps/:appId/domainChanges the app's public URL (Cosmoner subdomain or custom domain). Tears down old routing and provisions new.
Cosmoner subdomains are generated from the app's name and cannot be chosen. Send
domainType: "cosmoner" on its own to move an app back onto the Cosmoner address
it already has, or add regenerateSubdomain: true to have a new address
generated. Custom domains still take baseDomain and targetFqdn.
Auth: Required (member, write access, scope: apps:write)
Setup Routing
POST /v1/projects/:projectId/apps/:appId/setup-routingManually triggers routing provisioning for a PENDING app. Returns CNAME + DCV instructions for external domains.
Auth: Required (member, write access, scope: apps:write)
Verify Hostname
POST /v1/projects/:projectId/apps/:appId/verify-hostnameRe-checks DCV/SSL for an external custom hostname.
Auth: Required (member, write access, scope: apps:write)