Skip to content

Other CI & raw API deploy

Not every CI system can install the comwit CLI — locked-down runners, minimal container images, or platforms where you would rather not add a tool. In those cases you can deploy by calling the public API directly: build your app into a compressed .tar.zst artifact and upload it to the deployments endpoint. This page shows exactly how.

If you can install the CLI, GitHub Actions and the CLI flow are simpler. This page is for everything else.

A deploy uploads one build artifact for an app and (optionally) binds hostnames to it. The artifact is a directory of your built app, packed with tar and compressed with zstd into a single .tar.zst file. The CLI does this packing for you; here you do it yourself.

Before you can deploy you need two ids:

  • projectId — the project that owns the app.
  • appId — the app you are deploying to. Create one first if you have not: POST /v1/projects/{projectId}/apps with body { "name": "web" }.

You also need a user API token (cwt_...). See Authentication for how tokens and scopes work, and API overview for base URL and conventions.

The deployments route is the one exception to the API’s usual JSON-body rule. The request body is the raw artifact, and the deploy options are passed as query parameters, not as JSON fields.

POST /v1/projects/{projectId}/apps/{appId}/deployments
PartValue
AuthorizationBearer cwt_xxx
Content-Typeapplication/octet-stream
Bodythe raw .tar.zst artifact bytes

Query parameters (all optional):

Query paramMaps to CLI flagPurpose
hosts--hostComma-separated hostnames to bind on deploy (e.g. a.example.com,b.example.com)
env_ref--env-refRuntime environment reference
max_concurrent_requests--max-concurrent-requestsRuntime concurrency cap

A successful deploy returns:

{ "app_id": "app-xxx", "build_id": "build-yyy", "hosts": [], "uploaded": true }

Pack your built output directory (here ./dist) into a .tar.zst:

Build the artifact
tar --zstd -cf app.tar.zst -C ./dist .

Send the file as the raw request body. With curl, --data-binary sends the bytes unmodified (do not use -d/--data, which mangles binary input).

Deploy via curl
curl -X POST \
"https://api.cloud.comwit.io/v1/projects/$PROJECT_ID/apps/$APP_ID/deployments" \
-H "Authorization: Bearer $COMWIT_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @app.tar.zst

To bind hostnames and set runtime options at deploy time, add them as query parameters. Remember to URL-encode the comma in hosts if your shell needs it:

Deploy and bind hostnames
curl -X POST \
"https://api.cloud.comwit.io/v1/projects/$PROJECT_ID/apps/$APP_ID/deployments?hosts=a.example.com,b.example.com&max_concurrent_requests=50" \
-H "Authorization: Bearer $COMWIT_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @app.tar.zst

In CI, set PROJECT_ID, APP_ID, and COMWIT_TOKEN (your cwt_ token) as environment variables or secrets, then run the two commands above as a build step.

Uploading a build makes it the active build for the app. To list builds or revert, use the same API your CI can already reach:

GET /v1/projects/{projectId}/apps/{appId}/builds
POST /v1/projects/{projectId}/apps/{appId}/rollbacks

A rollback re-activates a prior build without re-uploading an artifact — handy as a CI “undo” step that does not need the original files.

Failures come back as standard HTTP status codes with a problem-detail JSON body. The common classes you will see from a deploy script:

StatusMeaning
400Caller mistake (bad input)
403Out-of-scope token, a project you are not a member of, or an operator-only route hit with a cwt_ token
404Missing resource (wrong projectId or appId)
409Conflict (e.g. a conflicting DNS record when binding hostnames)
502Internal control-plane dependency failure — including when the runtime control plane is unconfigured

(A deploy never returns 503; that status is reserved for the GET /readyz health probe.)

Upstream detail is logged server-side and sanitized out of public bodies — secret values and internal references never appear in error responses. See Errors & idempotency for the full treatment.

The deployments call is synchronous: the response returns once the upload is accepted. The API is synchronous wherever the upstream control plane is, so there is no separate “operation” resource to poll for a plain deploy today.

Some related flows are already re-entrant — for example, attaching an app hostname is idempotent, so repeating a hostname attach request to recover from a partial failure is safe. See Errors & idempotency for what is retry-safe today.