Using the API
Errors
Every status the API answers, and the fixed sentences it uses.
Updated
A failed request answers with a status and a JSON body holding one sentence, written to be shown to a person as it is.
{ "error": "That does not exist." }Statuses
| Status | Body | When |
|---|---|---|
401 | { "error": "Sign in to continue." } | No customer session, or a banned account |
404 | { "error": "That does not exist." } | The record does not exist, or it belongs to someone else or to another of your organizations |
404 | { "error": "That organization does not exist.", "reason": "organization" } | X-Cloud-Organization names no organization of yours (Authentication) |
409 | { "error": "<one sentence>" }, with "reason" added for a billing refusal | The request conflicts with the current state |
422 | { "error": "<one sentence>", "field": "<name>" } | An input is invalid; field names it, or is null for the request as a whole |
429 | { "error": "<one sentence>" } | Too many requests of one kind in the last minute, or in the last hour for new organizations (below) |
502 | { "error": "The engine did not answer. Try again in a minute." } | The platform failed or timed out |
502 | { "error": "GitHub did not answer. Try again in a minute." } | GitHub failed while listing repositories or branches |
503 | { "error": "GitHub cannot be connected right now." } | Connecting a GitHub account on a platform with no GitHub App configured |
Someone else's record is a 404
Asking for another customer's project, service or anything else answers 404, exactly as a record that does not exist. A 403 would confirm that it exists.
A record of another of your own organizations answers the same way while the request works in a different one. Name its organization in X-Cloud-Organization to reach it. A header that names no organization of yours has a 404 of its own, with reason: "organization", so a client can tell a stale choice of organization from a missing record.
Validation errors name the field
A 422 carries field, the input at fault, written the way the request names it, so a form can put the sentence under that input:
{ "error": "Name the image to run, such as nginx:alpine.", "field": "source.image" }Nested inputs use a dot, such as source.github_branch, and a catalogue answer uses answers.<NAME>. A fault of the whole request has "field": null, as "You can have up to 10 organizations. Contact support for more." does.
Most sentences are written for the field. Storage and notification endpoints answer the model's own message for the field instead, so it starts with the field's name, such as "Mount path is invalid" or "Config Use a Slack incoming webhook URL."
Conflicts you will meet
Most 409 sentences are fixed, so a client can match them. A billing refusal's is not: its organization's name, its month or its amount is the organization's own. Decide what happened by reason, never by the sentence:
| Sentence | When |
|---|---|
| Create an organization first. | A route that works in an organization, for an account that has none yet |
| Your workspace is still being prepared. | The workspace of the organization the request works in is not ready yet |
| This project still has services. Remove them first. | Deleting a project that is not empty |
| A deployment of this service is already running. | Deploying or deleting the service, or adding, renaming, checking or removing one of its domains, while a deployment of it is in progress |
| Wait for the service deployment to finish. | Changing storage, backups or the pre-deploy dump while a deployment is in progress |
| This service has unfinished backup work. | Deploying, stopping, starting, changing or backing up a service whose backup has not settled |
| This service has an unconfirmed volume attachment. | The same requests, while a volume attachment awaits confirmation |
| This deployment has already finished. | Cancelling a finished deployment |
| This service is suspended. | Adding, renaming, checking or removing a domain of a service whose account is suspended; contact support |
| Remove this service's domains first. | Deleting a service that still has domains |
| Remove this service's volumes first. | Deleting a service that still has attached volumes |
| This service still has backup schedules or unfinished backups. | Deleting a service with a schedule you created or a backup in progress |
| A service still builds from this connection. Remove it first. | Removing a git provider a service uses |
| This belongs to a different engine than the one running now. | A record made on a platform engine that has since been replaced |
| Add a card in billing to run apps past the free tier. | Paid work in an organization that pays by card and has no card on file; reason is no_card |
| The charge for November 2026 did not go through. Pay it in billing to run apps past the free tier. | Paid work in an organization whose card charge for that month has failed; reason is unpaid |
| The invoice for November 2026 is past its due date. Pay it in billing to run apps past the free tier. | Paid work in an organization whose invoice for that month is overdue; reason is unpaid |
| This would take this month to $27.40 against your $20 limit. Contact support to raise the limit. | Paid work that would take the organization's month past its monthly limit, with its own amounts; reason is over_limit |
| Paid services in Kopi Tiga are stopped for billing because the charge for November 2026 did not go through. Pay it in billing to start them again. | Paid work in an organization whose paid services are stopped for billing, and starting a service stopped for billing. The sentence names each cause and what clears it; reason is the first cause's, unpaid for a month, no_card for no card on file and over_limit for the limit (Stopped for billing) |
| Paid services in Kopi Tiga are starting again. Try again in a minute. | The same work in the minute between the last cause clearing and Cloud starting the services again; no reason |
| This account is suspended by Cahaves. Contact support to have the suspension lifted. | Cahaves has suspended the account, which holds all its organizations; creating an organization is refused the same way |
| This account is starting again. Try again in a minute. | A change after Cahaves lifts a suspension or a ban, before the account state job returns the account to active; blocked services can keep retrying after the account is active |
Other conflicts answer a sentence written for that endpoint.
Rate limits
| Request | Limit | Sentence |
|---|---|---|
| The sign-in callback | 20 a minute per address | Too many attempts. |
| Create an organization | 10 an hour per account, refused attempts included | Too many new organizations at once. Try again in an hour. |
A 502 leaves Cloud's record unchanged
A 502 means the platform did not complete the request, and Cloud's own record is left as it was: nothing is saved that the engine did not take, and a service creation that fails part-way removes what the engine had made. Retrying after a short wait is safe.
One case is narrower. Update a service sends each changed setting to the engine in its own call, so a failure after the first can leave the earlier settings applied on the engine while Cloud's record keeps the old values. Sending the same request again brings the two back together.
The platform's own error detail is kept in Cloud's logs and never returned.