Docs Reference
API
API conventions
Every Kumo.io API response is JSON in one of three shapes, a single object, a page of objects, or an error, and every list pages the same way.
Requests
- Base URL and version. Paths start with
/v1, and almost all of them are scoped to your account:/v1/accounts/<account-id>/…. For the base URL and theAuthorizationheader, see How do I authenticate to the API? - JSON. Send request bodies as JSON with
Content-Type: application/json. A body that isn't valid JSON is refused with400 BAD_REQUEST("malformed or unsupported request body"). - Size. A request body can be up to 1 MiB. Workspace imports can be up to 32 MiB. A larger body is refused with
413 PAYLOAD_TOO_LARGE. - IDs. Objects are identified by opaque IDs. Use them as given; a malformed ID is treated as not found.
A single object
A request that returns one object wraps it in data:
{
"data": { "id": "…", "name": "Production" }
}
Some responses add a meta object alongside data with extra information about the response. meta is left out when there is nothing to say.
A page of objects
A request that returns a list returns one page at a time:
{
"data": [ { "id": "…" }, { "id": "…" } ],
"page": { "limit": 100, "nextCursor": "c1:…" },
"meta": { "totalMatching": 240 }
}
| Field | Meaning |
|---|---|
data | The objects on this page. |
page.limit | The page size actually applied. |
page.nextCursor | Pass this back to get the next page. null means this is the last page. |
meta.totalMatching | On lists that report it, how many objects match in total. |
Paging through a list
Two query parameters control paging:
| Parameter | Meaning |
|---|---|
limit | Page size. Default 100, maximum 500. A larger value is reduced to 500 without an error, and page.limit shows the value used. A value of 0, a negative number or a non-number is refused. |
cursor | The page.nextCursor from the previous page. Leave it out for the first page. |
GET /v1/accounts/<account-id>/workspaces?limit=200
GET /v1/accounts/<account-id>/workspaces?limit=200&cursor=<page.nextCursor>
Keep requesting until page.nextCursor is null. Treat cursors as opaque: don't build or change them. An invalid limit or cursor returns 400 BAD_REQUEST_PAGINATION.
A few list endpoints use a different default or maximum page size; the response's page.limit always tells you what was applied.
Errors
Any response that isn't a success has the same shape:
{
"error": {
"code": "NOT_FOUND",
"message": "workspace not found",
"status": 404,
"requestId": "req-1a"
}
}
| Field | Meaning |
|---|---|
code | A stable, machine-readable code. Branch on this, not on the message. |
message | A human-readable explanation. It can change; don't parse it. |
status | The HTTP status, repeated in the body. |
requestId | Identifies the request. Include it when you contact support. |
details | Sometimes present, with extra fields for that code, for example featureKey on FEATURE_NOT_ENTITLED. |
Ignore fields you don't recognise: new ones may be added.
Error codes
| Status | Code | When |
|---|---|---|
400 | BAD_REQUEST | The request is malformed or fails validation. |
400 | BAD_REQUEST_PAGINATION | limit or cursor is invalid. |
400 | BAD_REQUEST_AS_OF | A point-in-time (asOf) value is invalid. |
400 | INVARIANT_VIOLATION | The request would break a rule, for example an invalid combination of values. |
401 | UNAUTHENTICATED | No valid token. See API authentication. |
403 | AUTHORIZATION_DENIED | Your roles don't allow this, or the path names another account. |
403 | FEATURE_NOT_ENTITLED | Your plan doesn't include this feature. |
403 | LICENSE_DENIED | The self-hosted installation isn't licensed. |
404 | NOT_FOUND | The object or path doesn't exist in your account. |
409 | CONFLICT | The request conflicts with the object's current state. |
409 | SYNC_RUN_IN_PROGRESS | The workspace already has a sync running. |
409 | WORKSPACE_ARCHIVED | The workspace is archived; restore it first. |
409 | JOB_STATE_CONFLICT | The job can't make that change from its current state. |
409 | REPORT_STATE_CONFLICT | The report run can't make that change from its current state. |
413 | PAYLOAD_TOO_LARGE | The request body is too large. |
429 | RATE_LIMITED | Too many requests, too many failed logins, or a sync started sooner than your plan allows. |
429 | QUOTA_EXCEEDED | You've reached a plan limit. See plans and limits. |
501 | NOT_IMPLEMENTED | This operation isn't available on this deployment. |
503 | BILLING_UNAVAILABLE | Billing is temporarily unavailable. |
500 | INTERNAL_ERROR | Something went wrong on Kumo.io's side. The message is always "Internal server error". |
Rate limits and retries
- The API allows 1,000 requests per 60 seconds per IP address. Over that, it returns
429 RATE_LIMITEDwith aRetry-Afterheader giving the seconds to wait. - A sync started too soon also returns
429 RATE_LIMITED, without aRetry-Afterheader; the message says how long to wait ("retry in Ns"). 429 QUOTA_EXCEEDEDdoesn't clear by waiting, except for monthly limits at the start of the next month. Free capacity or change plan instead.- Retry
500and503with a backoff. Don't retry4xxerrors other than429without changing the request.
Long-running requests
Requests that start work in the background return 202 Accepted, usually with a Location header that points at the status of that work. See How do I track a long-running request?
Reference
Every endpoint is in the API reference. Open a route there to see its query parameters, its request body and its success response, field by field. The shapes are generated from the server's own types, so they change when the API changes. Files and event streams are listed by media type, and errors always use the error shape above.