Every error returns a 4xx or 5xx status and the same JSON body, whatever the operation: a stable code, to test in your code; a message for humans, in the language of Accept-Language, which may change; sometimes details.
{
"error": {
"code": "string",
"message": "string",
"details": "(optional)"
}
}Example: an invalid request
{
"error": {
"code": "validation_error",
"message": "The request is invalid: see `details.issues`.",
"details": {
"target": "json",
"issues": [
{
"path": "front",
"message": "Too small: expected string to have >=1 characters"
}
]
}
}
}details.target says where the problem is (json for the body, query, param for the path, header); details.issues lists each problem, with path (the field) and message. The Link: <…>; rel="help" header of an error points to its row in the reference’s table.
The 25 codes
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
400 | validation_error | A parameter or the body is invalid; details.issues lists each problem (target, path, message). | Fix the request; do not retry it unchanged |
400 | bad_request | The request cannot be understood (malformed JSON, for instance). | Fix the request; do not retry it unchanged |
400 | invalid_cursor | The cursor is unknown, expired, or was obtained with another limit: start again from the first page. | Start the list again from the first page |
401 | invalid_api_key | The Authorization: Bearer mj_… header is missing, malformed, or unknown. | Fix the request; do not retry it unchanged |
401 | revoked_api_key | This API key was revoked: ask for a new one. | Write to us (key, scopes, organization) |
402 | insufficient_credits | The account the key acts for has no AI credits left for this generation. | Wait for the account’s AI credits to renew, or change plans; nothing was used |
403 | insufficient_scope | The key lacks the scope this operation requires (details.required, details.granted). | Write to us (key, scopes, organization) |
403 | account_suspended | The organization is suspended. | Write to us (key, scopes, organization) |
403 | live_not_enabled | Live keys are not enabled yet for this organization: use a mj_test_ key. | Write to us (key, scopes, organization) |
403 | forbidden | The account the key acts for may not do this (inactive subscription, or not the owner of the resource). | Fix the request; do not retry it unchanged |
404 | not_found | The resource does not exist, or does not belong to the account the key acts for. | Fix the request; do not retry it unchanged |
406 | not_acceptable | The API only produces application/json: accept it in the Accept header. | Fix the request; do not retry it unchanged |
409 | idempotency_in_progress | A request with the same Idempotency-Key is still running: retry after Retry-After. | Retry after Retry-After |
412 | precondition_failed | If-Match does not match the current ETag of the resource: read it again before changing it. | Read the resource again (new ETag), then retry |
413 | payload_too_large | The body exceeds 1 MB. | Fix the request; do not retry it unchanged |
415 | unsupported_media_type | The body must be JSON: send Content-Type: application/json. | Fix the request; do not retry it unchanged |
422 | idempotency_key_reused | This Idempotency-Key was already used for a different request. | Fix the request; do not retry it unchanged |
422 | content_too_short | The content body is too short (under 100 characters) to generate from. | Fix the request; do not retry it unchanged |
422 | nothing_to_generate | Every part of the content already has its memocards (details.existingMemocards). | Fix the request; do not retry it unchanged |
422 | no_memocards | The content has no memocards to generate questions from. | Fix the request; do not retry it unchanged |
422 | all_memocards_have_questions | Every memocard of the content already has questions. | Fix the request; do not retry it unchanged |
429 | rate_limited | Too many requests for this organization: retry after Retry-After seconds. | Retry after Retry-After |
429 | test_quota_exceeded | The daily quota of mj_test_ calls is reached; it resets at midnight UTC. | Retry after Retry-After |
500 | internal_error | Unexpected error: quote the X-Request-Id header when contacting us. | Retry with a growing delay and the same Idempotency-Key |
503 | unavailable | The service is temporarily unavailable (maintenance): retry after Retry-After seconds. | Retry after Retry-After |
New codes may appear for new cases: handle an unknown code according to its HTTP status.
Retrying safely
- 4xx: do not retry unchanged, fix first — except
429and409 idempotency_in_progress(wait forRetry-After) and412(read the resource again). - 500, 503, timeout or dropped connection: retry with a growing delay (1 s, 5 s, 30 s…), with the same
Idempotency-Key. You do not know whether the first request went through; the key guarantees it runs only once. - An error never uses AI credits. Quote the
X-Request-Idheader when you write to us.