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.
What a “deploy” actually is
Section titled “What a “deploy” actually is”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}/appswith 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 deployment endpoint
Section titled “The deployment endpoint”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| Part | Value |
|---|---|
| Authorization | Bearer cwt_xxx |
| Content-Type | application/octet-stream |
| Body | the raw .tar.zst artifact bytes |
Query parameters (all optional):
| Query param | Maps to CLI flag | Purpose |
|---|---|---|
hosts | --host | Comma-separated hostnames to bind on deploy (e.g. a.example.com,b.example.com) |
env_ref | --env-ref | Runtime environment reference |
max_concurrent_requests | --max-concurrent-requests | Runtime concurrency cap |
A successful deploy returns:
{ "app_id": "app-xxx", "build_id": "build-yyy", "hosts": [], "uploaded": true }Build the artifact
Section titled “Build the artifact”Pack your built output directory (here ./dist) into a .tar.zst:
tar --zstd -cf app.tar.zst -C ./dist .Upload with curl
Section titled “Upload with curl”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).
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.zstTo 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:
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.zstIn 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.
After deploying
Section titled “After deploying”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}/buildsPOST /v1/projects/{projectId}/apps/{appId}/rollbacksA rollback re-activates a prior build without re-uploading an artifact — handy as a CI “undo” step that does not need the original files.
Errors
Section titled “Errors”Failures come back as standard HTTP status codes with a problem-detail JSON body. The common classes you will see from a deploy script:
| Status | Meaning |
|---|---|
400 | Caller mistake (bad input) |
403 | Out-of-scope token, a project you are not a member of, or an operator-only route hit with a cwt_ token |
404 | Missing resource (wrong projectId or appId) |
409 | Conflict (e.g. a conflicting DNS record when binding hostnames) |
502 | Internal 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.
Idempotency and retries
Section titled “Idempotency and retries”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.
Where to go next
Section titled “Where to go next”- API overview — base URL, envelopes, and conventions.
- Authentication — creating and scoping
cwt_tokens. - Errors & idempotency — status classes and retry behavior in full.