Errors
The error JSON shape, every status code the iCog API returns, and how to handle failures in code.
The iCog API uses conventional HTTP status codes and a single, predictable JSON
error shape. A 2xx status means success. A 4xx means something about your
request was wrong (auth, validation, quota, or a missing resource). A 5xx
means the request was valid but iCog or one of its dependencies failed.
Base URL for all endpoints is https://api.cognitivx.io. See
Authentication for how to pass a key.
Error shape
Every error response is JSON with a detail field. detail is usually a
string:
{
"detail": "Memory not found"
}For a few gated and quota errors (403 and 429), detail is a structured
object instead of a string. Those shapes are documented per status code below.
Always check whether detail is a string or an object before reading it.
Uncaught server errors are wrapped by a global handler, so even a 500 is
valid JSON, never a plain-text stack trace or an HTML page:
{
"detail": "Internal server error",
"path": "/api/remember"
}Every response, success or error, carries an X-Request-ID header (a UUID).
Include it when you report a problem so it can be traced in the logs.
Validation errors (422)
When a request body or query parameter fails schema validation, FastAPI returns
422 Unprocessable Entity. Here detail is an array of field errors, not a
string:
{
"detail": [
{
"loc": ["body", "query"],
"msg": "Field required",
"type": "missing"
}
]
}| Field | Type | Description |
|---|---|---|
loc | array | Path to the offending field, e.g. ["body", "query"] or ["path", "memory_id"]. |
msg | string | Human-readable description of what is wrong. |
type | string | Machine-readable error type, e.g. missing, string_type, uuid_parsing. |
A 422 also fires when a path parameter that must be a UUID (such as a
memory_id) cannot be parsed. Validation errors are not retryable: fix the
request and send it again.
Status codes
These are the status codes the API actually returns and what triggers each one.
| Status | Meaning | When it happens |
|---|---|---|
200 | OK | Successful request that returns a body. |
201 | Created | A resource was created (for example POST /api/auth/signup or minting an API key). |
204 | No Content | A successful mutation or delete with no body. |
400 | Bad Request | The app explicitly rejected your input. For example, storing a foundational memory without a first-person anchor. detail is a string explaining the problem. |
401 | Unauthorized | Missing or invalid credentials: bad API key, malformed or expired JWT, or no auth header. See below. |
402 | Payment Required | An organization seat or billing gate. Personal memory routes never return this. |
403 | Forbidden | Authenticated but not allowed: unverified email, insufficient tier, or not a member of the target org. detail is a structured object. See below. |
404 | Not Found | The resource (memory, session, org) does not exist or is not owned by you, or the route is unknown. |
409 | Conflict | A uniqueness or state conflict, such as signing up with an email that already exists. |
410 | Gone | A one-time link or vault entry has expired or already been consumed. |
413 | Payload Too Large | An upload exceeds its size cap. POST /api/transcribe rejects audio over 25 MB. |
422 | Unprocessable Entity | Request body or params failed validation. See Validation errors. |
429 | Too Many Requests | Auth rate limit, a usage quota cap, or credit depletion. See below. |
500 | Internal Server Error | An uncaught server error. Body is { "detail": "Internal server error", "path": "..." }. |
502 | Bad Gateway | An upstream extraction failure (for example URL fetch and extract). |
503 | Service Unavailable | A dependency is not ready. GET /api/ready returns this while the database or LLM router is down; GET /api/health/detail returns it until the search index finishes warming. |
There is no API version header. Endpoints live under the unversioned /api
prefix, and the OpenAPI spec reports version 1.0.0. Do not send an
Accept-Version or X-API-Version header; none is read.
401 Unauthorized
Returned when credentials are missing or invalid. detail is a string, and the
response carries a WWW-Authenticate: Bearer header when a token failed to
validate.
{
"detail": "Could not validate credentials"
}Common detail strings: Invalid API key, Could not validate credentials,
and Token expired. Fix the credential rather than retrying. If you are using a
short-lived JWT, mint or refresh it; if you are using an API key, confirm it
starts with icog_ and has not been revoked at
developers.cognitivx.io/keys.
403 Forbidden
You are authenticated but the action is not permitted. Here detail is an
object whose error field tells you which gate you hit.
Email not verified (gates /api/chat, /api/senses, /api/introspect,
/api/suggest and similar):
{
"detail": {
"error": "email_not_verified",
"message": "Verify your email to use this feature"
}
}Tier too low:
{
"detail": {
"error": "tier_insufficient",
"current_tier": "awakened",
"required_tier": "conscious",
"upgrade_url": "https://developers.cognitivx.io/billing"
}
}A paid-feature gate uses error: "feature_gate_denied" with feature,
current_tier, recommended_tier, and upgrade_url. Admin-only and
org-membership failures return a plain string detail (Admin access required,
Not a member of this organization).
429 Too Many Requests
429 covers three distinct situations, distinguishable by the body. Read
Rate limits for the full picture.
Auth rate limiter. A fixed limit of 5 requests per 60 seconds per client IP,
applied only to the auth endpoints (/api/auth/signup, /signin,
/forgot-password, /reset-password, /resend-verification). No
Retry-After or X-RateLimit-* headers are sent.
{
"detail": "Too many requests. Please try again later."
}Tier quota exceeded. You hit a monthly message or recall cap for your tier.
detail is an object:
{
"detail": {
"error": "quota_exceeded",
"message": "Monthly recall quota reached",
"quota": { "used": 1000, "limit": 1000, "remaining": 0 },
"current_tier": "awakened",
"recommended_tier": "conscious",
"upgrade_url": "https://developers.cognitivx.io/billing"
}
}Budget or credit depletion. Memory routes (/api/recall, /api/remember,
/api/talk) surface a quota error as a structured detail object when your
credits run out. Top up or upgrade.
The auth limiter and the usage quotas do not return Retry-After headers.
The only endpoint that sets Retry-After is the per-method limiter on
/api/url, whose body is {"error":"rate_limited","retry_after":N,"method":"..."}.
Do not write code that depends on Retry-After being present on a generic
429.
Retry guidance
| Status | Retry? | How |
|---|---|---|
400, 403, 404, 409, 410, 413, 422 | No | These are caused by the request. Fix it, then resend. |
401 | No | Refresh or replace the credential, then resend. |
429 (auth limiter) | Yes, after a delay | Wait and back off. No Retry-After is provided, so use your own backoff (start around 60s for the auth limiter). |
429 (quota or credits) | No | Retrying will not help until the quota resets or you upgrade or add credits. |
500 | Yes | Transient. Retry with exponential backoff and jitter. |
502, 503 | Yes | A dependency is down or warming. Retry with backoff; check GET /api/ready. |
There is no Idempotency-Key header. Because most write operations are not
idempotent, only retry writes when you received a 5xx (the server may not have
processed the request) or a network error, and keep retries bounded.
POST /api/recall is resilient: on an internal failure it returns an empty
result {"memories": [], "count": 0} with a 200 rather than a 500. Treat
an empty result as "nothing found right now," not necessarily as a hard error.
Catching errors in TypeScript
The @cognitivx/sdk throws an ApiError
for any non-2xx response. It exposes three properties:
| Property | Type | Description |
|---|---|---|
status | number | The HTTP status code. |
message | string | The detail string when detail is a string, otherwise a generic Request failed: <status>. |
body | unknown | The full parsed JSON response body. Read this for structured detail objects (403, 429) and 422 arrays. |
For 403 and 429 errors whose detail is an object, and for 422 whose
detail is an array, message falls back to Request failed: <status>. The
useful information lives in error.body. Always inspect error.body for those
cases.
import { configureApiClient, memory, ApiError } from '@cognitivx/sdk';
configureApiClient({ apiKey: process.env.ICOG_API_KEY });
try {
const result = await memory.recall({ query: 'what did we decide about billing', limit: 5 });
console.log(result.memories);
} catch (err) {
if (err instanceof ApiError) {
console.error(`iCog returned ${err.status}: ${err.message}`);
console.error('body:', err.body);
} else {
throw err; // network or unexpected error
}
}import { configureApiClient, memory, ApiError } from '@cognitivx/sdk';
configureApiClient({ apiKey: process.env.ICOG_API_KEY });
try {
await memory.remember({ content: 'Parsa prefers limit/offset pagination', memory_type: 'semantic' });
} catch (err) {
if (!(err instanceof ApiError)) throw err;
switch (err.status) {
case 401:
// Credential is bad or expired. Refresh or replace the key, then retry.
console.error('Auth failed:', err.message);
break;
case 403: {
// detail is a structured object; read it from body.
const detail = (err.body as { detail?: { error?: string; upgrade_url?: string } }).detail;
console.error('Forbidden:', detail?.error, detail?.upgrade_url);
break;
}
case 422: {
// detail is an array of field errors.
const fields = (err.body as { detail?: { loc: string[]; msg: string }[] }).detail ?? [];
for (const f of fields) console.error(`${f.loc.join('.')}: ${f.msg}`);
break;
}
case 429:
// Quota or rate limit. Back off; do not hammer the endpoint.
console.error('Rate limited or over quota:', err.body);
break;
default:
if (err.status >= 500) {
// Transient. Safe to retry with backoff.
console.error('Server error, retrying later:', err.status);
} else {
console.error('Request error:', err.status, err.message);
}
}
}