Cosmoner Docs
API Reference

Apps

Manage app deployments, networking, domains, and monitoring.

Manage app deployments on Cosmoner's managed app hosting.

List Apps

GET /v1/projects/:projectId/apps

Returns all apps in the project, ordered by creation date (newest first).

Auth: Required (member)


Get App

GET /v1/projects/:projectId/apps/:appId

Returns 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/draft

Saves 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/apps

Deploys 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/:appId

Updates 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:

ValueA 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.
NEWESTDeploys whatever tag was pushed and switches the app to that tag.
MANUALDoes 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/config

Compares 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:

StatusMeaning
changed_in_fileThe file changed; the app still holds what the file last applied.
changed_in_dashboardThe app changed since the file was applied; the file has not moved.
conflictBoth moved, to different values.
differsThey 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/apply

Writes 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)

ModeWhat the file does
WATCHCompared against the app and reported. Nothing is written.
APPLYAlso 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.
IGNOREDNot 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/sizes

Returns 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/size

Moves 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/:appId

Tears 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/deploy

Starts 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/deployments

Deploys 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
tagSwitch the app to this tag, e.g. sha-3f9c2a1.
digestPin 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/:deploymentId

Returns 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|RUN

Returns build or runtime logs.

Auth: Required (member, scope: apps:read)


Metrics

GET /v1/projects/:projectId/apps/:appId/metrics?range=1h|6h|24h

Returns CPU%, memory%, and restart count time series.

Auth: Required (member, scope: apps:read)


Console (Exec)

GET /v1/projects/:projectId/apps/:appId/exec

Returns 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/activity

Returns the last 10 deployments.

Auth: Required (member, scope: apps:read)


Networking

Get Network Config

GET /v1/projects/:projectId/apps/:appId/network

Returns the app's advanced networking settings.

Auth: Required (member, scope: apps:read)

Update Advanced Settings

PATCH /v1/projects/:projectId/apps/:appId/network/advanced

Updates 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/domain

Changes 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-routing

Manually 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-hostname

Re-checks DCV/SSL for an external custom hostname.

Auth: Required (member, write access, scope: apps:write)

On this page