API overview
The Koo API is how the console, the SDKs, and your own tools talk to Koo. Everything you can do in the console — create apps and services, trigger deployments, read logs — goes through the same API.
This page covers the conventions that apply to every endpoint. For the endpoint-by-endpoint reference, see the API reference.
Base URL & format
All requests go to:
https://api.koo.io- Requests and responses are JSON, with camelCase field names.
- There is no version prefix. The API is stable and evolves additively: new fields and endpoints appear, existing ones don't change meaning or disappear.
- Every response includes a request ID you can use when contacting support.
Authentication
Authenticate every request with a bearer token:
Authorization: Bearer <token>There are two kinds of token:
- Session tokens — issued when you sign in; these power the console. They identify you as a user and carry your role in each account.
- API tokens — opaque tokens prefixed
kc_…, made for machines: CI pipelines, scripts, and the SDKs. See API tokens.
Public endpoints — discovery, public profiles, and public app pages — need no authentication.
Errors
Every error response uses the same envelope:
{ "error": { "code": "not_found", "message": "Service not found.", "details": null, "requestId": "..." }}code is a stable, machine-readable enum — branch on it in your code. message is for humans and may change without notice; never parse it. details carries structured context for some codes, such as the failing fields on a validation error.
Common codes:
| Code | Meaning | What to do |
|---|---|---|
unauthenticated | Missing, expired, or invalid token | Re-authenticate and retry |
forbidden | Your role in the account doesn't allow this | Ask the account's owner or an admin, or switch accounts |
not_found | The resource doesn't exist or isn't visible to you | Check the ID and the account you're acting in |
validation_failed | The request body didn't pass validation | Fix the fields listed in details |
quota_exceeded | A plan ceiling was reached | Upgrade the plan or remove something |
rate_limited | Too many requests in a short window | Back off and retry |
Include the requestId from the error envelope when you contact support — it lets us find the exact request in our logs.
Pagination
List endpoints are cursor-based. Pass limit and cursor as query parameters; the response wraps results in data and a page object:
{ "data": [], "page": { "nextCursor": "...", "hasMore": true }}To fetch the next page, pass page.nextCursor as the cursor parameter. Cursors are opaque — store and replay them, but don't construct or decode them. When hasMore is false, you've reached the end.
Idempotency
POST endpoints that create resources honor an Idempotency-Key header. Send a unique key (a UUID works well) with each create; if the request is retried — after a timeout, say — the same key returns the original result instead of creating a duplicate.
Rate limits
Two distinct limits can reject a request, and they call for different fixes:
rate_limitedmeans you're sending requests too fast. This is a rolling window — back off, retry with exponential delay, and the requests will succeed.quota_exceededmeans you've hit a plan ceiling — for example, the per-tier cap on builds or deploys per hour. Retrying won't help; upgrade the plan on the billing page or reduce usage.
Explore the reference
The API reference documents every endpoint, generated from the same definitions the API exposes. Good starting points:
- Apps — create apps, manage their services
- Deployments — deploy, list versions, roll back
- Observability — logs, metrics, and service status
- The Environment object — the full shape of an environment and its services