Skip to main content

Error Handling

The Public API uses a consistent error format across every endpoint and standard HTTP status codes. Build your client to branch on the status code and read the shared error body.

Error schema​

Every error response is a JSON object with the same three fields:

{
"statusCode": 403,
"error": "Forbidden",
"message": "You are not allowed to access boards"
}
FieldTypeDescription
statusCodenumberThe HTTP status code, repeated in the body.
errorstringA short, machine-friendly label for the status (e.g. Bad Request, Forbidden).
messagestringA human-readable description of what went wrong.

The 429 (rate limit) response uses the same shape plus an extra retryAfter field — see below.

Status codes​

400 — Bad Request​

The request was invalid or cannot be served. Common causes:

  • A malformed parameter (for example, an ID that is not a 24-character hex string).
  • A required field is missing.
  • An invalid query parameter value.
{
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid parameter: boardId"
}

Recovery: Fix the request and retry. These will not succeed on retry without changes.

401 — Unauthorized​

Authentication failed. The Authorization header is missing, the token is malformed, or the token has expired.

{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid token"
}

Recovery: Check the Authorization: Bearer <token> header and the token's validity. If the token expired, create a new one. See Authentication.

403 — Forbidden​

The token is valid but lacks permission for this resource — typically a missing scope, or an integration that hasn't been added to the channel or board it is trying to reach.

{
"statusCode": 403,
"error": "Forbidden",
"message": "You are not allowed to access boards"
}

Recovery: Grant the required scope to the token, or add the integration as a participant of the resource. Do not retry blindly — the request will keep failing until access is granted.

404 — Not Found​

The requested resource does not exist or cannot be accessed with this token.

{
"statusCode": 404,
"error": "Not Found",
"message": "Not Found - The requested resource was not found"
}
note

Some endpoints return 400 with a descriptive message (for example, "Channel not found") rather than 404 when a referenced resource is missing. Handle both when validating IDs.

Recovery: Verify the resource ID and that your token's workspace contains it.

429 — Too Many Requests​

You exceeded the rate limit for that endpoint. The response carries a Retry-After header (seconds until the window resets) and a retryAfter field in the body.

{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Too many requests, please try again later",
"retryAfter": 27
}

Response headers on 429 (and on every response):

  • Retry-After — seconds to wait before retrying (on 429 only).
  • X-RateLimit-Limit — the endpoint's per-window cap.
  • X-RateLimit-Remaining — requests left in the current window.
  • X-RateLimit-Reset — when the window resets.

Recovery: Wait for the period in Retry-After, then retry. See Rate Limits for backoff guidance.

500 — Internal Server Error​

Something went wrong on Copera's side.

{
"statusCode": 500,
"error": "Internal Server Error",
"message": "Internal Server Error - Something went wrong on the server"
}

Recovery: Retry with exponential backoff. If it persists, the request itself is fine — the failure is server-side.

Handling errors in code​

Branch on the status code, and retry only the codes that can succeed on retry (429 and 5xx):

async function callApi(url, init, attempt = 0) {
const res = await fetch(url, init);

if (res.ok) return res.json();

// Retry rate limits and server errors with backoff.
if ((res.status === 429 || res.status >= 500) && attempt < 5) {
const retryAfter = Number(res.headers.get("Retry-After"));
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: 2 ** attempt * 1000; // exponential backoff fallback
await new Promise((r) => setTimeout(r, waitMs));
return callApi(url, init, attempt + 1);
}

const error = await res.json();
throw new Error(`${error.statusCode} ${error.error}: ${error.message}`);
}
tip

Do not retry 400, 401, 403, or 404 — they indicate a problem with the request, token, or permissions that a retry will not fix. Surface the message to the caller instead.