{"openapi":"3.1.0","info":{"title":"Canner API","version":"1.0.0","description":"REST API for the Canner deployment platform. Authenticate with a `cnr_` API token (Dashboard → Account) sent as `Authorization: Bearer cnr_…`. Base URL: `https://api.canner.ca`; the stable, versioned surface is `/v1`. Errors share one envelope: `{ \"error\": \"<code>\", \"message\": \"…\", \"request_id\": \"req_…\" }`. Every response carries an `X-Request-Id` header; quote it when contacting support.","contact":{"name":"Canner support","email":"support@canner.ca","url":"https://canner.ca"}},"components":{"securitySchemes":{"bearerToken":{"type":"http","scheme":"bearer","bearerFormat":"cnr_token","description":"API token (`cnr_…`). Create one at Dashboard → Account, then send `Authorization: Bearer cnr_…`."}},"schemas":{}},"paths":{"/v1/projects":{"post":{"summary":"Create a project","tags":["Projects"],"description":"Creates a project. Requires a verified email and available capacity on your plan. Returns 402 when the plan project cap is reached, 409 when the slug is taken or reserved.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$"},"repo_url":{"type":"string","format":"uri"},"repo_branch":{"type":"string","minLength":1,"maxLength":200}},"required":["name","slug"],"additionalProperties":false}}}},"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"repo_url":{"type":"string","nullable":true},"repo_branch":{"type":"string"},"is_connected":{"type":"boolean","description":"Whether a GitHub repository is linked."},"node_version":{"type":"string"},"install_command":{"type":"string"},"build_command":{"type":"string"},"root_directory":{"type":"string"},"memory_limit_mb":{"type":"number"},"port":{"type":"number","nullable":true},"status":{"type":"string","description":"Lifecycle status: created, building, live, inactive, …"},"always_warm":{"type":"boolean"},"is_cold":{"type":"boolean"},"caching_enabled":{"type":"boolean"},"has_purge_token":{"type":"boolean"},"last_request_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","repo_url","repo_branch","is_connected","node_version","install_command","build_command","root_directory","memory_limit_mb","port","status","always_warm","is_cold","caching_enabled","has_purge_token","last_request_at","created_at","updated_at"],"additionalProperties":false,"description":"A Canner project."}},"required":["project"],"additionalProperties":false}}}},"402":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"403":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"409":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}},"get":{"summary":"List projects","tags":["Projects"],"description":"Lists your projects (plus any owned by accounts whose team you belong to), newest first. Keyset-paginated: pass `limit` (1–100, default 50) and the previous response's `next_cursor` as `cursor`. `next_cursor` is null on the last page.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100},"in":"query","name":"limit","required":false,"description":"Max items to return (1–100). Defaults to 50."},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Opaque pagination token from a previous response's `next_cursor`. Omit for the first page."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"repo_url":{"type":"string","nullable":true},"repo_branch":{"type":"string"},"is_connected":{"type":"boolean","description":"Whether a GitHub repository is linked."},"node_version":{"type":"string"},"install_command":{"type":"string"},"build_command":{"type":"string"},"root_directory":{"type":"string"},"memory_limit_mb":{"type":"number"},"port":{"type":"number","nullable":true},"status":{"type":"string","description":"Lifecycle status: created, building, live, inactive, …"},"always_warm":{"type":"boolean"},"is_cold":{"type":"boolean"},"caching_enabled":{"type":"boolean"},"has_purge_token":{"type":"boolean"},"last_request_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","repo_url","repo_branch","is_connected","node_version","install_command","build_command","root_directory","memory_limit_mb","port","status","always_warm","is_cold","caching_enabled","has_purge_token","last_request_at","created_at","updated_at"],"additionalProperties":false,"description":"A Canner project."}},"next_cursor":{"type":"string","nullable":true}},"required":["projects","next_cursor"],"additionalProperties":false}}}}}}},"/v1/projects/{slug}":{"get":{"summary":"Get a project","tags":["Projects"],"parameters":[{"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"repo_url":{"type":"string","nullable":true},"repo_branch":{"type":"string"},"is_connected":{"type":"boolean","description":"Whether a GitHub repository is linked."},"node_version":{"type":"string"},"install_command":{"type":"string"},"build_command":{"type":"string"},"root_directory":{"type":"string"},"memory_limit_mb":{"type":"number"},"port":{"type":"number","nullable":true},"status":{"type":"string","description":"Lifecycle status: created, building, live, inactive, …"},"always_warm":{"type":"boolean"},"is_cold":{"type":"boolean"},"caching_enabled":{"type":"boolean"},"has_purge_token":{"type":"boolean"},"last_request_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","repo_url","repo_branch","is_connected","node_version","install_command","build_command","root_directory","memory_limit_mb","port","status","always_warm","is_cold","caching_enabled","has_purge_token","last_request_at","created_at","updated_at"],"additionalProperties":false,"description":"A Canner project."}},"required":["project"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}},"patch":{"summary":"Update a project","tags":["Projects"],"description":"Updates mutable fields. memory_limit_mb is clamped to the plan ceiling; always_warm requires a paid plan. Slug, status, and port are platform-managed and not editable here.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"repo_url":{"type":"string","format":"uri","nullable":true},"repo_branch":{"type":"string","minLength":1,"maxLength":200},"node_version":{"type":"string","minLength":1,"maxLength":20},"install_command":{"type":"string","minLength":1,"maxLength":500},"build_command":{"type":"string","minLength":1,"maxLength":500},"root_directory":{"type":"string","minLength":1,"maxLength":500},"memory_limit_mb":{"type":"integer","minimum":64,"maximum":1024},"always_warm":{"type":"boolean"},"caching_enabled":{"type":"boolean"}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"repo_url":{"type":"string","nullable":true},"repo_branch":{"type":"string"},"is_connected":{"type":"boolean","description":"Whether a GitHub repository is linked."},"node_version":{"type":"string"},"install_command":{"type":"string"},"build_command":{"type":"string"},"root_directory":{"type":"string"},"memory_limit_mb":{"type":"number"},"port":{"type":"number","nullable":true},"status":{"type":"string","description":"Lifecycle status: created, building, live, inactive, …"},"always_warm":{"type":"boolean"},"is_cold":{"type":"boolean"},"caching_enabled":{"type":"boolean"},"has_purge_token":{"type":"boolean"},"last_request_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","repo_url","repo_branch","is_connected","node_version","install_command","build_command","root_directory","memory_limit_mb","port","status","always_warm","is_cold","caching_enabled","has_purge_token","last_request_at","created_at","updated_at"],"additionalProperties":false,"description":"A Canner project."}},"required":["project"],"additionalProperties":false}}}},"400":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"402":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}},"delete":{"summary":"Delete a project","tags":["Projects"],"description":"Permanently deletes the project and all of its resources: tenant database, runtime, routing, bundles, and the project row. Returns 204. This cannot be undone.","parameters":[{"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/cache/token":{"post":{"summary":"Generate a cache-purge token","tags":["Projects"],"description":"Generates (or rotates) the per-project token a CMS uses to purge cached pages. The plaintext is returned only here; any prior token is invalidated.","parameters":[{"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$"},"in":"path","name":"slug","required":true}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"webhook_url":{"type":"string"}},"required":["token","webhook_url"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/projects/{slug}/cache/purge-all":{"post":{"summary":"Purge the entire project cache","tags":["Projects"],"parameters":[{"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"purged":{"type":"number"}},"required":["ok","purged"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"502":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/projects/{slug}/cache/purge-tags":{"post":{"summary":"Purge cached pages by tag","tags":["Projects"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string","minLength":1,"maxLength":128},"minItems":1,"maxItems":50,"description":"Surrogate-Key tags to purge."}},"required":["tags"],"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"purged":{"type":"number"}},"required":["ok","purged"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"502":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/projects/{slug}/cache/log":{"get":{"summary":"List recent cache-purge events","tags":["Projects"],"parameters":[{"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/stop":{"post":{"summary":"Stop a project","tags":["Projects"],"description":"Takes the project offline (stops the runtime + routing, sets status=inactive) without deleting it. Idempotent — redeploy to bring it back.","parameters":[{"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"status":{"type":"string"}},"required":["ok","status"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/projects/{slug}/database":{"post":{"summary":"Provision a database","tags":["Databases"],"description":"Provisions an isolated Postgres database for the project and returns its connection details. The connection string is also injected into the app as DATABASE_URL. Starter accounts need a payment method on file (402 otherwise); 409 if one already exists.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"database":{"type":"object","properties":{"db_user":{"type":"string"},"password":{"type":"string","description":"Plaintext database password — treat as a secret."},"connection_string":{"type":"string","description":"postgresql://… DSN (also injected into the app as DATABASE_URL)."}},"required":["db_user","password","connection_string"],"additionalProperties":true,"description":"Tenant Postgres connection details."}},"required":["database"],"additionalProperties":false}}}},"402":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"409":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}},"get":{"summary":"Get database connection info","tags":["Databases"],"description":"Returns the project database connection details (including the plaintext password). 404 if no database is provisioned.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"database":{"type":"object","properties":{"db_user":{"type":"string"},"password":{"type":"string","description":"Plaintext database password — treat as a secret."},"connection_string":{"type":"string","description":"postgresql://… DSN (also injected into the app as DATABASE_URL)."}},"required":["db_user","password","connection_string"],"additionalProperties":true,"description":"Tenant Postgres connection details."}},"required":["database"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}},"delete":{"summary":"Delete the database","tags":["Databases"],"description":"Drops the tenant database and its role. Returns 204. This permanently deletes all data in it.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/deploy":{"post":{"summary":"Trigger a deployment","tags":["Deployments"],"description":"Manually triggers a build + deploy from the linked GitHub repo HEAD. 400 if the project has no GitHub connection.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"201":{"description":"A newly enqueued deployment.","content":{"application/json":{"schema":{"type":"object","properties":{"deployment_id":{"type":"string"},"status":{"type":"string"}},"required":["deployment_id","status"],"additionalProperties":true,"description":"A newly enqueued deployment."}}}},"400":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/projects/{slug}/deployments/{id}/retry":{"post":{"summary":"Retry a deployment","tags":["Deployments"],"description":"Re-enqueues a new build from the same commit as the given (usually failed) deployment.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"201":{"description":"A newly enqueued deployment.","content":{"application/json":{"schema":{"type":"object","properties":{"deployment_id":{"type":"string"},"status":{"type":"string"}},"required":["deployment_id","status"],"additionalProperties":true,"description":"A newly enqueued deployment."}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/projects/{slug}/deployments/{id}/cancel":{"post":{"summary":"Cancel a deployment","tags":["Deployments"],"description":"Aborts an in-flight build. 409 if the deployment already reached a terminal state.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"409":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/projects/{slug}/deployments/{id}/rollback":{"post":{"summary":"Roll back to a deployment","tags":["Deployments"],"description":"Re-promotes a prior PRODUCTION deployment by rebuilding from its commit. 409 for preview/branch deployments.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"201":{"description":"A newly enqueued deployment.","content":{"application/json":{"schema":{"type":"object","properties":{"deployment_id":{"type":"string"},"status":{"type":"string"}},"required":["deployment_id","status"],"additionalProperties":true,"description":"A newly enqueued deployment."}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"409":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/projects/{slug}/previews":{"get":{"summary":"List preview deployments","tags":["Deployments"],"description":"Lists currently-open preview branches and their URLs.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/metrics":{"get":{"summary":"Get request metrics","tags":["Deployments"],"description":"Bucketed request metrics plus cold-start and cache stats over the selected range.","parameters":[{"schema":{"type":"string","enum":["24h","7d","30d"]},"in":"query","name":"range","required":false,"description":"Window + bucket size. Default 24h."},{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/runtime-logs":{"get":{"summary":"Stream runtime logs (SSE)","tags":["Deployments"],"description":"Server-Sent Events stream of the running app stdout/stderr. Not a JSON endpoint.","parameters":[{"schema":{"type":"string"},"in":"query","name":"lines","required":false,"description":"Initial backlog lines (20-2000, default 200)."},{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/deployments":{"get":{"summary":"List deployments","tags":["Deployments"],"description":"Lists deployments newest-first. Keyset-paginated: pass `limit` (1–100, default 50) and the previous response's `next_cursor` as `cursor`. `next_cursor` is null on the last page.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100},"in":"query","name":"limit","required":false,"description":"Max items to return (1–100). Defaults to 50."},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Opaque pagination token from a previous response's `next_cursor`. Omit for the first page."},{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/deployments/{id}":{"get":{"summary":"Get a deployment","tags":["Deployments"],"description":"Full deployment detail, including the build log.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/deployments/{id}/screenshot/{variant}.png":{"get":{"summary":"Get a deployment screenshot (PNG)","tags":["Deployments"],"description":"Captured PNG screenshot (variant: desktop or mobile). 404 until captured. Returns image/png, not JSON.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"variant","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/deployments/{id}/logs":{"get":{"summary":"Stream build logs (SSE)","tags":["Deployments"],"description":"Server-Sent Events stream: history replay, live log lines, and status. Not a JSON endpoint.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/env-vars":{"get":{"summary":"List environment variables","tags":["Environment variables"],"description":"Lists the project's environment variables with decrypted values, ordered by environment then key.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"env_vars":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"value":{"type":"string","description":"Decrypted value (the owner can read their own secrets back)."},"environment":{"type":"string","nullable":true,"description":"\"production\", \"preview\", or null (applies to both)."},"is_sensitive":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","key","value","environment","is_sensitive","created_at","updated_at"],"additionalProperties":false}}},"required":["env_vars"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}},"post":{"summary":"Create environment variable(s)","tags":["Environment variables"],"description":"Creates one variable, or many at once via `{ \"vars\": [...] }`. Duplicates (same key + environment) are skipped, not overwritten. Adding a production variable triggers a redeploy.","requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"},"value":{"type":"string","maxLength":10000},"environment":{"anyOf":[{"type":"string","enum":["production"]},{"type":"string","enum":["preview"]},{"enum":["null"],"nullable":true}],"default":null},"is_sensitive":{"type":"boolean","default":false}},"required":["key","value"],"additionalProperties":false},{"type":"object","properties":{"vars":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"},"value":{"type":"string","maxLength":10000},"environment":{"anyOf":[{"type":"string","enum":["production"]},{"type":"string","enum":["preview"]},{"enum":["null"],"nullable":true}],"default":null},"is_sensitive":{"type":"boolean","default":false}},"required":["key","value"],"additionalProperties":false},"minItems":1,"maxItems":200}},"required":["vars"],"additionalProperties":false}]}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"created":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"environment":{"type":"string","nullable":true}},"required":["id","key","environment"],"additionalProperties":false}},"skipped":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"environment":{"type":"string","nullable":true},"reason":{"type":"string"}},"required":["key","environment","reason"],"additionalProperties":false}}},"required":["created","skipped"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"created":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"environment":{"type":"string","nullable":true}},"required":["id","key","environment"],"additionalProperties":false}},"skipped":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"environment":{"type":"string","nullable":true},"reason":{"type":"string"}},"required":["key","environment","reason"],"additionalProperties":false}}},"required":["created","skipped"],"additionalProperties":false}}}}}}},"/v1/projects/{slug}/env-vars/{id}":{"patch":{"summary":"Update an environment variable","tags":["Environment variables"],"description":"Updates the value (and optionally is_sensitive). Key and environment are immutable — delete and recreate to change those.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string","maxLength":10000},"is_sensitive":{"type":"boolean"}},"required":["value"],"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}},"delete":{"summary":"Delete an environment variable","tags":["Environment variables"],"description":"Deletes a variable. Returns 204. Deleting a production variable triggers a redeploy.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/billing/status":{"get":{"summary":"Get billing status","tags":["Billing"],"description":"Returns the account plan, trial state, saved card summary, and free-domain credit balance. Always succeeds (renders even when Stripe is not configured).","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"stripe_enabled":{"type":"boolean"},"publishable_key":{"type":"string","nullable":true,"description":"Stripe publishable key for Elements, or null when Stripe is off."},"has_payment_method":{"type":"boolean"},"subscription_status":{"type":"string","nullable":true},"card":{"type":"object","properties":{"last4":{"type":"string"},"brand":{"type":"string","nullable":true}},"required":["last4","brand"],"additionalProperties":false,"nullable":true},"plan":{"type":"string","description":"starter | basic | pro | enterprise"},"trial_ends_at":{"type":"string","format":"date-time","nullable":true},"billing_interval":{"type":"string","enum":["monthly","yearly"]},"free_domain_credits":{"type":"number"}},"required":["stripe_enabled","publishable_key","has_payment_method","subscription_status","card","plan","trial_ends_at","billing_interval","free_domain_credits"],"additionalProperties":true}}}}}}},"/v1/billing/setup-intent":{"post":{"summary":"Create a SetupIntent","tags":["Billing"],"description":"Returns a Stripe SetupIntent client_secret so a browser can collect a card via Stripe Elements. 503 when Stripe is not configured.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"client_secret":{"type":"string"},"customer_id":{"type":"string"}},"required":["client_secret","customer_id"],"additionalProperties":false}}}},"500":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"503":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/billing/confirm":{"post":{"summary":"Confirm subscription","tags":["Billing"],"description":"Adopts the card saved via the SetupIntent as default and starts (or switches) the subscription. Defaults to monthly Basic. 503 when Stripe is not configured.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["basic","pro","enterprise"],"description":"Defaults to basic."},"interval":{"type":"string","enum":["monthly","yearly"],"description":"Defaults to monthly."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"plan":{"type":"string","enum":["basic","pro","enterprise"]},"interval":{"type":"string","enum":["monthly","yearly"]}},"required":["ok","plan","interval"],"additionalProperties":false}}}},"500":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"503":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/billing/cancel":{"post":{"summary":"Cancel subscription","tags":["Billing"],"description":"Schedules cancellation at period end. The account is demoted to the Starter plan once Stripe finalizes (via webhook). 503 when Stripe is not configured.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"],"additionalProperties":false}}}},"500":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"503":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/domains":{"get":{"summary":"List all domains","tags":["Domains"],"description":"Lists every custom domain across all projects the caller can access, each annotated with its parent project.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"platform_ip":{"type":"string","description":"Point a custom hostname here with an A record."},"purchase_enabled":{"type":"boolean"},"domains":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"hostname":{"type":"string"},"status":{"type":"string","description":"pending | verified | failed | transfer_requested"},"verified_at":{"type":"string","format":"date-time","nullable":true},"last_check_at":{"type":"string","format":"date-time","nullable":true},"last_check_error":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"account_owned_domain_id":{"type":"string","nullable":true,"description":"Set when Canner registered the domain (vs bring-your-own)."},"expires_at":{"type":"string","format":"date-time","nullable":true},"auto_renew":{"type":"boolean","nullable":true},"transfer_requested_at":{"type":"string","format":"date-time","nullable":true},"project_id":{"type":"string"},"project_slug":{"type":"string"},"project_name":{"type":"string"}},"required":["id","hostname","status","verified_at","last_check_at","last_check_error","created_at","account_owned_domain_id","expires_at","auto_renew","transfer_requested_at"],"additionalProperties":true}}},"required":["platform_ip","purchase_enabled","domains"],"additionalProperties":false}}}}}}},"/v1/domains/{id}":{"get":{"summary":"Get a domain","tags":["Domains"],"description":"Returns a single domain by its id (the id from the domains list), including registrar + registrant metadata. 404 if the caller does not own the parent project.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"platform_ip":{"type":"string"},"domain":{"type":"object","properties":{"id":{"type":"string"},"hostname":{"type":"string"},"status":{"type":"string","description":"pending | verified | failed | transfer_requested"},"verified_at":{"type":"string","format":"date-time","nullable":true},"last_check_at":{"type":"string","format":"date-time","nullable":true},"last_check_error":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"account_owned_domain_id":{"type":"string","nullable":true,"description":"Set when Canner registered the domain (vs bring-your-own)."},"expires_at":{"type":"string","format":"date-time","nullable":true},"auto_renew":{"type":"boolean","nullable":true},"transfer_requested_at":{"type":"string","format":"date-time","nullable":true},"project_id":{"type":"string"},"project_slug":{"type":"string"},"project_name":{"type":"string"}},"required":["id","hostname","status","verified_at","last_check_at","last_check_error","created_at","account_owned_domain_id","expires_at","auto_renew","transfer_requested_at"],"additionalProperties":true}},"required":["platform_ip","domain"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/projects/{slug}/domains":{"get":{"summary":"List a project's domains","tags":["Domains"],"description":"Lists the custom domains attached to one project.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"platform_ip":{"type":"string","description":"Point a custom hostname here with an A record."},"purchase_enabled":{"type":"boolean"},"domains":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"hostname":{"type":"string"},"status":{"type":"string","description":"pending | verified | failed | transfer_requested"},"verified_at":{"type":"string","format":"date-time","nullable":true},"last_check_at":{"type":"string","format":"date-time","nullable":true},"last_check_error":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"account_owned_domain_id":{"type":"string","nullable":true,"description":"Set when Canner registered the domain (vs bring-your-own)."},"expires_at":{"type":"string","format":"date-time","nullable":true},"auto_renew":{"type":"boolean","nullable":true},"transfer_requested_at":{"type":"string","format":"date-time","nullable":true},"project_id":{"type":"string"},"project_slug":{"type":"string"},"project_name":{"type":"string"}},"required":["id","hostname","status","verified_at","last_check_at","last_check_error","created_at","account_owned_domain_id","expires_at","auto_renew","transfer_requested_at"],"additionalProperties":true}}},"required":["platform_ip","purchase_enabled","domains"],"additionalProperties":false}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}},"post":{"summary":"Add a custom domain (BYO)","tags":["Domains"],"description":"Attaches a hostname you already own. It starts in `pending`; point an A record at platform_ip then call verify. Custom domains need a payment method on Starter (402).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"hostname":{"type":"string","minLength":3,"maxLength":253,"pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$"}},"required":["hostname"],"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"object","properties":{"id":{"type":"string"},"hostname":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"required":["id","hostname","status","created_at"],"additionalProperties":true},"platform_ip":{"type":"string"}},"required":["domain","platform_ip"],"additionalProperties":false}}}},"400":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"402":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"409":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/projects/{slug}/domains/search":{"get":{"summary":"Search for a domain to buy","tags":["Domains"],"description":"Typeahead domain-availability search across registrars, with CAD pricing (free-domain credits applied inline). Returns `{ results, credits_available }`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"q","required":false,"description":"Search term — a bare label or a full hostname."},{"schema":{"type":"string"},"in":"query","name":"limit","required":false,"description":"Max results (1-20, default 10)."},{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/domains/purchase":{"post":{"summary":"Buy and attach a domain","tags":["Domains"],"description":"Registers a domain through Canner (acting as reseller) and attaches it to the project: charges the card (or redeems a free-domain credit), registers with the registrar, points DNS at the platform, and provisions TLS. .ca/.quebec require residency acknowledgement.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain_name":{"type":"string"},"registrant":{"type":"object","properties":{"first_name":{"type":"string","minLength":1,"maxLength":80},"last_name":{"type":"string","minLength":1,"maxLength":80},"organization":{"type":"string","maxLength":120,"nullable":true},"email":{"type":"string","format":"email","maxLength":254},"phone_cc":{"type":"string","pattern":"^\\+?\\d{1,3}$"},"phone_num":{"type":"string","pattern":"^[\\d\\-\\s().]{4,30}$"},"address1":{"type":"string","minLength":2,"maxLength":120},"address2":{"type":"string","maxLength":120,"nullable":true},"city":{"type":"string","minLength":1,"maxLength":80},"state":{"type":"string","minLength":1,"maxLength":80},"postal_code":{"type":"string","minLength":2,"maxLength":20},"country":{"type":"string","minLength":2,"maxLength":2,"pattern":"^[A-Z]{2}$"}},"required":["first_name","last_name","email","phone_cc","phone_num","address1","city","state","postal_code","country"],"additionalProperties":false},"residency_acknowledged":{"type":"boolean","default":false},"cira_cpr_category":{"type":"string"},"cira_lang_pref":{"type":"string","enum":["EN","FR"]}},"required":["domain_name","registrant"],"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/domains/{id}/transfer-request":{"post":{"summary":"Request a domain transfer-out","tags":["Domains"],"description":"Requests transfer of a Canner-registered domain to another registrar. Notifies the operator (manual until the registrar transfer API ships) and flags the domain transfer_requested.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"destination":{"type":"string","maxLength":500,"description":"Where you want to transfer the domain."},"notes":{"type":"string","maxLength":2000}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/domains/{id}/verify":{"post":{"summary":"Verify a custom domain","tags":["Domains"],"description":"Checks that the hostname resolves to platform_ip; on success marks it verified and provisions TLS. 400 with resolved_ips/expected_ip if DNS is not pointed yet. Idempotent.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"object","properties":{"id":{"type":"string"},"hostname":{"type":"string"},"status":{"type":"string"}},"required":["id","hostname","status"],"additionalProperties":false}},"required":["domain"],"additionalProperties":false}}}},"400":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}},"/v1/projects/{slug}/domains/{id}":{"delete":{"summary":"Remove a custom domain","tags":["Domains"],"description":"Detaches a hostname from the project and rebuilds the TLS routing. Returns 204. Does not deregister a Canner-purchased domain — use transfer-request for that.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/tokens":{"get":{"summary":"List API tokens","tags":["API tokens"],"description":"Returns metadata for your non-revoked API tokens, newest first. Secrets are never returned after creation.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"tokens":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Token id (use with DELETE to revoke)."},"name":{"type":"string"},"prefix":{"type":"string","description":"First 12 chars (cnr_ + 8 hex) — identifies the token without exposing it."},"last_used_at":{"type":"string","format":"date-time","nullable":true,"description":"When the token last authenticated a request, or null."},"created_at":{"type":"string","format":"date-time"}},"required":["id","name","prefix","last_used_at","created_at"],"additionalProperties":false,"description":"API token metadata. The secret itself is only returned once, at creation."}}},"required":["tokens"],"additionalProperties":false}}}}}},"post":{"summary":"Create an API token","tags":["API tokens"],"description":"Creates a new token and returns its plaintext secret. The secret is shown only in this response — store it immediately.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"A human-readable label, e.g. \"github-actions\"."}},"required":["name"],"additionalProperties":false}}}},"responses":{"201":{"description":"A newly created token, including its one-time plaintext secret.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"token":{"type":"string","description":"The plaintext secret (cnr_…). Shown ONCE — copy it now; it cannot be retrieved later."}},"required":["id","name","prefix","created_at","token"],"additionalProperties":false,"description":"A newly created token, including its one-time plaintext secret."}}}}}}},"/v1/tokens/{id}":{"delete":{"summary":"Revoke an API token","tags":["API tokens"],"description":"Soft-revokes a token. Requests using it afterward return 401. Returns 204 on success, 404 if the token does not exist or is already revoked.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true,"description":"Token id from the list endpoint."}],"responses":{"200":{"description":"Default Response"}}}},"/v1/projects/{slug}/upload":{"post":{"summary":"Upload project source","tags":["Uploads"],"description":"Uploads a .zip or .tar.gz of the project source as multipart/form-data (one file field) and enqueues a build. Size is clamped to the plan upload cap; a per-account rate cap (10/hour) applies. Returns the new deployment id.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"deployment_id":{"type":"string"},"status":{"type":"string"},"archive_kind":{"type":"string","description":"\"zip\" or \"tar\""},"max_upload_mb":{"type":"number"}},"required":["deployment_id","status","archive_kind","max_upload_mb"],"additionalProperties":false}}}},"400":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"404":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"413":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"415":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}},"429":{"description":"Standard error response.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Stable, machine-readable error code, e.g. \"not_found\"."},"message":{"type":"string","description":"Human-readable explanation."},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false},"description":"Field-level validation problems (on 400 invalid_input)."},"request_id":{"type":"string","description":"Quote this when contacting support."}},"required":["error"],"additionalProperties":true,"description":"Standard error response."}}}}}}}},"servers":[{"url":"https://api.canner.ca","description":"Production"}],"security":[{"bearerToken":[]}],"tags":[{"name":"Projects","description":"Create, inspect, update, and tear down projects."},{"name":"Deployments","description":"Trigger, inspect, retry, cancel, and roll back deployments."},{"name":"Environment variables","description":"Per-project build and runtime environment variables."},{"name":"Databases","description":"Per-project managed Postgres databases."},{"name":"Domains","description":"Custom domains and domain registration."},{"name":"Billing","description":"Plan, subscription, and payment status."},{"name":"API tokens","description":"Manage the tokens used to authenticate against this API."},{"name":"Uploads","description":"Deploy by uploading a project archive."}]}