CaaS
Create an app
Section titled “Create an app”POST /v1/caas/{id}/apps · scope caas:write · reversible · idempotent
Creates the app and configures its source, but does not deploy it: it stays idle until you call POST /v1/caas/{id}/apps/{app_id}/deploy. Separating the two is what lets you create the app, load its variables, and only then deploy — the reverse order would start the application without its configuration.
curl https://api.truo.cloud/v1/caas/svc_10432/apps \ -X POST \ -H "Authorization: Bearer $TRUO_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"api"}'truo caas app create svc_10432await truo.caas.apps.create("svc_10432", {"name":"api"});truo_caas({ "action": "app_create", "id": "svc_10432"})operationId: caas.apps.create
Delete an app
Section titled “Delete an app”DELETE /v1/caas/{id}/apps/{app_id} · scope caas:write · destructive — cannot be undone · idempotent
Destructive. Deletes the app, its variables, and its domains. The service’s database data is untouched: it lives separately.
curl https://api.truo.cloud/v1/caas/svc_10432/apps/app_9f2c1b7e \ -X DELETE \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas app delete svc_10432 app_9f2c1b7eawait truo.caas.apps.delete("svc_10432", "app_9f2c1b7e");truo_caas({ "action": "app_delete", "id": "svc_10432", "appId": "app_9f2c1b7e"})operationId: caas.apps.delete
Deploy an app
Section titled “Deploy an app”POST /v1/caas/{id}/apps/{app_id}/deploy · scope caas:deploy · reversible · asynchronous · idempotent
Returns 202 as soon as the deployment starts. The operation resolves by looking up that deployment in the app’s history, the only place the backend reports its outcome. Wait on it with GET /v1/operations/{id}; failure details are in GET /v1/caas/{id}/apps/{app_id}/logs.
It lives in its own scope (caas:deploy) because deploying executes whatever code is in the configured source — which is different from editing the app’s configuration.
curl https://api.truo.cloud/v1/caas/svc_10432/apps/app_9f2c1b7e/deploy \ -X POST \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas deploy svc_10432 app_9f2c1b7eawait truo.caas.apps.deploy("svc_10432", "app_9f2c1b7e");truo_caas({ "action": "deploy", "id": "svc_10432", "appId": "app_9f2c1b7e"})operationId: caas.apps.deploy
Get an app
Section titled “Get an app”GET /v1/caas/{id}/apps/{app_id} · scope caas:read
Returns only the declared fields. The backend responds with the deployment engine’s full internal object —including plaintext environment variables—; none of that leaves through here. For variable names, use GET /v1/caas/{id}/apps/{app_id}/env.
curl https://api.truo.cloud/v1/caas/svc_10432/apps/app_9f2c1b7e \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas app get svc_10432 app_9f2c1b7eawait truo.caas.apps.get("svc_10432", "app_9f2c1b7e");truo_caas({ "action": "app_get", "id": "svc_10432", "appId": "app_9f2c1b7e"})operationId: caas.apps.get
List the service’s apps
Section titled “List the service’s apps”GET /v1/caas/{id}/apps · scope caas:read
source comes back null: the backend does not include it in the listing.
curl https://api.truo.cloud/v1/caas/svc_10432/apps \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas app list svc_10432await truo.caas.apps.list("svc_10432");truo_caas({ "action": "app_list", "id": "svc_10432"})operationId: caas.apps.list
Get an app’s logs
Section titled “Get an app’s logs”GET /v1/caas/{id}/apps/{app_id}/logs · scope caas:read
A snapshot, not a stream. Returns whatever the backend has at call time, and there is no way to ask for “what came after”: the backend accepts a cursor but never emits the next one, so this endpoint publishes none. To follow an application live, call again.
curl https://api.truo.cloud/v1/caas/svc_10432/apps/app_9f2c1b7e/logs \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas logs svc_10432 app_9f2c1b7eawait truo.caas.apps.logs("svc_10432", "app_9f2c1b7e");truo_caas({ "action": "logs", "id": "svc_10432", "appId": "app_9f2c1b7e"})operationId: caas.apps.logs
Restart an app
Section titled “Restart an app”POST /v1/caas/{id}/apps/{app_id}/restart · scope caas:write · reversible · idempotent
Restarts the process without rebuilding the image: it picks up the current environment variables but does not pull new code. That is what deploy is for.
curl https://api.truo.cloud/v1/caas/svc_10432/apps/app_9f2c1b7e/restart \ -X POST \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas app restart svc_10432 app_9f2c1b7eawait truo.caas.apps.restart("svc_10432", "app_9f2c1b7e");truo_caas({ "action": "app_restart", "id": "svc_10432", "appId": "app_9f2c1b7e"})operationId: caas.apps.restart
Create a database
Section titled “Create a database”POST /v1/caas/{id}/databases · scope caas:write · reversible · idempotent
The platform generates the password and it is not returned here or by any other /v1 endpoint: there is no way to recover it through this API. Connect from an app in the same service, where the connection string is already available.
Deleting a database is not in this version: the backend does not implement it yet, and publishing an endpoint that always fails would be publishing roadmap.
curl https://api.truo.cloud/v1/caas/svc_10432/databases \ -X POST \ -H "Authorization: Bearer $TRUO_TOKEN" \ -H "Content-Type: application/json" \ -d '{"engine":"postgres","name":"main"}'truo caas database create svc_10432await truo.caas.databases.create("svc_10432", {"engine":"postgres","name":"main"});truo_caas({ "action": "database_create", "id": "svc_10432"})operationId: caas.databases.create
List the service’s databases
Section titled “List the service’s databases”GET /v1/caas/{id}/databases · scope caas:read
They belong to the service, not to an app: several apps in the same service can use the same database. Credentials are not returned by any endpoint of this API.
curl https://api.truo.cloud/v1/caas/svc_10432/databases \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas database list svc_10432await truo.caas.databases.list("svc_10432");truo_caas({ "action": "database_list", "id": "svc_10432"})operationId: caas.databases.list
List an app’s deployment history
Section titled “List an app’s deployment history”GET /v1/caas/{id}/apps/{app_id}/deployments · scope caas:read
Newest first, as the backend returns it.
curl https://api.truo.cloud/v1/caas/svc_10432/apps/app_9f2c1b7e/deployments \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas deployment list svc_10432 app_9f2c1b7eawait truo.caas.deployments.list("svc_10432", "app_9f2c1b7e");truo_caas({ "action": "deployment_list", "id": "svc_10432", "appId": "app_9f2c1b7e"})operationId: caas.deployments.list
Add a domain to an app
Section titled “Add a domain to an app”POST /v1/caas/{id}/apps/{app_id}/domains · scope caas:write · reversible · idempotent
The host’s DNS must point at the service’s IP before you call: certificate issuance is validated over HTTP.
Two more things to know:
- It is not atomic. Creation registers the domain and then rebuilds the ingress routing; if the second step fails, the call returns an error with the domain already created. Retrying is safe and is the right move — creation is idempotent per host.
- The certificate is issued afterwards, asynchronously, with no state or id to query. That is why
certificate_typecomes backnullhere. The only real check is an HTTPS request to the host.
curl https://api.truo.cloud/v1/caas/svc_10432/apps/app_9f2c1b7e/domains \ -X POST \ -H "Authorization: Bearer $TRUO_TOKEN" \ -H "Content-Type: application/json" \ -d '{"host":"app.example.com"}'truo caas domain add svc_10432 app_9f2c1b7eawait truo.caas.domains.create("svc_10432", "app_9f2c1b7e", {"host":"app.example.com"});truo_caas({ "action": "domain_create", "id": "svc_10432", "appId": "app_9f2c1b7e"})operationId: caas.domains.create
Remove a domain from an app
Section titled “Remove a domain from an app”DELETE /v1/caas/{id}/apps/{app_id}/domains/{host} · scope caas:write · destructive — cannot be undone · idempotent
Deleting a host that is not on the app is not an error: the ingress routing is rebuilt either way, which is what makes retrying safe.
curl https://api.truo.cloud/v1/caas/svc_10432/apps/app_9f2c1b7e/domains/app.example.com \ -X DELETE \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas domain remove svc_10432 app_9f2c1b7e app.example.comawait truo.caas.domains.delete("svc_10432", "app_9f2c1b7e", "app.example.com");truo_caas({ "action": "domain_delete", "id": "svc_10432", "appId": "app_9f2c1b7e", "host": "app.example.com"})operationId: caas.domains.delete
List an app’s domains
Section titled “List an app’s domains”GET /v1/caas/{id}/apps/{app_id}/domains · scope caas:read
curl https://api.truo.cloud/v1/caas/svc_10432/apps/app_9f2c1b7e/domains \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas domain list svc_10432 app_9f2c1b7eawait truo.caas.domains.list("svc_10432", "app_9f2c1b7e");truo_caas({ "action": "domain_list", "id": "svc_10432", "appId": "app_9f2c1b7e"})operationId: caas.domains.list
List environment variable names
Section titled “List environment variable names”GET /v1/caas/{id}/apps/{app_id}/env · scope caas:read
Returns names, never values. There is no version of this endpoint that returns them: once written, a value is read only by the application. The backend masks by applying a regex to the key name, which lets anything not named like a secret (DATABASE_URL, SENTRY_DSN) through in plaintext; that is not a classification policy and it is not published.
curl https://api.truo.cloud/v1/caas/svc_10432/apps/app_9f2c1b7e/env \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas env list svc_10432 app_9f2c1b7eawait truo.caas.env.list("svc_10432", "app_9f2c1b7e");truo_caas({ "action": "env_list", "id": "svc_10432", "appId": "app_9f2c1b7e"})operationId: caas.env.list
Replace the environment variables
Section titled “Replace the environment variables”PUT /v1/caas/{id}/apps/{app_id}/env · scope caas:write · destructive — cannot be undone · idempotent
Replaces the entire set: anything missing from vars is deleted. Not a limitation — it is the semantics of the backend, which writes the whole block at once.
Since GET /env returns no values, the set has to come from your side — your secrets manager or your configuration repository. That is the natural shape for declarative infrastructure, and it also removes the panel’s failure mode, where saving without rewriting the secrets erased them.
Changes take effect on the next deploy or restart.
curl https://api.truo.cloud/v1/caas/svc_10432/apps/app_9f2c1b7e/env \ -X PUT \ -H "Authorization: Bearer $TRUO_TOKEN" \ -H "Content-Type: application/json" \ -d '{"vars":[]}'truo caas env set svc_10432 app_9f2c1b7eawait truo.caas.env.replace("svc_10432", "app_9f2c1b7e", {"vars":[]});truo_caas({ "action": "env_replace", "id": "svc_10432", "appId": "app_9f2c1b7e"})operationId: caas.env.replace
Get a CaaS service with its live state
Section titled “Get a CaaS service with its live state”GET /v1/caas/{id} · scope caas:read
Queries the control plane. If it does not respond, provisioning_state and machine come back null instead of failing: a control plane hiccup should not stop you from reading the rest of the resource or its capabilities.
curl https://api.truo.cloud/v1/caas/svc_10432 \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas get svc_10432await truo.caas.instances.get("svc_10432");truo_caas({ "action": "get", "id": "svc_10432"})operationId: caas.instances.get
List CaaS services
Section titled “List CaaS services”GET /v1/caas · scope caas:read
Served from the database, without querying the control plane: provisioning_state and machine come back null. Fetching them would cost two calls per page item.
A page can come back with fewer items than limit even when more exist: every control plane product shares the same provisioning module, so the family filter can only be applied after reading the page. has_more remains the correct signal for whether anything is left to fetch.
curl https://api.truo.cloud/v1/caas \ -H "Authorization: Bearer $TRUO_TOKEN"truo caas listawait truo.caas.instances.list();truo_caas({ "action": "list"})operationId: caas.instances.list