Error Handling
C.O.S. API v1
Error format
C.O.S. uses two error formats depending on the error type:
Standard JSON error (all errors except 429)
Content type: application/json
| Field | Type | Description |
|---|---|---|
error |
string | Machine-readable error code (see table below) |
message |
string | Human-readable explanation |
details |
array | Validation details for VALIDATION_ERROR; empty otherwise |
For VALIDATION_ERROR, details contains one object per field:
{
"error": "VALIDATION_ERROR",
"message": "Request body validation failed",
"details": [
{ "field": "name", "issue": "field required" },
{ "field": "industry_section", "issue": "extra fields not permitted" }
]
}
RFC 7807 Problem Details (429 only)
Content type: application/problem+json
{
"type": "https://cosprotocol.io/errors/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "Organisation rate limit exceeded. Retry after 1 second.",
"cos_error_code": "RATE_LIMIT_EXCEEDED",
"retry_after": 1,
"instance": "/v1/organisations/7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
Error codes
| HTTP status | error / cos_error_code |
Meaning |
|---|---|---|
400 |
VALIDATION_ERROR |
Request body or parameters failed validation. Check details array. |
401 |
UNAUTHORIZED |
Missing, malformed, or revoked API key. |
403 |
FORBIDDEN |
Valid key, but insufficient permissions (e.g. platform key required). |
404 |
NOT_FOUND |
The resource does not exist or is not visible to your key. |
429 |
RATE_LIMIT_EXCEEDED |
Per-org or global rate limit exceeded. See Rate Limits. |
500 |
INTERNAL_ERROR |
Unexpected server error. Transient; safe to retry with backoff. |
503 |
SERVICE_UNAVAILABLE |
Dependency unavailable. Transient; retry with backoff. |
Retry semantics
| Error | Retry? | Guidance |
|---|---|---|
400 VALIDATION_ERROR |
No | Fix the request payload. |
401 UNAUTHORIZED |
No | Check your key; it may be revoked. |
403 FORBIDDEN |
No | You need a different key type. |
404 NOT_FOUND |
No | Resource doesn't exist. |
429 RATE_LIMIT_EXCEEDED |
Yes | Wait Retry-After seconds, then retry. |
500 INTERNAL_ERROR |
Yes | Retry with exponential backoff. |
503 SERVICE_UNAVAILABLE |
Yes | Retry with exponential backoff. |
Checking the error code in code
Python:
import requests
resp = requests.get(
"https://cosprotocol.io/v1/organisations/bad-uuid",
headers={"Authorization": f"Bearer {API_KEY}"},
)
if not resp.ok:
# 429 uses application/problem+json; others use application/json
body = resp.json()
error_code = body.get("cos_error_code") or body.get("error")
print(f"Error {resp.status_code}: {error_code} — {body.get('detail') or body.get('message')}")
curl — inspect status code only:
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $COS_API_KEY" \
https://cosprotocol.io/v1/organisations/bad-uuid)
echo "HTTP $STATUS"
Common mistakes
400 with industry_section in details — industry_section is
read-only and is set by C.O.S. internally. Remove it from the request body.
400 with slug in details — slug is immutable after initial
assignment. It cannot be changed via PUT or PATCH.
403 instead of 404 — If you request a resource belonging to another
organisation, the API returns 403 FORBIDDEN rather than 404 NOT_FOUND.
This prevents enumeration.
401 on a key you just issued — Check that the full key string (including
the cos_live_ prefix) is being sent and that there are no extra spaces or
newlines in the header value.