Appearance
Error codes
Two catalogs: CLI error codes (in the --json envelope's error.code, with a process exit code) and web API error codes (in the HTTP response's error.code, with an HTTP status). The CLI maps HTTP failures onto its own codes — the original server message is preserved in error.message.
CLI error codes
Every CLI failure is a single JSON envelope on stdout (in --json mode):
json
{ "ok": false, "operation": "deploy",
"error": { "code": "DEPLOY_FAILED", "message": "Deployment 8a41... failed: build exited 1",
"hint": "Run `apco logs` to inspect the build output." } }| Code | Exit | Meaning | Typical cause |
|---|---|---|---|
CLOUD_NOT_LOGGED_IN | 2 | No credentials available | Never ran apco login and APCO_TOKEN unset |
CLOUD_UNAUTHORIZED | 1 | Server answered an authentication/authorization 401/403 | Key invalid/expired or a non-business access denial |
CLOUD_FORBIDDEN_SCOPE | 1 | Server answered FORBIDDEN_SCOPE | Token lacks the required scope/project grant or has an invalid ci:deploy shape |
CLOUD_NOT_FOUND | 1 | Server answered 404, or no project resolvable | Wrong id/slug; auto-slug project with no .apco/state.json — pass --project |
CLOUD_UNAVAILABLE | 1 | Network failure or 5xx | Control plane down/unreachable; retry later |
CLOUD_REQUEST_FAILED | 1 | Business error (4xx) | Quota exceeded, slug conflict, rollback unavailable, declined creation, poll timeout — message carries the server detail |
CLOUD_LOGIN_DENIED | 1 | Device flow denied | User rejected the browser approval |
CLOUD_LOGIN_EXPIRED | 1 | Device flow timed out | Verification code expired before approval |
CLOUD_LOGIN_FAILED | 1 | Device flow failed | Unexpected auth-server response |
CONFIG_INVALID | 2 | ~/.config/apco/config.json is corrupt | Malformed JSON / invalid cloud key — re-run apco login |
TOKEN_REF_INVALID | 2 | Stored token ref is malformed | Not one of literal: / env:NAME / file:PATH / dotenv:PATH:VAR |
TOKEN_MISSING | 2 | Token ref points at nothing | Unset env var, unreadable/empty token file |
MANIFEST_NOT_FOUND | 2 | No apco.yml at the manifest path | Run apco init |
MANIFEST_INVALID | 2 | Manifest fails validation | Bad YAML, schema issues (listed in hint), database/release/jobs configuration on static, existing manifest on init without --force |
ENV_FILE_NOT_FOUND | 2 | env push could not find the dotenv file | Missing .env — pass --file |
DATABASE_NOT_READY | 1 | Selected branch missing/not ready, or db migrate/restore failed | Check apco db status --channel <channel> --json; create it with apco db branch <channel> |
DEV_SYNC_UNAVAILABLE | 1 | Plan lacks dev sync | apco dev --sync pre-check against GET /account/usage |
DEPLOY_TRIGGER_FAILED | 4 | Upload rejected before build | Packed archive over the size limit |
DEPLOY_FAILED | 4 | Build/start failed | Build error, crash on boot — inspect apco logs --json |
DEPLOY_TIMEOUT | 4 | No terminal status within 15 minutes | Stuck build/worker — check apco status |
VERIFY_FAILED | 5 | Health path never returned 200 in time | Wrong health.path/port, app not listening on PORT, DNS |
CLI_INVALID_USAGE | 2 | Invalid flag/argument combination | Invalid database channel, backup list --all-channels --channel ..., --follow with --json, missing migrations dir |
DESTRUCTIVE_CONFIRMATION_REQUIRED | 6 | Destructive command without --yes in --json/non-TTY mode | projects delete, db disable — re-run with --yes if intended |
UNEXPECTED | 1 | Unclassified failure | Message carries the cause |
Exit-code summary
| Exit | Codes |
|---|---|
| 0 | success |
| 1 | CLOUD_* (except CLOUD_NOT_LOGGED_IN), DATABASE_NOT_READY, DEV_SYNC_UNAVAILABLE, UNEXPECTED |
| 2 | CLOUD_NOT_LOGGED_IN, CLI_INVALID_USAGE, CONFIG_*, TOKEN_*, MANIFEST_*, ENV_* |
| 4 | DEPLOY_TRIGGER_FAILED, DEPLOY_FAILED, DEPLOY_TIMEOUT |
| 5 | VERIFY_FAILED |
| 6 | DESTRUCTIVE_CONFIRMATION_REQUIRED |
Exit codes 3 and 7 are retired (Coolify era) and never emitted.
How the CLI maps HTTP errors
| Server response | CLI code |
|---|---|
| Network error / unreachable | CLOUD_UNAVAILABLE |
Server code QUOTA_EXCEEDED, ROLLBACK_UNAVAILABLE, SLUG_CONFLICT, BACKUP_IN_PROGRESS, BACKUP_NOT_READY, or any other 409 | CLOUD_REQUEST_FAILED (with a targeted hint; the server code stays in the message) |
Server code DATABASE_NOT_READY / DATABASE_PROVISIONING | DATABASE_NOT_READY |
Server code SUSPENDED / EMAIL_NOT_VERIFIED (403) | CLOUD_REQUEST_FAILED with a targeted remediation hint |
Server code FORBIDDEN_SCOPE (403) | CLOUD_FORBIDDEN_SCOPE |
| 401 / 403 (any other) | CLOUD_UNAUTHORIZED |
| 404 | CLOUD_NOT_FOUND |
| 5xx | CLOUD_UNAVAILABLE |
| Any other non-2xx | CLOUD_REQUEST_FAILED |
Web API error codes
All /api/v1 errors use the shape { "ok": false, "error": { "code", "message" } }.
Auth & access
| Code | Status | Meaning / typical cause |
|---|---|---|
UNAUTHORIZED | 401 | No/invalid credential, or the user is banned |
FORBIDDEN_SCOPE | 403 | Scoped API key lacks the required scope, or a scoped key hit a full-access-only route |
FORBIDDEN | 403 | Admin role required (/admin/*) |
SESSION_REQUIRED | 403 | POST /tokens called with an API key — token creation is session-only |
EMAIL_NOT_VERIFIED | 403 | Deploys require a verified email |
SUSPENDED | 403 | Project is suspended — deploy/rename/start/database ops disabled; an admin must resume |
Validation & quota
| Code | Status | Meaning / typical cause |
|---|---|---|
VALIDATION | 422 | Invalid body/schema/slug, unsupported or conflicting database channel, channel assertion mismatches backup metadata, dev deploy on static |
BAD_REQUEST | 400 | Invalid body on POST /account/set-password |
INVALID_INPUT | 400 | Invalid scheduled-job filters, cursor, attempt number, or enable body |
INVALID_BODY | 400 | Bad body on the SQL console, admin ban/role/plan, invites, waitlist |
INVALID_EMAIL | 422 | Waitlist/invite email failed validation |
QUOTA_EXCEEDED | 403 | Plan limit reached: maxProjects, maxScheduledJobs; maxDatabases distinct database-enabled projects (three branches bundled); project-wide maxBackups; or fixed 20/project saved queries. Also 422 for env over maxEnvVars |
RATE_LIMITED | 429 | Per-user deploy cap (maxDeploysPerHour) reached, or a screenshot refresh requested during the per-deployment cooldown |
UPLOAD_TOO_LARGE | 413 | Source upload over the plan's maxUploadMb |
OCI_REFERENCE_INVALID | 422 | OCI reference/registry-host syntax or public-network policy failed before queueing |
PLAN_FEATURE_UNAVAILABLE | 403 | Plan lacks the feature: managed databases, dev sync, previews, custom domains, scheduled-job execution, or OCI images (maxImageMb: 0) |
Resources & conflicts
| Code | Status | Meaning / typical cause |
|---|---|---|
NOT_FOUND | 404 | Resource not found/not owned; selected database branch or backup ID absent |
SLUG_CONFLICT | 409 | Project or plan slug already taken |
CONFLICT | 409 | Unique violation on project create (name/slug race), or a saved query with that name already exists for the project |
ROLLBACK_UNAVAILABLE | 400 / 409 | 400: source is a dev-channel deployment. 409: source not superseded, or its image/static artifact is gone |
STOP_UNAVAILABLE | 409 | No running production deployment to stop |
START_UNAVAILABLE | 409 | No stopped production deployment to start |
SCOPE_UNAVAILABLE | 409 | Scheduled-job definition no longer points at the exact active Production/Dev/Preview/PR route scope |
ARTIFACT_UNAVAILABLE | 409 | Scheduled-job definition's pinned deployment/image is no longer runnable |
SCREENSHOT_UNAVAILABLE | 409 | Screenshot refresh requested for a deployment that isn't a live production/preview one |
DATABASE_PROVISIONING | 409 | Database still provisioning (drop/credentials/query/rotate while provisioning) |
DATABASE_NOT_READY | 409 | Database exists but is not ready (e.g. dropping, error), or credentials missing |
DATABASE_ENGINE_CONFLICT | 409 | Requested engine differs from the project's immutable first-branch engine |
DATABASE_ENGINE_UNAVAILABLE | 503 | MariaDB was requested while the operator rollout gate/service is disabled |
DRY_RUN_UNAVAILABLE | 400 | Rollback dry run was requested for MariaDB; use a disposable branch or an explicit transaction only for statements whose semantics you control |
QUERY_ERROR | 400 | SQL console / row / count routes: the tenant engine rejected the SQL and its safe driver message is returned (also a 10s query timeout on Studio operations) |
TABLE_NOT_FOUND | 404 | Database Studio row/count routes: :table is not a live base table in public |
COLUMN_NOT_FOUND | 400 | Database Studio row routes: an unknown column in orderBy, values, changes, or pk |
NO_PRIMARY_KEY | 400 | Row PATCH/DELETE on a table with no primary key — there is nothing to match pk against |
BACKUP_IN_PROGRESS | 409 | A backup is already pending/running for the project, or deleting one that isn't complete/failed yet |
BACKUP_NOT_READY | 409 | Downloading a backup whose status isn't complete |
RUNTIME_LOGS_UNAVAILABLE | 409 / 502 | 409: no running container. 502: worker log service unreachable/errored |
PASSWORD_ALREADY_SET | 409 | set-password on an account that already has a credential — use change-password |
Tokens, GitHub, account
| Code | Status | Meaning / typical cause |
|---|---|---|
TOKEN_CREATE_FAILED | 400 / 500 | Auth layer rejected the token creation (e.g. expiry out of range) / unexpected failure |
SET_PASSWORD_FAILED | 400 / 500 | Auth layer rejected the password / unexpected failure |
GITHUB_NOT_CONNECTED | 400 | No GitHub account linked |
GITHUB_TOKEN_INVALID | 400 | GitHub token invalid or missing scopes — re-login with GitHub |
Admin
| Code | Status | Meaning / typical cause |
|---|---|---|
SELF_ACTION | 400 | Admin tried to delete/ban/demote their own account |
UNKNOWN_PLAN | 422 | POST /admin/users/:id/plan with a slug not in the plans table |
ALREADY_SUSPENDED | 409 | Suspending an already-suspended project |
NOT_SUSPENDED | 409 | Resuming a project that is not suspended |
PLAN_IS_DEFAULT | 409 | Deleting the default plan |
PLAN_IN_USE | 409 | Deleting a plan that still has users |
CLUSTER_SCHEDULER_DISABLED | 409 | Node mutation attempted while the fail-closed scheduler rollout flag is off |
NODE_ACTION_UNAVAILABLE | 409 | Node state does not allow the requested lifecycle transition or required confirmation is absent |
Portable deployment failures may surface safe machine codes such as NODE_CAPACITY_UNAVAILABLE, NODE_UNAVAILABLE, NODE_FENCE_REQUIRED, STALE_NODE_OBSERVATION, NODE_TASK_FAILED, ROUTE_DIVERGED, ARTIFACT_NOT_FOUND, ARTIFACT_DIGEST_MISMATCH, and ARTIFACT_REGISTRY_UNAVAILABLE. Re-read deployment/node state before retrying; never replay an indeterminate claimed scheduled job.
Internal
| Code | Status | Meaning / typical cause |
|---|---|---|
INTERNAL | 500 | Unexpected server failure (slug generation exhausted, credential decryption failed, query/schema execution failure) |
OCI deployment state
OCI pull/start failures are recorded as safe deployment.errorCode values and surface through status/detail while the CLI reports the terminal operation as DEPLOY_FAILED. Raw registry response bodies and credentials are never copied into the code/message/log.
| Code | Meaning / action |
|---|---|
OCI_PLAN_UNAVAILABLE | The current plan has OCI disabled; change plan configuration before retrying. |
OCI_REGISTRY_BLOCKED | Registry DNS/host resolved outside the public allow policy. |
OCI_CREDENTIAL_INVALID | Exact-host credential could not be loaded/decrypted; rotate it. |
OCI_AUTH_FAILED | Registry rejected or required authentication; correct the exact-host pull credential. |
OCI_PULL_FAILED | Registry pull failed with a safe generic result; inspect bounded build logs. |
OCI_PULL_TIMEOUT | Pull exceeded 15 minutes. |
OCI_IMAGE_TOO_LARGE | Transfer or inspected image size exceeded maxImageMb. |
OCI_DIGEST_MISMATCH | Pull/recovery did not produce the required immutable repository digest. |
OCI_PLATFORM_UNSUPPORTED | Image is not Linux/current worker architecture. |
OCI_ENTRYPOINT_MISSING | Image provides neither a usable entrypoint nor command. |
OCI_INSPECT_FAILED | Image metadata/size could not be verified. |
OCI_DEPLOY_FAILED | Safe fallback for candidate creation/activation failure. |
OCI_HEALTH_CHECK_FAILED | Candidate did not pass its configured health path before timeout. |