2026-08-31
Coolify REST API traps: what actually bites when you build your own tooling
Notes from building my own tooling against the Coolify v4 REST API. None of these are documented anywhere obvious; all of them cost real time to find.
I run Coolify v4 on a single server and drive most of it through a small script against the REST API rather than the UI: listing apps, deploying, reading logs, and managing env vars. Building that script surfaced a handful of traps in the API that are not obvious from the docs. These are the ones that actually bit me.
Services and applications are different objects
A one-click service (something like Umami) is created differently to a regular application and does not behave the same way afterwards. You create it with:
POST /services
{ "type": "...", "name": "...", "project_uuid": "...", "environment_uuid": "...", "server_uuid": "...", "instant_deploy": true }But domains is rejected at create time ("This field is not allowed"), and the same field is rejected on a PATCH /services/<uuid> afterwards too. A service's underlying sub-application is also not addressable at /applications/<uuid>: that returns 404 "Application not found", so the usual PATCH-the-application recipe you would use to fix an app's domain does not reach a service at all.
In practice: create and deploy a service through the API, then set its domain through the UI. I have not found an API surface for it. Trying to force it through the database or a workaround is not worth the time; budget the one UI click.
Setting a service env var needs the env's own uuid in the body
Env vars on a service do work through the API, but only if you include the environment variable's own uuid in the PATCH body:
PATCH /services/<uuid>/envs
{ "uuid": "<env-uuid>", "key": "...", "value": "..." }The same call without uuid still returns 201, which looks like success, but silently changes nothing. Always verify with a read-back rather than trusting the status code. /envs/<env_uuid> as a path is 404, so you cannot address the variable directly that way either; it has to go through the collection endpoint on the service with the uuid inside the body.
env-set is update-only: creating a new key needs POST, not PATCH
PATCH /applications/<uuid>/envs updates an existing key. Send it for a key that does not exist yet and you get a 404 "Environment variable not found", not a create. To add a genuinely new key, POST to the same endpoint instead:
POST /api/v1/applications/<uuid>/envs
{ "key": "...", "value": "...", "is_preview": false }This matters most right after creating a new app, when every env key is new by definition: a naive script that always PATCHes will 404 on the first run. My own tooling now tries PATCH first and falls back to POST on a 404, which makes the same command idempotent whether the key already exists or not.
Env endpoints echo the decrypted secret back in the response
Both PATCH and POST to an application's envs endpoint return the plaintext secret in the response body, in both a value and a real_value field, even when you are calling it with a write-scoped token you trust. If you ever hand-roll a raw curl against this endpoint instead of going through wrapped tooling, for example to copy one app's decrypted secret into another app, do not cat or log the response body. Check only the HTTP status code, or the secret ends up sitting in your shell history or logs.
/deploy changed from GET to POST
After upgrading Coolify from 4.0.0 to 4.3.14, the /deploy endpoint that used to accept GET now requires POST. A GET against it returns a 405 with "This endpoint has changed to a POST request". Any script written against an older Coolify version needs this fixed after an upgrade, or every deploy call silently 405s.
Versions this applies to
- Coolify v4 REST API, observed on 4.0.0 through 4.3.14
- The GET-to-POST /deploy change specifically landed somewhere in the 4.3.x line
Have Emsden Studio fix it for you, from A$149