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"
}
| Field | Type | Description |
|---|---|---|
statusCode | number | The HTTP status code, repeated in the body. |
error | string | A short, machine-friendly label for the status (e.g. Bad Request, Forbidden). |
message | string | A 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"
}
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 (on429only).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}`);
}
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.