Error envelope
All errors follow the same JSON structure:
{
"error": "Human-readable message",
"code": "MACHINE_READABLE_CODE"
}
The error field is a plain English string suitable for logging or displaying to users. The code field is a stable machine-readable identifier for programmatic error handling.
HTTP status codes
| HTTP | Meaning |
|---|---|
400 | Bad Request - malformed JSON or invalid parameter |
401 | Unauthorized - missing or invalid API key |
403 | Forbidden - key is valid but the account lacks the required plan |
404 | Not Found - resource does not exist or belongs to another account |
422 | Unprocessable Entity - valid JSON but business rule violation |
429 | Too Many Requests - rate limit exceeded |
500 | Internal Server Error - unexpected server-side failure |
Error codes
| HTTP | code | Description |
|---|---|---|
401 | MISSING_API_KEY | No API key was provided in the request |
401 | INVALID_API_KEY | Key not found in the database, or has been revoked |
403 | PLAN_REQUIRED | Account is not on the Agency plan |
404 | NOT_FOUND | Resource not found or does not belong to the authenticated account |
422 | VALIDATION_ERROR | Request body failed schema validation |
422 | LIMIT_REACHED | Plan limit reached (e.g. maximum brands exceeded) |
422 | LIMIT_EXCEEDED | Resource-level limit reached (e.g. maximum 5 avatars per brand) |
422 | QUOTA_EXCEEDED | Insufficient credits to launch the requested campaign |
409 | CONFLICT | A resource with the same unique identifier already exists |
429 | RATE_LIMITED | 60 requests per minute per key has been exceeded |
500 | INTERNAL_ERROR | Unexpected server-side error |
Rate limiting details
The rate limit is 60 requests per minute per API key, enforced via a Redis sliding window counter. When the limit is exceeded:
- HTTP status:
429 - Response header:
Retry-After: 60 - Body code:
RATE_LIMITED
The limiter fails open - if Redis is temporarily unavailable, all requests are allowed through rather than being blocked.
Handling errors
const res = await fetch("https://mentova.ai/api/v1/brands", {
headers: { "X-API-Key": "mtv_live_your_key" },
});
if (!res.ok) {
const { error, code } = await res.json();
if (res.status === 429) {
const retryAfter = res.headers.get("Retry-After");
console.error(`Rate limited. Retry after ${retryAfter}s`);
} else if (code === "QUOTA_EXCEEDED") {
console.error("Not enough credits:", error);
} else {
console.error(`API error [${code}]:`, error);
}
}