{
  "INVALID_PARAMETER": {
    "status": 400,
    "meaning": "A parameter is missing, malformed, or not one of the legal values. The body names the parameter and, for an enum, what was allowed.",
    "action": "Read the message, correct that one argument, and try once more."
  },
  "BAD_REQUEST": {
    "status": 400,
    "meaning": "Generic malformed request. Accepted synonym of INVALID_PARAMETER.",
    "action": "Read the message, correct the argument it names, and try once more."
  },
  "BAD_LESSON_ID": {
    "status": 400,
    "meaning": "The path did not resolve to a \"{tier}/{subjectSlug}/{classNum}\" lesson id.",
    "action": "The id is malformed. Copy one verbatim from a find_lessons hit."
  },
  "BAD_MEDIA_ID": {
    "status": 400,
    "meaning": "Reserved. A mediaId is \"{lessonId}#{cardId}#{type}\"; in practice a malformed one fails on the lessonId in front of the # and comes back as BAD_LESSON_ID. Take ids from the media route, never build them."
  },
  "CURSOR_EXPIRED": {
    "status": 400,
    "meaning": "The cursor was minted against an older contentVersion. Restart the walk from page 1."
  },
  "BATCH_TOO_LARGE": {
    "status": 400,
    "meaning": "More than 100 mediaIds in one POST /v1/media/urls. Split the batch."
  },
  "AMBIGUOUS_SUBJECT": {
    "status": 400,
    "meaning": "The slug exists in more than one tier. Disambiguate with the `tier` query parameter.",
    "action": "That slug exists in more than one tier. Use the full lessonId from a search hit."
  },
  "invalid_token": {
    "status": 401,
    "meaning": "Missing, malformed, expired, or unverifiable bearer token.",
    "action": "Authentication failed. Do not retry; this is a configuration problem, not something you can fix."
  },
  "invalid_request": {
    "status": 401,
    "meaning": "No Authorization header, or not a Bearer credential."
  },
  "insufficient_scope": {
    "status": 403,
    "meaning": "The token lacks the scope this route requires. The body and the challenge both name it.",
    "action": "This bot is not permitted to call that. Do not retry."
  },
  "NO_ORGANIZATION": {
    "status": 403,
    "meaning": "The token is not bound to a licensed organization."
  },
  "ORG_SUSPENDED": {
    "status": 403,
    "meaning": "The plan is not active — includes an enumeration-alarm auto-suspend.",
    "action": "Access is suspended. Stop calling tools and tell the learner you cannot look anything up right now."
  },
  "LICENSE_EXPIRED": {
    "status": 403,
    "meaning": "The plan's renewsAt is in the past. Denied on the very next call after it lapses; there is no grace period.",
    "action": "The licence has expired. Tell the learner you cannot look anything up, and stop calling tools."
  },
  "LICENSE_REQUIRED": {
    "status": 403,
    "meaning": "A newer ECLA version must be accepted. See requiredEclaVersion on GET /v1/license.",
    "action": "There is no active licence. Tell the learner you cannot look anything up, and stop calling tools."
  },
  "TIER_NOT_LICENSED": {
    "status": 403,
    "meaning": "No grant covers this content tier.",
    "action": "This licence does not cover that tier. Tell the learner it is outside what you can teach, and offer something list_subjects does cover."
  },
  "CONTENT_NOT_GRANTED": {
    "status": 403,
    "meaning": "The tier is licensed but this subject is not (per-subject licences).",
    "action": "This licence does not cover that material. Offer something list_subjects does cover."
  },
  "LEARNER_REQUIRED": {
    "status": 403,
    "meaning": "The progress routes need an interactive learner token; a client_credentials token carries no learner."
  },
  "UNKNOWN_SUBJECT": {
    "status": 404,
    "meaning": "No such subjectSlug. Slugs are tier-specific and are matched EXACTLY — display names and partial slugs are not accepted.",
    "action": "Do not guess another slug. Call list_subjects to see what exists, or find_lessons to search."
  },
  "LESSON_NOT_FOUND": {
    "status": 404,
    "meaning": "classNum out of range for the subject, or the lesson object is absent. The body reports the real range.",
    "action": "Use a lessonId exactly as find_lessons returned it. Do not assemble one."
  },
  "UNKNOWN_MEDIA": {
    "status": 404,
    "meaning": "No such mediaId on that lesson. In a batch mint this fails the WHOLE batch."
  },
  "UNKNOWN_TEST": {
    "status": 404,
    "meaning": "No such test id."
  },
  "UNKNOWN_SECTION": {
    "status": 404,
    "meaning": "That test has no such section; the body lists the ones it does have."
  },
  "NOT_FOUND": {
    "status": 404,
    "meaning": "No route matches this method and path."
  },
  "METHOD_NOT_ALLOWED": {
    "status": 405,
    "meaning": "The path exists but not for this method. The Allow header lists the methods it does serve."
  },
  "WRITE_CONFLICT": {
    "status": 409,
    "retryable": true,
    "meaning": "Another writer updated the learner record; nothing was saved. Retry after a short pause."
  },
  "PAYLOAD_TOO_LARGE": {
    "status": 413,
    "meaning": "The response would exceed the API Gateway response limit. Lower `limit`, use detail=brief, or pull the subject through /v1/exports."
  },
  "RATE_LIMITED": {
    "status": 429,
    "retryable": true,
    "meaning": "An hourly velocity cap (calls / lesson reads / URL mints). Retry-After holds the remainder of the hour.",
    "action": "Too many requests just now. Do not retry immediately; carry on with what you already have."
  },
  "QUOTA_EXCEEDED": {
    "status": 429,
    "retryable": true,
    "meaning": "A monthly quota is exhausted. Retry-After points at the monthly reset — treat a large value as \"stop\", not \"spin\".",
    "action": "The quota is spent. Do NOT retry. Keep teaching from what is already in this conversation."
  },
  "EXPORT_LIMIT": {
    "status": 429,
    "retryable": true,
    "meaning": "The monthly export allowance is exhausted; the body carries exportsUsed / exportsLimit."
  },
  "INTERNAL": {
    "status": 500,
    "meaning": "Server fault. Safe to retry once; then contact licensing@scolavo.com quoting the requestId.",
    "action": "A server error, not your mistake. Try once more; if it fails again, carry on without it."
  }
}