{
  "openapi": "3.1.0",
  "info": {
    "title": "Triumph API",
    "version": "1.0.0",
    "summary": "Public API for the Triumph Praxis 5001 readiness tool: practice-question bank, subtest metadata and account sync.",
    "description": "Triumph is a free Praxis 5001 (Elementary Education: Multiple Subjects) study tool. This API exposes the public practice-question bank (969 original questions across four subtests), bank statistics, and the same account endpoints the web app uses.\n\n**Authentication tiers**\n\n- Anonymous: all `GET /api/v1/*` reads work with no credentials (public tier).\n- Scoped API key: send `X-API-Key: tri_live_...`. Create a free key with `POST /api/v1/keys`. Scopes: meta:read, questions:read, stats:read.\n- Bearer JWT: account endpoints (`/api/state`) use the token returned by `POST /api/login`.\n\nAll errors use one structured envelope: `{ \"error\": { \"code\", \"message\", \"hint\" }, \"status\" }`.",
    "contact": {
      "name": "Triumph support",
      "email": "abc15531888397@gmail.com",
      "url": "https://praxis5001.com/contact"
    },
    "termsOfService": "https://praxis5001.com/terms-of-service.md"
  },
  "servers": [
    {
      "url": "https://praxis5001.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "public-data",
      "description": "Read-only question bank endpoints (anonymous access allowed)"
    },
    {
      "name": "api-keys",
      "description": "Self-serve scoped API key management"
    },
    {
      "name": "account",
      "description": "Account creation, verification and cloud state sync used by the web app"
    }
  ],
  "paths": {
    "/api/v1/health": {
      "get": {
        "tags": [
          "public-data"
        ],
        "operationId": "getHealth",
        "summary": "Service health check",
        "description": "Returns service availability, version and the list of public endpoints. Suitable as an uptime probe.",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is healthy.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "service": {
                      "type": "string",
                      "const": "triumph-api"
                    },
                    "version": {
                      "type": "string"
                    },
                    "time": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "endpoints": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "docs": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/meta": {
      "get": {
        "tags": [
          "public-data"
        ],
        "operationId": "getApiMeta",
        "summary": "Product and subtest metadata",
        "description": "Returns the four Praxis 5001 subtests with their content categories, per-subtest question counts and pointers to the developer documentation.",
        "security": [
          {
            "ApiKeyAuth": [
              "meta:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Metadata about the question bank.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/stats": {
      "get": {
        "tags": [
          "public-data"
        ],
        "operationId": "getQuestionStats",
        "summary": "Question-bank statistics",
        "description": "Counts of practice questions per subtest and per content category.",
        "security": [
          {
            "ApiKeyAuth": [
              "stats:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Bank statistics.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/questions": {
      "get": {
        "tags": [
          "public-data"
        ],
        "operationId": "listQuestions",
        "summary": "List practice questions",
        "description": "Paginated access to the full original question bank. Filter by subtest code, content category, or a case-insensitive substring search on the question text.",
        "security": [
          {
            "ApiKeyAuth": [
              "questions:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "subtest",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "5002",
                "5003",
                "5004",
                "5005"
              ]
            },
            "description": "Subtest code filter"
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive exact category-name filter"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring match on question text"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size"
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Pagination offset"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of questions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuestionPage"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/questions/random": {
      "get": {
        "tags": [
          "public-data"
        ],
        "operationId": "getRandomQuestions",
        "summary": "Random practice questions",
        "description": "Draws random questions from the bank (optionally filtered). Useful for generating quick quizzes.",
        "security": [
          {
            "ApiKeyAuth": [
              "questions:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "count",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 5
            },
            "description": "Number of questions to draw"
          },
          {
            "name": "subtest",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "5002",
                "5003",
                "5004",
                "5005"
              ]
            },
            "description": "Subtest code filter"
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Category filter"
          }
        ],
        "responses": {
          "200": {
            "description": "Randomly drawn questions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RandomQuestionSet"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys": {
      "post": {
        "tags": [
          "api-keys"
        ],
        "operationId": "createApiKey",
        "summary": "Create a free scoped API key (self-serve)",
        "description": "Issues a free-tier API key immediately — no account required. The key is returned once; store it securely. Rate limited to 10 keys per hour per IP.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateKeyRequest"
              },
              "examples": {
                "anonymous": {
                  "value": {
                    "scopes": [
                      "questions:read"
                    ]
                  }
                },
                "attributed": {
                  "value": {
                    "email": "you@example.com",
                    "scopes": [
                      "meta:read",
                      "questions:read",
                      "stats:read"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key created. The plaintext key appears exactly once in this response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateKeyResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys/verify": {
      "get": {
        "tags": [
          "api-keys"
        ],
        "operationId": "verifyApiKey",
        "summary": "Verify an API key and inspect its scopes",
        "description": "Confirms a key is valid and returns the scopes it holds. Pass the key as the X-API-Key header.",
        "security": [
          {
            "ApiKeyAuth": [
              "meta:read",
              "questions:read",
              "stats:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Key is valid.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "valid": {
                      "type": "boolean",
                      "const": true
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "meta:read",
                          "questions:read",
                          "stats:read"
                        ]
                      }
                    },
                    "tier": {
                      "type": "string",
                      "const": "free"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/register": {
      "post": {
        "tags": [
          "account"
        ],
        "operationId": "registerAccount",
        "summary": "Register an account",
        "description": "Creates an unverified account and emails a 6-digit verification code. Responds with `need_verify: true`; activate with `POST /api/verify`.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Credentials"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Account created; verification email sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "need_verify": {
                      "type": "boolean",
                      "const": true
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/login": {
      "post": {
        "tags": [
          "account"
        ],
        "operationId": "loginAccount",
        "summary": "Log in",
        "description": "Verifies credentials and returns a JWT (30-day expiry) plus the user record. Unverified accounts receive a structured `not_verified` error.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Credentials"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "JWT; send as Authorization: Bearer <token>"
                    },
                    "user": {
                      "$ref": "#/components/schemas/User"
                    },
                    "verified": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/verify": {
      "post": {
        "tags": [
          "account"
        ],
        "operationId": "verifyAccount",
        "summary": "Activate an account with the emailed code",
        "description": "Checks the 6-digit code, marks the account verified and returns a JWT.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Account verified; JWT returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string"
                    },
                    "user": {
                      "$ref": "#/components/schemas/User"
                    },
                    "verified": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/resend": {
      "post": {
        "tags": [
          "account"
        ],
        "operationId": "resendVerificationCode",
        "summary": "Resend the verification email",
        "description": "Generates and emails a fresh 6-digit code (valid 15 minutes).",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailOnly"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Code sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/logout": {
      "post": {
        "tags": [
          "account"
        ],
        "operationId": "logoutAccount",
        "summary": "Log out (client-side token removal)",
        "description": "Kept for interface completeness: tokens are removed client-side; this endpoint simply acknowledges.",
        "security": [],
        "responses": {
          "200": {
            "description": "Acknowledged.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/me": {
      "get": {
        "tags": [
          "account"
        ],
        "operationId": "getCurrentUser",
        "summary": "Current authenticated user",
        "description": "Resolves the Bearer token to the user record.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The authenticated user.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/User"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/state": {
      "get": {
        "tags": [
          "account"
        ],
        "operationId": "getState",
        "summary": "Read the caller’s synced study state",
        "description": "Returns the cloud-synced study state (answers, mastery, plan, spaced-repetition data) for the authenticated user.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current state (may be null for a fresh account).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "state": {
                      "$ref": "#/components/schemas/StudyState"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "account"
        ],
        "operationId": "putState",
        "summary": "Write the caller’s synced study state",
        "description": "Overwrites the cloud copy of the study state. A timestamp is added server-side.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PutStateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "State saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error — structured error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Self-serve scoped key. Free tier — create via POST /api/v1/keys.",
        "x-scopes": {
          "meta:read": "Read access to meta resources",
          "questions:read": "Read access to questions resources",
          "stats:read": "Read access to stats resources"
        }
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "JWT issued by POST /api/login or POST /api/verify."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Structured error envelope returned by every non-2xx response.",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable error code, e.g. invalid_email."
              },
              "message": {
                "type": "string",
                "description": "Human-readable summary."
              },
              "hint": {
                "type": "string",
                "description": "Actionable resolution hint for agents."
              },
              "details": {
                "type": "object",
                "description": "Optional machine-readable context (offending field, allowed values, etc.)."
              }
            },
            "required": [
              "code",
              "message",
              "hint"
            ]
          },
          "status": {
            "type": "integer",
            "description": "Mirrors the HTTP status code."
          }
        },
        "required": [
          "error",
          "status"
        ]
      },
      "Question": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "5002-001"
            ]
          },
          "subtest_code": {
            "type": "string",
            "enum": [
              "5002",
              "5003",
              "5004",
              "5005"
            ]
          },
          "subtest": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "question": {
            "type": "string"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "answer_index": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3
          },
          "answer_text": {
            "type": "string"
          },
          "explanation": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "subtest_code",
          "subtest",
          "category",
          "question",
          "options",
          "answer_index",
          "answer_text",
          "explanation"
        ]
      },
      "QuestionPage": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "total": {
            "type": "integer",
            "description": "Total matching questions before pagination."
          },
          "count": {
            "type": "integer",
            "description": "Questions on this page."
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Question"
            }
          }
        }
      },
      "RandomQuestionSet": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "count": {
            "type": "integer"
          },
          "total_pool": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Question"
            }
          }
        }
      },
      "MetaResponse": {
        "type": "object",
        "properties": {
          "product": {
            "type": "string",
            "const": "Triumph"
          },
          "description": {
            "type": "string"
          },
          "base_url": {
            "type": "string",
            "format": "uri"
          },
          "subtests": {
            "type": "object",
            "description": "Map of subtest code → { name, questionCount, categories[] }"
          },
          "total_questions": {
            "type": "integer"
          },
          "passing_model": {
            "type": "string"
          },
          "docs": {
            "type": "object",
            "properties": {
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "developers": {
                "type": "string",
                "format": "uri"
              },
              "mcp": {
                "type": "string",
                "format": "uri"
              },
              "llms": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "StatsResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "total": {
            "type": "integer"
          },
          "generatedAt": {
            "type": "string",
            "format": "date"
          },
          "subtests": {
            "type": "object"
          }
        }
      },
      "CreateKeyRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Optional — lets us contact you about breaking changes."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "meta:read",
                "questions:read",
                "stats:read"
              ]
            },
            "default": [
              "meta:read",
              "questions:read",
              "stats:read"
            ],
            "description": "Least-privilege scopes for this key."
          }
        }
      },
      "CreateKeyResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "key": {
            "type": "string",
            "pattern": "^tri_live_[0-9a-f]{40}$",
            "description": "Plaintext key — shown exactly once."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tier": {
            "type": "string",
            "const": "free"
          },
          "note": {
            "type": "string"
          },
          "docs": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "ok",
          "key",
          "scopes",
          "tier"
        ]
      },
      "Credentials": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string",
            "minLength": 8
          }
        },
        "required": [
          "email",
          "password"
        ]
      },
      "EmailOnly": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          }
        },
        "required": [
          "email"
        ]
      },
      "VerifyRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "code": {
            "type": "string",
            "minLength": 6,
            "maxLength": 6,
            "description": "6-digit code from the verification email."
          }
        },
        "required": [
          "email",
          "code"
        ]
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          }
        },
        "required": [
          "id",
          "email"
        ]
      },
      "PutStateRequest": {
        "type": "object",
        "properties": {
          "state": {
            "$ref": "#/components/schemas/StudyState"
          }
        },
        "required": [
          "state"
        ]
      },
      "StudyState": {
        "type": "object",
        "description": "Free-form study-state document (answers, mastery map, plan, SRS data, exam date).",
        "properties": {
          "answers": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "mastery": {
            "type": "object"
          },
          "plan": {
            "type": [
              "object",
              "null"
            ]
          },
          "srs": {
            "type": "object"
          },
          "tasks": {
            "type": "object"
          },
          "examDate": {
            "type": "string"
          },
          "updatedAt": {
            "type": "integer",
            "description": "Set by the server on write."
          }
        }
      }
    }
  }
}
