Closedloop.ai
API Reference

Responses and Errors

The JSON envelope every endpoint returns, request body limits, and the status codes to handle

The response envelope

Every endpoint answers with the same JSON envelope. A successful response sets success: true and carries the result under data:

{
  "success": true,
  "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "slug": "PRD-42" }
}

An error response sets success: false and carries a human-readable error. Three more fields are optional and appear only when the endpoint has them to give:

{
  "success": false,
  "error": "...",
  "code": "project_team_required",
  "details": { "teams": [{ "id": "...", "name": "Platform" }] }
}
FieldTypeMeaning
successbooleantrue on success, false on error.
dataanyThe result. Present only on success. Its shape is documented on each endpoint page.
errorstringHuman-readable message. Present only on error.
codestringOptional machine-readable error code. Branch on this, not on error.
detailsobjectOptional structured payload that goes with code, carrying what you need to correct the request.
timestampstringOptional time the error was produced.

Error codes are endpoint-specific; each endpoint page lists the ones it documents. Treat a code you do not recognize as a generic failure of that status.

Request bodies

Send request bodies as JSON. Request bodies are capped at 4 MiB by default; a body over the cap is refused with 413 and nothing is applied. A few endpoints set a different cap, which their pages state.

A body that is not valid JSON, or that fails validation, is refused with 400. For a validation failure, error names each failing field as path: message, joined with commas:

{
  "success": false,
  "error": "title: <validation message>, type: <validation message>"
}

Query parameters are validated the same way. Many endpoints reject a parameter combination they cannot honor (for example, offset without limit) rather than silently ignoring part of it.

Status codes

StatusWhen
200The request succeeded. A /v2 batch endpoint also returns 200 when some items failed; see Batch operations.
202Accepted for processing, on the few endpoints that finish work asynchronously.
400Malformed JSON, a validation failure, or a request the endpoint cannot satisfy as written.
401No credential, or the credential is not valid. See Authentication.
403The credential is valid but not allowed to do this: a missing API-key scope, or the caller lacks permission on the resource.
404The resource does not exist in your organization. The message reads <Entity> not found.
409The request conflicts with the resource's current state, such as a duplicate name or a stale precondition.
410The endpoint has been retired permanently. Do not retry.
413The request body, or an asset in it, is over its size limit.
429The request hit a rate limit (see below).
500The server failed. The message is deliberately generic.
503A dependency the request needs could not be reached, so the server could not decide the request.

A resource in another organization answers 404, not 403: lookups are scoped to the caller's organization, so the API does not say whether it exists elsewhere.

Rate limits

The REST API has no global per-key request rate limit. Individual endpoints can still refuse with 429. Document attachment uploads, for example, are rate-limited; their 429 carries a Retry-After header and details.retryAfterSeconds:

{
  "success": false,
  "error": "Attachment upload rate limit exceeded",
  "code": "attachment_upload_rate_limited",
  "details": { "retryAfterSeconds": 30 }
}

When a response carries Retry-After, wait at least that many seconds before retrying.

On this page