Conventions
Rules that hold across every /api/v1 operation, so you can write one client rather than one per resource.
Versioning
The version lives in the path. /api/v1 is the only version, and operations are added to it rather than replacing it. A breaking change to an existing operation would arrive as a new version path, not as a silent reshape — so a working client keeps working.
Errors
Every failure uses one envelope, so a client can branch on error.code without special-casing routes:
{
"error": {
"code": "not_found",
"message": "App not found.",
"details": {}
}
}
Status codes carry consistent meaning:
| Status | Meaning |
|---|---|
400 |
The request failed validation. message names the first problem. |
401 |
The key is missing, malformed, expired, or revoked. |
403 |
The key is valid but its organization or role cannot perform this operation. |
404 |
The resource does not exist within the key's organization. |
429 |
A rate limit was exceeded. |
502 |
An upstream provider failed. Usually retryable. |
500 |
An unexpected failure. |
A resource belonging to another organization returns 404, not 403. The API does not confirm that a resource exists outside your scope.
Request identifiers
Every response carries a request identifier header. Log it — quoting it is the fastest way to have a specific request investigated.
Rate limits
Limits are per route and, for authenticated requests, per key — so one noisy integration cannot exhaust another's budget. Read operations are generous; provisioning and credential operations are tight and fail closed.
When a route is rate-limited, successful responses and 429 responses include:
RateLimit-Limit— the route's configured request quota for the windowRateLimit-Remaining— requests left in the current window after this responseRateLimit-Reset— whole seconds until capacity may free in the sliding window
A 429 also carries a Retry-After header in seconds. Honour it. Retry with exponential backoff and jitter rather than a fixed interval; several clients retrying on the same schedule reproduce the burst that caused the limit.
File upload prepare (POST /files/uploads) and complete (POST /files/uploads/complete) each allow 120 requests per minute per key. The counters are independent. Each HTTP request counts as one toward that route's limit, regardless of how many files the body lists.
Sending feedback (POST /feedback) allows 10 requests per minute per key, and fails closed.
Retries and idempotency
Retry 429, 502, and 500. Do not blindly retry 400, 401, 403, or 404 — those describe the request, and repeating it produces the same answer.
Read operations are safe to retry. Write operations are not uniformly idempotent, and the ones that are not say so in their description. Provisioning operations are the ones to watch: creating a database responds only once provisioning has settled, and retrying a request that already succeeded provisions a second database rather than returning the first. Where an operation accepts an idempotency key, supply one.
If a write times out, prefer reading the collection to discover whether it took effect over resending it.
Long-running operations
Provisioning runs inside the request and can take tens of seconds. These operations respond 201 with the persisted record whether provisioning ended ready or failed — a 201 means the record exists, not that the resource is usable. Branch on the returned status (creating, ready, failed) rather than on the HTTP status, and read failureState when it is failed.
Set your client's timeout accordingly; a 10-second default will abort work that was going to succeed.
Pagination
Collection endpoints that paginate document their parameters on their own reference page and return a cursor for the next page. Follow the returned cursor rather than constructing offsets. Endpoints without pagination parameters return the organization's full set, which is bounded by what a workspace can hold.
Engines, not vendors
The API names engines and capabilities: databases for PostgreSQL, sqlite-databases for SQLite, redis-databases for Redis, buckets for object storage. Which provider runs a given resource is a fact about that resource, not part of the interface, so it is reported in the provider field. This is enforced, so paths and operation names stay stable if a provider changes underneath.
Server-to-server only
The API sends no cross-origin resource sharing headers and does not answer preflight requests. Browser JavaScript cannot call it, and that is deliberate: an organization-scoped key grants full access to a workspace, so it must never be shipped to client-side code where any visitor can read it.
Call the API from your server, and let your own frontend talk to your server.