Appearance
Agent contract
The machine contract for agents and automation driving APCO Cloud — via the apco CLI, the MCP server, or raw HTTP. This page is the normative summary; the exhaustive command/payload listing lives in the repository's agents.md and in the reference section.
Constraints
- Use
--jsonfor every command whose output you parse. Stdout is exactly one JSON envelope; human diagnostics and progress go to stderr. - Never print, log, or commit environment variable values, API keys,
.env*files, or~/.config/apco/config.json.env pullwrites values to a file — do not cat that file into logs. - Pass
--yesonly for intentionally destructive operations (projects delete,db disable) or for deploy automation that must not prompt. Without--yes, destructive commands in--json/non-TTY mode fail withDESTRUCTIVE_CONFIRMATION_REQUIRED(exit code 6) instead of prompting. - Do not guess project ids. Read them from
apco projects list --jsonorapco status --json; the CLI itself resolves the current directory's project from.apco/state.json. - Do not guess node ids or infer them from project placement output. Admins discover nodes with
apco admin nodes list --json, then use the exact id for lifecycle operations. Project and CI responses intentionally omit node topology. - On failure, read
error.codeanderror.hintbefore retrying. Auth errors (CLOUD_UNAUTHORIZED,CLOUD_NOT_LOGGED_IN) will not fix themselves by retrying. - Do not blindly retry a node action, placement failure, stale report, fence barrier, or indeterminate claimed job. Re-read node/status detail and act on the returned machine code; a retry must preserve the same authoritative ids and generation or restart discovery.
--followstreams and is incompatible with--json(rejected withCLI_INVALID_USAGE); pollapco logs --json/apco status --jsoninstead.
Authentication
Three ways, in order of preference:
- Interactive:
apco loginruns a device flow (browser approval), mints a full-access API key, and stores it in~/.config/apco/config.jsonas a token ref (literal:by default;env:NAME,file:PATH, anddotenv:PATH:VARrefs are also supported in that file). - CI / headless: set
APCO_TOKEN=<api key>. It takes precedence over the stored login. Create scoped keys in the dashboard (Tokens page). - Raw HTTP: send the key as an
x-api-keyheader tohttps://apco.space/api/v1/....
apco whoami --json verifies credentials (source is "env" when APCO_TOKEN is in effect). apco logout revokes the CLI key.
Platform URL authority
Project and deployment url fields returned by the control plane are authoritative. Do not derive app hosts from the API hostname: an installation may keep dashboard/API/auth on one control-plane domain while Production, Dev, and Preview use another content domain. Project payloads may include legacyUrl while an older content wildcard remains a serving alias. The examples in this documentation use APCO's current hosted defaults.
The JSON envelope
Every CLI command in --json mode emits one of two shapes (produced centrally in the CLI's src/output/envelope.ts — no command hand-rolls its output):
json
{ "ok": true, "operation": "<command>", "warnings": [], "...": "command payload" }json
{
"ok": false,
"operation": "deploy",
"error": {
"code": "CLOUD_UNAUTHORIZED",
"message": "Invalid API key.",
"hint": "Run `apco login` again or provide a valid APCO_TOKEN."
}
}error.hint is optional but usually present, and states the recommended next action. MCP tool errors carry the same code + hint text.
Redaction
Success payloads pass through a redaction filter before printing: any field whose key looks secret — token, tokenRef, password, passwd, secret, apiKey, privateKey, value, and the like — is replaced with "[redacted]", recursively. Env-var reads return names only. Assume anything the CLI prints on stdout is safe to store in a transcript; never assume the same about files it writes (env pull).
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic/cloud error |
| 2 | Usage error (bad flags, missing manifest/config/token, not logged in) |
| 4 | Deployment failed or timed out |
| 5 | Verification (health check) failed |
| 6 | Destructive confirmation required (missing --yes) |
Error codes
The most common error.code values and how to react (full table):
| Code | Action |
|---|---|
CLOUD_NOT_LOGGED_IN | Run apco login or set APCO_TOKEN. |
CLOUD_UNAUTHORIZED | Key invalid/expired or authentication was rejected. Don't retry unchanged. |
CLOUD_FORBIDDEN_SCOPE | Token lacks the required scope/project grant or has an invalid CI-token shape. Replace the credential. |
CLOUD_NOT_FOUND | Project/deployment doesn't exist, or no project resolvable from cwd — pass --project. |
CLOUD_UNAVAILABLE | Network failure or 5xx; retry later. |
CLOUD_REQUEST_FAILED | Business error; the message carries the server detail (quota, slug conflict, suspended, ...). |
MANIFEST_NOT_FOUND / MANIFEST_INVALID | Run apco init, or fix apco.yml (the hint lists schema issues). |
DATABASE_NOT_READY | Check the named branch with apco db status --channel <channel> --json; create it with apco db branch <channel> if appropriate. |
DEPLOY_FAILED / DEPLOY_TIMEOUT | Inspect apco logs --json. |
VERIFY_FAILED | Deployed, but the health path never returned 200. |
DESTRUCTIVE_CONFIRMATION_REQUIRED | Re-run with --yes if intended. |
Server-side machine codes surfaced through CLOUD_REQUEST_FAILED (or directly over HTTP) include QUOTA_EXCEEDED, SLUG_CONFLICT, ROLLBACK_UNAVAILABLE, SUSPENDED, FORBIDDEN, FORBIDDEN_SCOPE, PLAN_FEATURE_UNAVAILABLE, RATE_LIMITED, UPLOAD_TOO_LARGE, OCI_REFERENCE_INVALID, EMAIL_NOT_VERIFIED, DATABASE_PROVISIONING, DATABASE_NOT_READY, DATABASE_ENGINE_CONFLICT, DATABASE_ENGINE_UNAVAILABLE, DRY_RUN_UNAVAILABLE, and QUERY_ERROR. OCI terminal failures additionally carry safe deployment errorCode values such as OCI_AUTH_FAILED, OCI_IMAGE_TOO_LARGE, and OCI_PLATFORM_UNSUPPORTED; inspect status/logs and do not retry policy/auth errors unchanged.
Token scopes
API keys carry a scope list (dashboard → Tokens). A key with no scopes is full-access — the apco login default. Grant agents the minimum they need:
| Scope | Grants |
|---|---|
projects:read | List/read projects, deployments, status |
projects:write | Create/rename/delete projects and mutate database branches |
deploy | Create deployments, rollback, code-only promotion |
env:read | Read env vars |
env:write | Write env vars |
logs:read | Build + runtime logs |
jobs:read | Safe scheduled-job definitions, history, and attempt metadata |
jobs:write | Enable/disable, manual run, and cancellation controls |
metrics:read | Metrics, resources, analytics |
lifecycle | Stop/start |
database:query | Run SQL against a selected branch (also satisfied by projects:write) |
database:credentials | Fetch selected-branch credentials (also satisfied by projects:write) |
ci:deploy | Setup-only: deploy one project and poll/read only deployments created by that same key |
ci:deploy must be the token's only scope and must carry exactly one project id. Create it from the project's GitHub Actions setup card; it is not offered in the generic combinable-scope picker.
Admin (/admin/*) and account (/account/*) endpoints require a session or a full-access key — scoped keys are always rejected there. See Token scopes.
HTTP API
Base https://apco.space/api/v1, header x-api-key: <key>. Responses are { "ok": true, ... } or { "error": { "code", "message" } }-style JSON. Async actions (deploy, rename, stop/start, rollback, database provision/drop) return 202 with a jobId/deploymentId to poll. The full route table is in the HTTP API reference.
Database-bound commands and routes use production | preview | dev; omission defaults to Production for compatibility. Treat channel and engine in every JSON result as part of the safety contract. The first branch fixes postgres or mariadb for the project. Postgres alone supports rollback dry runs; never retry MariaDB DRY_RUN_UNAVAILABLE by silently running the write normally. apco dev --db always uses Dev, and promotion reuses code only while resolving Production data.
When apco.yml configures release.command, deployment results include a safe release summary (configured, status/timestamps/exit code/error/skipped reason) but never the command or environment. Poll it together with deployment status and inspect redacted [release] lines through apco logs --json. Promotion runs a fresh Production release; rollback never replays the historical command and reports skipped. A release failure leaves the previous route serving.
When apco.yml configures jobs, use apco jobs ... --json or the scheduled-job MCP tools. Definitions and runs expose only safe schedule/provenance/state; commands, pinned image references, environments, and container identities remain internal. A manual run is asynchronous, so poll its stable logical run id. Job logs require both jobs:read and logs:read; apco jobs logs --follow cannot be combined with JSON.
When app.type is dockerimage, apco deploy and MCP parse the manifest before packing and send JSON with no source archive. Treat returned artifact.digest as the immutable identity; promotion, Production rollback, and start do not resolve the requested tag again. apco registry set reads its secret from a hidden prompt/stdin, and list/API/MCP responses return only exact host/timestamp metadata—never username, password/token, or ciphertext. OCI has Production/Preview but no Dev or GitHub App source-deploy path.
When the cluster scheduler is enabled, deployment status may add safe portableArtifact and placement summaries. They contain immutable artifact state and tenant-safe placement/route convergence only—never a node id, address, task payload, lease verifier, repository credential, or sealed secret. Legacy responses and scheduler-disabled installations omit these optional fields.
Platform admins use apco admin nodes list|show ... --json for read-only discovery and cordon, uncordon, drain, or cancel-drain for ordinary lifecycle control. force-fence requires both --reason and --yes; remove requires --yes. Never pass --yes until the exact node id and current state have been read. Node enrollment material is created only with apco admin nodes bootstrap-token create --file <new-path> --json; the one-time value goes directly to a mode-0600 file and never to stdout. MCP exposes only the read-only cluster_nodes and cluster_node diagnostics, not node mutations.
Ingestion
/llms.txtat the docs root is a compact machine-readable index of the platform (manifest schema, MCP tool list, key concepts) — load it into your agent's context instead of scraping these pages.- The repository ships the same contract as
agents.mdat its root;apco.yml's schema is on the manifest reference page.