Errors
Error codes, shapes, and how to handle them.
Error Shape
All errors return a consistent JSON body:
{ "error": "Human-readable error message" }Never parse the error string — use the HTTP status code for logic. The string is for humans and logging.
HTTP Status Codes
| Code | Name | When it happens |
|---|---|---|
400 | Bad Request | Missing required fields, invalid JSON, failed Zod validation |
401 | Unauthorized | Missing Authorization header, invalid API key, revoked key |
403 | Forbidden | Key exists but lacks permission for this operation |
404 | Not Found | Resource doesn't exist or isn't in your tenant scope |
409 | Conflict | Duplicate — e.g. certifying an agent that already has an active cert |
422 | Unprocessable | Valid input but illegal state — e.g. certifying a retired agent |
425 | Too Early | Report is still generating — poll again |
429 | Rate Limited | API key exceeded rate limit — see Retry-After header |
500 | Internal Error | Unexpected server error — contact support |
503 | Service Unavailable | Dependency unavailable (e.g. KMS not configured) |
Common Errors by Endpoint
Certificate issuance (POST /v1/agents/{id}/certify)
| Status | error message | Fix |
|---|---|---|
404 | Agent not found | Check agent ID and tenant scope |
409 | Agent already has an active certificate | Revoke existing cert first |
422 | Cannot certify a retired agent | Status must be pending or active |
503 | KMS not configured | AWS credentials missing — contact support |
Certificate revocation (POST /v1/certificates/{id}/revoke)
| Status | error message | Fix |
|---|---|---|
404 | Certificate not found | Check cert ID |
409 | Certificate is already revoked | Already done |
422 | Certificate has expired | Cannot revoke an expired cert |
Event ingest (POST /v1/events)
| Status | error message | Fix |
|---|---|---|
400 | Invalid action_type | Must be one of the 8 supported types |
404 | Agent not found | Agent ID not in your tenant |
422 | Agent is not active | Only active (certified) agents can ingest events |
Rate Limit Headers
When rate limited (429), the response includes:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1716040800000
Retry-After: 42Retry-After is in seconds. Wait that duration before retrying.
Handling Errors in Code
const res = await fetch('https://api.kakunin.ai/v1/agents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'my-agent', model: 'gpt-4o', version: '1.0' }),
});
if (!res.ok) {
const { error } = await res.json();
// Use res.status for branching, error string for logging
if (res.status === 429) {
const retryAfter = res.headers.get('Retry-After');
throw new Error(`Rate limited — retry after ${retryAfter}s`);
}
throw new Error(`Kakunin API error ${res.status}: ${error}`);
}