{
  "openapi": "3.1.0",
  "info": {
    "title": "StudySoda API",
    "version": "1.0.0",
    "description": "Self-hosted study materials API. JSON envelopes. All new creations default public; private creation/editing requires Pro. Existing private material remains private after expiry/downgrade. API keys support content automation; Account management requires a signed-in browser session; public study also supports isolated anonymous guest cookies. Cookie mutations require the exact configured Origin. No cross-origin CORS is enabled. Other than health/config, PostgreSQL must be available. API budget240 units/account/min: ordinary requests1, imports10, exports60. Default per-account bounds500materials,100subjects,2000attempts,5000idempotentimports and50MiB logicalcontent; configurable server-side, overflow422 QUOTA_EXCEEDED."
  },
  "servers": [
    {
      "url": "/",
      "description": "This StudySoda installation"
    }
  ],
  "paths": {
    "/api/config": {
      "get": {
        "summary": "Public authentication capabilities",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "emailEnabled": {
                          "type": "boolean"
                        },
                        "requireEmailVerification": {
                          "type": "boolean"
                        },
                        "googleEnabled": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "emailEnabled",
                        "requireEmailVerification",
                        "googleEnabled"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "summary": "Liveness",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": {
                          "const": "ok"
                        }
                      },
                      "required": [
                        "status"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/ready": {
      "get": {
        "summary": "PostgreSQL and schema readiness",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": {
                          "const": "ready"
                        }
                      },
                      "required": [
                        "status"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "summary": "Current profile",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Profile"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Change display name",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  }
                },
                "required": [
                  "name"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Profile"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete account and all owned data",
        "description": "Requires current password and no active/pending billing deletion lock (409 BILLING_ACTIVE). Cascades content, subjects, sessions, API keys, progress, attempts, and manual plan records. Irreversible.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "password": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  }
                },
                "required": [
                  "password"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "deleted": {
                          "const": true
                        }
                      },
                      "required": [
                        "deleted"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me/security": {
      "get": {
        "summary": "Own sign-in method availability",
        "description": "Returns only the signed-in user's sign-in method flags and this site's enabled recovery/provider flags. No provider IDs, email addresses or tokens. API keys are rejected. OAuth-only accounts must create a StudySoda password through configured email recovery before password-confirmed deletion.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "hasPassword": {
                          "type": "boolean"
                        },
                        "googleLinked": {
                          "type": "boolean"
                        },
                        "googleEnabled": {
                          "type": "boolean"
                        },
                        "emailEnabled": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "hasPassword",
                        "googleLinked",
                        "googleEnabled",
                        "emailEnabled"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/plan": {
      "get": {
        "summary": "Current effective plan",
        "description": "Active manual Pro OR trusted eligible billing grants private-content access. Expired grants resolve to Free. There is no plan mutation endpoint; account/signup input cannot grant Pro.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Plan"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/materials": {
      "get": {
        "summary": "List public or saved/owned materials",
        "description": "Default scope=library requires sign-in or a materials:read API key; scope=public permits guests. List responses contain metadata/itemCount and content={}; fetch a material by ID for its full visible body.",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "enum": [
                "library",
                "public"
              ],
              "default": "library"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "schema": {
              "enum": [
                "note",
                "flashcards",
                "quiz"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "subjectId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "topicId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 40
            },
            "description": "Exact case-insensitive tag match; SQL-like punctuation is treated literally."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 24
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Material"
                          }
                        },
                        "total": {
                          "type": "integer"
                        },
                        "pageCount": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "items",
                        "total",
                        "pageCount"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create material",
        "description": "Public by default on every plan. Free accounts may create public content only; private requires active Pro (403 PRO_REQUIRED). Notes need markdown; decks need cards; quizzes need questions. IDs inside a set must be unique; answer indices must match options. subjectId and topicId must be owned. A linked topic supplies the subject; an explicit conflicting subjectId returns422 TOPIC_SUBJECT_MISMATCH.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:write",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MaterialInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Material"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/materials/{id}": {
      "get": {
        "summary": "Read accessible material",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          },
          {
            "name": "editing",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "true requires ownership."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Material"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update owned material",
        "description": "Only supplied fields change. Kind cannot change. Seed content is immutable. Editing private material or making material private requires active Pro. Free owners retain read/export/delete of existing private content and may explicitly publish it with visibility=public. Expiry/downgrade never changes visibility.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MaterialPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Material"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete owned material",
        "description": "Cascades bookmarks, progress, and attempts tied to the deleted material.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "deleted": {
                          "const": true
                        }
                      },
                      "required": [
                        "deleted"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/materials/{id}/duplicate": {
      "post": {
        "summary": "Create an owned copy with explicit publication rules",
        "description": "Public sources default to public copies. Private sources require explicit visibility in the body (409 COPY_VISIBILITY_REQUIRED otherwise); explicit public publishes the new copy, explicit private requires Pro. Source visibility never changes. Topic/subject grouping is preserved only for an owned source. Optional topicId chooses an owned target topic (null detaches); copies never inherit another owner's topic.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DuplicateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Material"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/materials/{id}/save": {
      "post": {
        "summary": "Bookmark or unbookmark public material",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "saved": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "saved"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "saved": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "saved"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/materials/{id}/related": {
      "get": {
        "summary": "Read visible related study formats",
        "description": "Returns the three newest metadata-only siblings per kind from the same explicit topic (at most9), excluding the source. Public and requester-owned materials are visible; quiz answers and material bodies are omitted. One format cannot crowd out another; topic-filtered lists retain all results. A material without a topic returns topic:null and items:[]. No title/tag similarity fallback is used. An inaccessible source returns404.",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "topic": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/Topic"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Material"
                          }
                        }
                      },
                      "required": [
                        "topic",
                        "items"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/topics": {
      "get": {
        "summary": "List owned or public topics",
        "description": "Default owned scope requires authentication; public scope permits guests and counts only public material even for signed-in owners. Private/empty topics are excluded from public scope. At most100 topics per account.",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "enum": [
                "owned",
                "public"
              ],
              "default": "owned"
            }
          },
          {
            "name": "subjectId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Topic"
                          }
                        },
                        "total": {
                          "type": "integer"
                        },
                        "pageCount": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "items",
                        "total",
                        "pageCount"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create an owned topic",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:write",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TopicInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Topic"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/topics/{id}": {
      "get": {
        "summary": "Read a visible topic",
        "description": "Owners see their own topic and visible count. Other viewers require at least one public material and receive its public count only.",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Topic"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update an owned topic",
        "description": "Topic subject can change only while no materials are linked; otherwise422 TOPIC_NOT_EMPTY. Seed topics are immutable.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TopicPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Topic"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete topic and detach its materials",
        "description": "Material content, subject and visibility are preserved. Only topicId is detached.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "deleted": {
                          "const": true
                        }
                      },
                      "required": [
                        "deleted"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/subjects": {
      "get": {
        "summary": "List visible subjects",
        "description": "Guests see global subjects and subjects containing public material. Signed-in users also see owned subjects. Counts include only visible material; at most500 summaries, owned first.",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Subject"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create subject",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:write",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubjectInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Subject"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/subjects/{id}": {
      "get": {
        "summary": "Read a visible subject",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Subject"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update owned subject",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubjectPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Subject"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete subject and detach materials",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "deleted": {
                          "const": true
                        }
                      },
                      "required": [
                        "deleted"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/progress/{id}": {
      "get": {
        "summary": "Read personal progress for material",
        "description": "Anonymous use is limited to public sources and isolated by a 30-day HttpOnly guest cookie, automatically created on first successful progress PUT/exam POST. Default-mode keys remain hidden until submit; instant mode reveals only checked, locked questions. Even submitted guest results require source visibility to remain public. Account sessions take precedence, without automatic merging. API keys cannot study. Guest limits:100 progress/30 attempts/10MiB snapshots; same-origin mutations required.",
        "security": [
          {},
          {
            "guestCookie": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "X-Study-Owner",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Optional identity precondition: guest or the account ID captured when study data loaded. Mismatch returns 409 STUDY_IDENTITY_CHANGED; this never grants authority."
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Progress"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Replace personal study progress",
        "description": "id is material ID. known/starred must identify current cards/questions, current is zero-based. Notes require empty known/starred arrays and current=0. Saving checks source visibility and content transactionally. Default 1,000 tracked materials per account; existing state may be updated at capacity. Anonymous use is limited to public sources and isolated by a 30-day HttpOnly guest cookie, automatically created on first successful progress PUT/exam POST. Default-mode keys remain hidden until submit; instant mode reveals only checked, locked questions. Even submitted guest results require source visibility to remain public. Account sessions take precedence, without automatic merging. API keys cannot study. Guest limits:100 progress/30 attempts/10MiB snapshots; same-origin mutations required.",
        "security": [
          {},
          {
            "guestCookie": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "X-Study-Owner",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Optional identity precondition: guest or the account ID captured when study data loaded. Mismatch returns 409 STUDY_IDENTITY_CHANGED; this never grants authority."
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProgressInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Progress"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Remove personal study progress and free tracking capacity",
        "description": "Idempotently deletes only the caller's progress. Remains available after the source becomes private; does not remove other learners' state or reveal source content. Anonymous use is limited to public sources and isolated by a 30-day HttpOnly guest cookie, automatically created on first successful progress PUT/exam POST. Default-mode keys remain hidden until submit; instant mode reveals only checked, locked questions. Even submitted guest results require source visibility to remain public. Account sessions take precedence, without automatic merging. API keys cannot study. Guest limits:100 progress/30 attempts/10MiB snapshots; same-origin mutations required.",
        "security": [
          {},
          {
            "guestCookie": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "X-Study-Owner",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Optional identity precondition: guest or the account ID captured when study data loaded. Mismatch returns 409 STUDY_IDENTITY_CHANGED; this never grants authority."
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "deleted": {
                          "const": true
                        }
                      },
                      "required": [
                        "deleted"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/attempts": {
      "get": {
        "summary": "List last 50 personal attempts",
        "description": "Anonymous use is limited to public sources and isolated by a 30-day HttpOnly guest cookie, automatically created on first successful progress PUT/exam POST. Default-mode keys remain hidden until submit; instant mode reveals only checked, locked questions. Even submitted guest results require source visibility to remain public. Account sessions take precedence, without automatic merging. API keys cannot study. Guest limits:100 progress/30 attempts/10MiB snapshots; same-origin mutations required.",
        "security": [
          {},
          {
            "guestCookie": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "X-Study-Owner",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Optional identity precondition: guest or the account ID captured when study data loaded. Mismatch returns 409 STUDY_IDENTITY_CHANGED; this never grants authority."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Activity"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Start or resume exam snapshot",
        "description": "Concurrent starts return the same active attempt, retaining its feedback mode and elapsedMs. Default feedbackMode is after-submit; opt-in instant mode reveals only checked questions. Snapshot freezes question content and correct answers. Anonymous use is limited to public sources and isolated by a 30-day HttpOnly guest cookie, automatically created on first successful progress PUT/exam POST. Default-mode keys remain hidden until submit; instant mode reveals only checked, locked questions. Even submitted guest results require source visibility to remain public. Account sessions take precedence, without automatic merging. API keys cannot study. Guest limits:100 progress/30 attempts/10MiB snapshots; same-origin mutations required.",
        "security": [
          {},
          {
            "guestCookie": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "X-Study-Owner",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Optional identity precondition: guest or the account ID captured when study data loaded. Mismatch returns 409 STUDY_IDENTITY_CHANGED; this never grants authority."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "materialId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128,
                    "pattern": "^[a-zA-Z0-9_-]+$"
                  },
                  "feedbackMode": {
                    "type": "string",
                    "enum": [
                      "after-submit",
                      "instant"
                    ]
                  }
                },
                "required": [
                  "materialId"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Attempt"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/attempts/{id}": {
      "get": {
        "summary": "Read owned attempt",
        "description": "Anonymous use is limited to public sources and isolated by a 30-day HttpOnly guest cookie, automatically created on first successful progress PUT/exam POST. Default-mode keys remain hidden until submit; instant mode reveals only checked, locked questions. Even submitted guest results require source visibility to remain public. Account sessions take precedence, without automatic merging. API keys cannot study. Guest limits:100 progress/30 attempts/10MiB snapshots; same-origin mutations required.",
        "security": [
          {},
          {
            "guestCookie": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "X-Study-Owner",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Optional identity precondition: guest or the account ID captured when study data loaded. Mismatch returns 409 STUDY_IDENTITY_CHANGED; this never grants authority."
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Attempt"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Save answers, flags, and position",
        "description": "Supplied answers/flags replace the entire map/list; current is zero-based. Checked answers cannot change or be omitted. feedbackMode may change only while no questions have been checked/revealed. elapsedMs stores max(previous,supplied), bounded to one year; descriptive only. Submitted attempts are immutable. Anonymous use is limited to public sources and isolated by a 30-day HttpOnly guest cookie, automatically created on first successful progress PUT/exam POST. Default-mode keys remain hidden until submit; instant mode reveals only checked, locked questions. Even submitted guest results require source visibility to remain public. Account sessions take precedence, without automatic merging. API keys cannot study. Guest limits:100 progress/30 attempts/10MiB snapshots; same-origin mutations required.",
        "security": [
          {},
          {
            "guestCookie": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "X-Study-Owner",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Optional identity precondition: guest or the account ID captured when study data loaded. Mismatch returns 409 STUDY_IDENTITY_CHANGED; this never grants authority."
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttemptPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Attempt"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/attempts/{id}/check": {
      "post": {
        "summary": "Check and lock one instant-feedback answer",
        "description": "Requires in-progress instant mode. Atomically records the chosen answer and adds its question ID to revealed. Returns the attempt with that question's answer/explanation visible; other unchecked keys stay hidden. Identical retries are idempotent; a different answer returns 409 ANSWER_LOCKED. After-submit mode returns 409 FEEDBACK_DISABLED. Score remains null until submission. Anonymous use is limited to public sources and isolated by a 30-day HttpOnly guest cookie, automatically created on first successful progress PUT/exam POST. Default-mode keys remain hidden until submit; instant mode reveals only checked, locked questions. Even submitted guest results require source visibility to remain public. Account sessions take precedence, without automatic merging. API keys cannot study. Guest limits:100 progress/30 attempts/10MiB snapshots; same-origin mutations required.",
        "security": [
          {},
          {
            "guestCookie": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "X-Study-Owner",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Optional identity precondition: guest or the account ID captured when study data loaded. Mismatch returns 409 STUDY_IDENTITY_CHANGED; this never grants authority."
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "questionId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128,
                    "pattern": "^[a-zA-Z0-9_-]+$"
                  },
                  "answer": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 5
                  }
                },
                "required": [
                  "questionId",
                  "answer"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Attempt"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/attempts/{id}/submit": {
      "post": {
        "summary": "Grade attempt on server",
        "description": "Unanswered questions count as incorrect. Repeated submits return the same result. Score is raw correct count. Full worked explanations become visible. Anonymous use is limited to public sources and isolated by a 30-day HttpOnly guest cookie, automatically created on first successful progress PUT/exam POST. Default-mode keys remain hidden until submit; instant mode reveals only checked, locked questions. Even submitted guest results require source visibility to remain public. Account sessions take precedence, without automatic merging. API keys cannot study. Guest limits:100 progress/30 attempts/10MiB snapshots; same-origin mutations required.",
        "security": [
          {},
          {
            "guestCookie": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "X-Study-Owner",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Optional identity precondition: guest or the account ID captured when study data loaded. Mismatch returns 409 STUDY_IDENTITY_CHANGED; this never grants authority."
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Attempt"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/attempts/{id}/retry": {
      "post": {
        "summary": "Start a new attempt after submission",
        "description": "Requires a submitted source attempt; uses current accessible material. Bodyless POST inherits previous feedback mode; optional feedbackMode selects a mode for a new attempt. New elapsedMs is 0 and revealed is empty. Resumes an already-active retry unchanged if present. Anonymous use is limited to public sources and isolated by a 30-day HttpOnly guest cookie, automatically created on first successful progress PUT/exam POST. Default-mode keys remain hidden until submit; instant mode reveals only checked, locked questions. Even submitted guest results require source visibility to remain public. Account sessions take precedence, without automatic merging. API keys cannot study. Guest limits:100 progress/30 attempts/10MiB snapshots; same-origin mutations required.",
        "security": [
          {},
          {
            "guestCookie": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "X-Study-Owner",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Optional identity precondition: guest or the account ID captured when study data loaded. Mismatch returns 409 STUDY_IDENTITY_CHANGED; this never grants authority."
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "feedbackMode": {
                    "type": "string",
                    "enum": [
                      "after-submit",
                      "instant"
                    ]
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Attempt"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/stats": {
      "get": {
        "summary": "Personal dashboard totals",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Stats"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys": {
      "get": {
        "summary": "List API key metadata",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Key"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a scoped API key",
        "description": "The secret is returned once. Up to 20 keys per account; expiry 1–365 days. Store as a secret. Only SHA-256 is retained by StudySoda.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KeyInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "record": {
                          "$ref": "#/components/schemas/Key"
                        }
                      },
                      "required": [
                        "key",
                        "record"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys/{id}": {
      "delete": {
        "summary": "Revoke API key immediately",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "revoked": {
                          "const": true
                        }
                      },
                      "required": [
                        "revoked"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/import": {
      "post": {
        "summary": "Transactionally import up to 50 materials",
        "description": "Maximum 8,000,000 bytes. All or nothing. Omitted visibility defaults public. A Free batch containing any private creation returns403 PRO_REQUIRED and creates nothing. Reusing a key with the same parsed payload returns the original IDs with current owned material content; different payload returns 409. If imported records were later deleted, replay returns 409 IMPORT_RECORDS_REMOVED. Only IDs/hash are cached, not private content. Use a new key for each logical import. Keys retained until account deletion.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "materials": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Material"
                          }
                        }
                      },
                      "required": [
                        "materials"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/export": {
      "get": {
        "summary": "Export portable owned content with answer keys",
        "description": "Downloaded JSON can be sent directly to /import. Excludes IDs, subject associations, other users, bookmarks, progress, attempts, password hashes and API tokens. Maximum import batch 50; split larger exports.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "x-required-api-key-scope": "materials:read",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "materials": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/MaterialInput"
                          }
                        }
                      },
                      "required": [
                        "materials"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or key invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not owner, missing scope, session required, Pro required, or origin rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Record or endpoint not found (also conceals private records)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body was not received within 15 seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Submitted attempt, idempotency conflict, explicit copy visibility required, or billing blocks account deletion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON content type required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema or domain validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Scoped sr_ API key issued in Settings → API. Independent of cookies and Origin."
      },
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "better-auth.session_token",
        "description": "Better Auth cookie; HTTPS uses __Secure-better-auth.session_token. Browser login through /api/auth; HttpOnly, SameSite=Lax."
      },
      "guestCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "studysoda.guest",
        "description": "Opaque browser-linked guest identity; HTTPS uses __Host-studysoda.guest with Secure. HttpOnly, SameSite=Lax, fixed30-day expiry. Created automatically by a valid anonymous study write; never an account credential."
      }
    },
    "schemas": {
      "RichTextDocument": {
        "type": "object",
        "properties": {
          "type": {
            "const": "doc"
          },
          "content": {
            "type": "array",
            "maxItems": 4000,
            "items": {
              "$ref": "#/components/schemas/RichTextNode"
            }
          }
        },
        "required": [
          "type"
        ],
        "additionalProperties": false,
        "description": "Optional notes-only Tiptap JSON. At most 12,000 nodes, depth 24, 200,000 text characters. Only allowlisted formatting and attributes are accepted; raw HTML, scripts and unsafe URLs are rejected. The server regenerates portable markdown from this document, overriding any supplied markdown."
      },
      "RichTextNode": {
        "type": "object",
        "properties": {
          "type": {
            "enum": [
              "paragraph",
              "heading",
              "text",
              "hardBreak",
              "blockquote",
              "bulletList",
              "orderedList",
              "listItem",
              "codeBlock",
              "horizontalRule",
              "table",
              "tableRow",
              "tableCell",
              "tableHeader",
              "image"
            ]
          },
          "text": {
            "type": "string",
            "maxLength": 200000
          },
          "attrs": {
            "type": "object",
            "description": "Node-specific allowlisted attributes only. Heading level 1–6; ordered-list start 1–1,000,000; HTTPS or root-relative images. No HTML attributes."
          },
          "marks": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "enum": [
                    "bold",
                    "italic",
                    "underline",
                    "strike",
                    "code",
                    "highlight",
                    "textStyle",
                    "link"
                  ]
                },
                "attrs": {
                  "type": "object",
                  "description": "Formatting-specific allowlisted attributes only. Links accept http, https, mailto, root-relative or local anchors; scripts/data URLs are rejected."
                }
              },
              "required": [
                "type"
              ],
              "additionalProperties": false
            }
          },
          "content": {
            "type": "array",
            "maxItems": 4000,
            "items": {
              "$ref": "#/components/schemas/RichTextNode"
            }
          }
        },
        "required": [
          "type"
        ],
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "issues": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "path": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "path",
                    "message"
                  ],
                  "additionalProperties": false
                }
              }
            },
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false
      },
      "Plan": {
        "type": "object",
        "properties": {
          "tier": {
            "enum": [
              "free",
              "pro"
            ]
          },
          "canCreatePrivate": {
            "type": "boolean"
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Effective access expiry; null for an active manual grant without expiry. Expired manual grants may retain their past expiry while tier is free."
          }
        },
        "required": [
          "tier",
          "canCreatePrivate",
          "expiresAt"
        ],
        "additionalProperties": false
      },
      "Profile": {
        "type": "object",
        "properties": {
          "plan": {
            "$ref": "#/components/schemas/Plan"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          },
          "emailVerified": {
            "type": "boolean"
          }
        },
        "required": [
          "plan",
          "id",
          "name",
          "email",
          "image",
          "emailVerified"
        ],
        "additionalProperties": false
      },
      "Content": {
        "type": "object",
        "properties": {
          "markdown": {
            "type": "string",
            "maxLength": 200000
          },
          "richText": {
            "$ref": "#/components/schemas/RichTextDocument"
          },
          "cards": {
            "maxItems": 1000,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[a-zA-Z0-9_-]+$"
                },
                "front": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 10000
                },
                "back": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 20000
                },
                "explanation": {
                  "type": "string",
                  "maxLength": 20000
                },
                "source": {
                  "type": "string",
                  "maxLength": 500
                }
              },
              "required": [
                "id",
                "front",
                "back"
              ],
              "additionalProperties": false
            }
          },
          "questions": {
            "maxItems": 500,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[a-zA-Z0-9_-]+$"
                },
                "prompt": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 10000
                },
                "options": {
                  "minItems": 2,
                  "maxItems": 6,
                  "type": "array",
                  "items": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 5000
                  }
                },
                "answer": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 5
                },
                "explanation": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 30000
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "concept",
                    "calculation"
                  ]
                },
                "source": {
                  "type": "string",
                  "maxLength": 500
                }
              },
              "required": [
                "id",
                "prompt",
                "options",
                "answer",
                "explanation",
                "kind"
              ],
              "additionalProperties": false
            }
          },
          "conventions": {
            "type": "string",
            "maxLength": 10000
          }
        },
        "additionalProperties": false
      },
      "MaterialInput": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "default": "",
            "type": "string",
            "maxLength": 2000
          },
          "kind": {
            "type": "string",
            "enum": [
              "note",
              "flashcards",
              "quiz"
            ]
          },
          "visibility": {
            "default": "public",
            "type": "string",
            "enum": [
              "private",
              "public"
            ]
          },
          "subjectId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_-]+$"
              },
              {
                "type": "null"
              }
            ]
          },
          "topicId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_-]+$"
              },
              {
                "type": "null"
              }
            ]
          },
          "content": {
            "type": "object",
            "properties": {
              "markdown": {
                "type": "string",
                "maxLength": 200000
              },
              "richText": {
                "$ref": "#/components/schemas/RichTextDocument"
              },
              "cards": {
                "maxItems": 1000,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 128,
                      "pattern": "^[a-zA-Z0-9_-]+$"
                    },
                    "front": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 10000
                    },
                    "back": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 20000
                    },
                    "explanation": {
                      "type": "string",
                      "maxLength": 20000
                    },
                    "source": {
                      "type": "string",
                      "maxLength": 500
                    }
                  },
                  "required": [
                    "id",
                    "front",
                    "back"
                  ],
                  "additionalProperties": false
                }
              },
              "questions": {
                "maxItems": 500,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 128,
                      "pattern": "^[a-zA-Z0-9_-]+$"
                    },
                    "prompt": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 10000
                    },
                    "options": {
                      "minItems": 2,
                      "maxItems": 6,
                      "type": "array",
                      "items": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 5000
                      }
                    },
                    "answer": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 5
                    },
                    "explanation": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30000
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "concept",
                        "calculation"
                      ]
                    },
                    "source": {
                      "type": "string",
                      "maxLength": 500
                    }
                  },
                  "required": [
                    "id",
                    "prompt",
                    "options",
                    "answer",
                    "explanation",
                    "kind"
                  ],
                  "additionalProperties": false
                }
              },
              "conventions": {
                "type": "string",
                "maxLength": 10000
              }
            },
            "additionalProperties": false
          },
          "tags": {
            "default": [],
            "maxItems": 12,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            }
          },
          "cover": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 1000,
                "pattern": "^(\\/[a-zA-Z0-9/_.,-]+|https:\\/\\/[^\\s]+)$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "title",
          "kind",
          "content"
        ],
        "additionalProperties": false
      },
      "MaterialPatch": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string",
            "maxLength": 2000
          },
          "visibility": {
            "type": "string",
            "enum": [
              "private",
              "public"
            ]
          },
          "subjectId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_-]+$"
              },
              {
                "type": "null"
              }
            ]
          },
          "topicId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_-]+$"
              },
              {
                "type": "null"
              }
            ]
          },
          "content": {
            "type": "object",
            "properties": {
              "markdown": {
                "type": "string",
                "maxLength": 200000
              },
              "richText": {
                "$ref": "#/components/schemas/RichTextDocument"
              },
              "cards": {
                "maxItems": 1000,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 128,
                      "pattern": "^[a-zA-Z0-9_-]+$"
                    },
                    "front": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 10000
                    },
                    "back": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 20000
                    },
                    "explanation": {
                      "type": "string",
                      "maxLength": 20000
                    },
                    "source": {
                      "type": "string",
                      "maxLength": 500
                    }
                  },
                  "required": [
                    "id",
                    "front",
                    "back"
                  ],
                  "additionalProperties": false
                }
              },
              "questions": {
                "maxItems": 500,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 128,
                      "pattern": "^[a-zA-Z0-9_-]+$"
                    },
                    "prompt": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 10000
                    },
                    "options": {
                      "minItems": 2,
                      "maxItems": 6,
                      "type": "array",
                      "items": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 5000
                      }
                    },
                    "answer": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 5
                    },
                    "explanation": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30000
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "concept",
                        "calculation"
                      ]
                    },
                    "source": {
                      "type": "string",
                      "maxLength": 500
                    }
                  },
                  "required": [
                    "id",
                    "prompt",
                    "options",
                    "answer",
                    "explanation",
                    "kind"
                  ],
                  "additionalProperties": false
                }
              },
              "conventions": {
                "type": "string",
                "maxLength": 10000
              }
            },
            "additionalProperties": false
          },
          "tags": {
            "maxItems": 12,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            }
          },
          "cover": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 1000,
                "pattern": "^(\\/[a-zA-Z0-9/_.,-]+|https:\\/\\/[^\\s]+)$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false
      },
      "DuplicateInput": {
        "type": "object",
        "properties": {
          "visibility": {
            "type": "string",
            "enum": [
              "public",
              "private"
            ]
          },
          "topicId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_-]+$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false
      },
      "SubjectInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "description": {
            "default": "",
            "type": "string",
            "maxLength": 1000
          },
          "color": {
            "default": "#4255FF",
            "type": "string",
            "pattern": "^#[0-9a-fA-F]{6}$"
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false
      },
      "SubjectPatch": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "maxLength": 1000
          },
          "color": {
            "type": "string",
            "pattern": "^#[0-9a-fA-F]{6}$"
          }
        },
        "additionalProperties": false
      },
      "TopicInput": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "default": "",
            "type": "string",
            "maxLength": 2000
          },
          "subjectId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_-]+$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "title"
        ],
        "additionalProperties": false
      },
      "TopicPatch": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string",
            "maxLength": 2000
          },
          "subjectId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_-]+$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false
      },
      "ProgressInput": {
        "type": "object",
        "properties": {
          "known": {
            "maxItems": 1000,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[a-zA-Z0-9_-]+$"
            }
          },
          "starred": {
            "maxItems": 1000,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[a-zA-Z0-9_-]+$"
            }
          },
          "current": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1000
          },
          "completed": {
            "type": "boolean"
          }
        },
        "required": [
          "known",
          "starred",
          "current",
          "completed"
        ],
        "additionalProperties": false
      },
      "AttemptPatch": {
        "type": "object",
        "properties": {
          "answers": {
            "type": "object",
            "propertyNames": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[a-zA-Z0-9_-]+$"
            },
            "additionalProperties": {
              "type": "integer",
              "minimum": 0,
              "maximum": 5
            }
          },
          "flags": {
            "maxItems": 500,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[a-zA-Z0-9_-]+$"
            }
          },
          "current": {
            "type": "integer",
            "minimum": 0,
            "maximum": 499
          },
          "elapsedMs": {
            "type": "integer",
            "minimum": 0,
            "maximum": 31536000000
          },
          "feedbackMode": {
            "type": "string",
            "enum": [
              "after-submit",
              "instant"
            ]
          }
        },
        "additionalProperties": false
      },
      "KeyInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "scopes": {
            "minItems": 1,
            "maxItems": 2,
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "materials:read",
                "materials:write"
              ]
            }
          },
          "expiresInDays": {
            "default": 90,
            "type": "integer",
            "minimum": 1,
            "maximum": 365
          }
        },
        "required": [
          "name",
          "scopes"
        ],
        "additionalProperties": false
      },
      "ImportInput": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "materials": {
                "minItems": 1,
                "maxItems": 50,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 200
                    },
                    "description": {
                      "default": "",
                      "type": "string",
                      "maxLength": 2000
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "note",
                        "flashcards",
                        "quiz"
                      ]
                    },
                    "visibility": {
                      "default": "public",
                      "type": "string",
                      "enum": [
                        "private",
                        "public"
                      ]
                    },
                    "subjectId": {
                      "anyOf": [
                        {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 128,
                          "pattern": "^[a-zA-Z0-9_-]+$"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "topicId": {
                      "anyOf": [
                        {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 128,
                          "pattern": "^[a-zA-Z0-9_-]+$"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "content": {
                      "type": "object",
                      "properties": {
                        "markdown": {
                          "type": "string",
                          "maxLength": 200000
                        },
                        "richText": {
                          "$ref": "#/components/schemas/RichTextDocument"
                        },
                        "cards": {
                          "maxItems": 1000,
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 128,
                                "pattern": "^[a-zA-Z0-9_-]+$"
                              },
                              "front": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 10000
                              },
                              "back": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 20000
                              },
                              "explanation": {
                                "type": "string",
                                "maxLength": 20000
                              },
                              "source": {
                                "type": "string",
                                "maxLength": 500
                              }
                            },
                            "required": [
                              "id",
                              "front",
                              "back"
                            ],
                            "additionalProperties": false
                          }
                        },
                        "questions": {
                          "maxItems": 500,
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 128,
                                "pattern": "^[a-zA-Z0-9_-]+$"
                              },
                              "prompt": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 10000
                              },
                              "options": {
                                "minItems": 2,
                                "maxItems": 6,
                                "type": "array",
                                "items": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 5000
                                }
                              },
                              "answer": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 5
                              },
                              "explanation": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 30000
                              },
                              "kind": {
                                "type": "string",
                                "enum": [
                                  "concept",
                                  "calculation"
                                ]
                              },
                              "source": {
                                "type": "string",
                                "maxLength": 500
                              }
                            },
                            "required": [
                              "id",
                              "prompt",
                              "options",
                              "answer",
                              "explanation",
                              "kind"
                            ],
                            "additionalProperties": false
                          }
                        },
                        "conventions": {
                          "type": "string",
                          "maxLength": 10000
                        }
                      },
                      "additionalProperties": false
                    },
                    "tags": {
                      "default": [],
                      "maxItems": 12,
                      "type": "array",
                      "items": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 40
                      }
                    },
                    "cover": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 1000,
                          "pattern": "^(\\/[a-zA-Z0-9/_.,-]+|https:\\/\\/[^\\s]+)$"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "title",
                    "kind",
                    "content"
                  ],
                  "additionalProperties": false
                }
              }
            },
            "required": [
              "materials"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "materials": {
                    "minItems": 1,
                    "maxItems": 50,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200
                        },
                        "description": {
                          "default": "",
                          "type": "string",
                          "maxLength": 2000
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "note",
                            "flashcards",
                            "quiz"
                          ]
                        },
                        "visibility": {
                          "default": "public",
                          "type": "string",
                          "enum": [
                            "private",
                            "public"
                          ]
                        },
                        "subjectId": {
                          "anyOf": [
                            {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 128,
                              "pattern": "^[a-zA-Z0-9_-]+$"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "topicId": {
                          "anyOf": [
                            {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 128,
                              "pattern": "^[a-zA-Z0-9_-]+$"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "content": {
                          "type": "object",
                          "properties": {
                            "markdown": {
                              "type": "string",
                              "maxLength": 200000
                            },
                            "richText": {
                              "$ref": "#/components/schemas/RichTextDocument"
                            },
                            "cards": {
                              "maxItems": 1000,
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 128,
                                    "pattern": "^[a-zA-Z0-9_-]+$"
                                  },
                                  "front": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 10000
                                  },
                                  "back": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 20000
                                  },
                                  "explanation": {
                                    "type": "string",
                                    "maxLength": 20000
                                  },
                                  "source": {
                                    "type": "string",
                                    "maxLength": 500
                                  }
                                },
                                "required": [
                                  "id",
                                  "front",
                                  "back"
                                ],
                                "additionalProperties": false
                              }
                            },
                            "questions": {
                              "maxItems": 500,
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 128,
                                    "pattern": "^[a-zA-Z0-9_-]+$"
                                  },
                                  "prompt": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 10000
                                  },
                                  "options": {
                                    "minItems": 2,
                                    "maxItems": 6,
                                    "type": "array",
                                    "items": {
                                      "type": "string",
                                      "minLength": 1,
                                      "maxLength": 5000
                                    }
                                  },
                                  "answer": {
                                    "type": "integer",
                                    "minimum": 0,
                                    "maximum": 5
                                  },
                                  "explanation": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 30000
                                  },
                                  "kind": {
                                    "type": "string",
                                    "enum": [
                                      "concept",
                                      "calculation"
                                    ]
                                  },
                                  "source": {
                                    "type": "string",
                                    "maxLength": 500
                                  }
                                },
                                "required": [
                                  "id",
                                  "prompt",
                                  "options",
                                  "answer",
                                  "explanation",
                                  "kind"
                                ],
                                "additionalProperties": false
                              }
                            },
                            "conventions": {
                              "type": "string",
                              "maxLength": 10000
                            }
                          },
                          "additionalProperties": false
                        },
                        "tags": {
                          "default": [],
                          "maxItems": 12,
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 40
                          }
                        },
                        "cover": {
                          "anyOf": [
                            {
                              "type": "string",
                              "maxLength": 1000,
                              "pattern": "^(\\/[a-zA-Z0-9/_.,-]+|https:\\/\\/[^\\s]+)$"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      },
                      "required": [
                        "title",
                        "kind",
                        "content"
                      ],
                      "additionalProperties": false
                    }
                  }
                },
                "required": [
                  "materials"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "data"
            ],
            "additionalProperties": false
          }
        ]
      },
      "Material": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "readOnly": true,
            "description": "Public-page slug, stable after first publication; unpublished title edits may change it. API and study endpoints continue to use id."
          },
          "ownerId": {
            "type": [
              "string",
              "null"
            ]
          },
          "subjectId": {
            "type": [
              "string",
              "null"
            ]
          },
          "topicId": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "kind": {
            "enum": [
              "note",
              "flashcards",
              "quiz"
            ]
          },
          "visibility": {
            "enum": [
              "private",
              "public"
            ]
          },
          "content": {
            "$ref": "#/components/schemas/MaterialContent"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "cover": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "authorName": {
            "type": "string"
          },
          "itemCount": {
            "type": "integer"
          },
          "isOwner": {
            "type": "boolean"
          },
          "isSaved": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "slug",
          "ownerId",
          "subjectId",
          "topicId",
          "title",
          "description",
          "kind",
          "visibility",
          "content",
          "tags",
          "cover",
          "createdAt",
          "updatedAt",
          "authorName",
          "itemCount",
          "isOwner",
          "isSaved"
        ],
        "additionalProperties": false
      },
      "Subject": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "readOnly": true,
            "description": "Public-page slug, stable after the first public child; unpublished name edits may change it. subjectId references remain unchanged."
          },
          "ownerId": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "color": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "materialCount": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "slug",
          "ownerId",
          "name",
          "description",
          "color",
          "createdAt",
          "materialCount"
        ],
        "additionalProperties": false
      },
      "Topic": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "readOnly": true,
            "description": "Public-page slug, stable after the first public child; unpublished title edits may change it. topicId references remain unchanged."
          },
          "ownerId": {
            "type": [
              "string",
              "null"
            ]
          },
          "subjectId": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "materialCount": {
            "type": "integer"
          },
          "isOwner": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "slug",
          "ownerId",
          "subjectId",
          "title",
          "description",
          "createdAt",
          "updatedAt",
          "materialCount",
          "isOwner"
        ],
        "additionalProperties": false
      },
      "Progress": {
        "type": "object",
        "properties": {
          "materialId": {
            "type": "string"
          },
          "known": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "starred": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "current": {
            "type": "integer"
          },
          "completed": {
            "type": "boolean"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "materialId",
          "known",
          "starred",
          "current",
          "completed"
        ],
        "additionalProperties": false
      },
      "AttemptQuestion": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "kind": {
            "enum": [
              "concept",
              "calculation"
            ]
          },
          "source": {
            "type": "string"
          },
          "answer": {
            "type": "integer",
            "description": "Present after submission, or for this checked/locked question in opt-in instant mode; zero-based option index."
          },
          "explanation": {
            "type": "string",
            "description": "Present after submission or for a checked/locked instant-mode question."
          }
        },
        "required": [
          "id",
          "prompt",
          "options",
          "kind"
        ],
        "additionalProperties": false
      },
      "Attempt": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "materialId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "enum": [
              "in_progress",
              "submitted"
            ]
          },
          "answers": {
            "type": "object",
            "additionalProperties": {
              "type": "integer",
              "minimum": 0,
              "maximum": 5
            }
          },
          "flags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "current": {
            "type": "integer"
          },
          "elapsedMs": {
            "type": "integer",
            "minimum": 0,
            "maximum": 31536000000,
            "description": "Browser-reported cumulative active milliseconds, monotonic on save, never used for grading. Legacy attempts start at zero."
          },
          "feedbackMode": {
            "enum": [
              "after-submit",
              "instant"
            ]
          },
          "revealed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Checked instant-mode question IDs; their recorded answers are locked."
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          },
          "submittedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Correct answer count, not percentage. Null until submitted."
          },
          "total": {
            "type": "integer"
          },
          "questions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AttemptQuestion"
            }
          },
          "conventions": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "materialId",
          "title",
          "status",
          "answers",
          "flags",
          "current",
          "elapsedMs",
          "feedbackMode",
          "revealed",
          "startedAt",
          "submittedAt",
          "score",
          "total",
          "questions"
        ],
        "additionalProperties": false
      },
      "Activity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "score": {
            "type": [
              "integer",
              "null"
            ]
          },
          "total": {
            "type": "integer"
          },
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "materialId": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "title",
          "kind",
          "score",
          "total",
          "at",
          "materialId"
        ],
        "additionalProperties": false
      },
      "Key": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "enum": [
                "materials:read",
                "materials:write"
              ]
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name",
          "prefix",
          "scopes",
          "createdAt",
          "lastUsedAt",
          "expiresAt"
        ],
        "additionalProperties": false
      },
      "Stats": {
        "type": "object",
        "properties": {
          "materials": {
            "type": "integer"
          },
          "subjects": {
            "type": "integer"
          },
          "cardsKnown": {
            "type": "integer"
          },
          "completedExams": {
            "type": "integer"
          },
          "averageScore": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean submitted attempt percentage (0–100)."
          },
          "activity": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Activity"
            }
          }
        },
        "required": [
          "materials",
          "subjects",
          "cardsKnown",
          "completedExams",
          "averageScore",
          "activity"
        ],
        "additionalProperties": false
      },
      "MaterialContent": {
        "type": "object",
        "properties": {
          "markdown": {
            "type": "string",
            "maxLength": 200000
          },
          "richText": {
            "$ref": "#/components/schemas/RichTextDocument"
          },
          "cards": {
            "maxItems": 1000,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[a-zA-Z0-9_-]+$"
                },
                "front": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 10000
                },
                "back": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 20000
                },
                "explanation": {
                  "type": "string",
                  "maxLength": 20000
                },
                "source": {
                  "type": "string",
                  "maxLength": 500
                }
              },
              "required": [
                "id",
                "front",
                "back"
              ],
              "additionalProperties": false
            }
          },
          "questions": {
            "maxItems": 500,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[a-zA-Z0-9_-]+$"
                },
                "prompt": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 10000
                },
                "options": {
                  "minItems": 2,
                  "maxItems": 6,
                  "type": "array",
                  "items": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 5000
                  }
                },
                "answer": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 5
                },
                "explanation": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 30000
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "concept",
                    "calculation"
                  ]
                },
                "source": {
                  "type": "string",
                  "maxLength": 500
                }
              },
              "required": [
                "id",
                "prompt",
                "options",
                "kind"
              ],
              "additionalProperties": false
            }
          },
          "conventions": {
            "type": "string",
            "maxLength": 10000
          }
        },
        "additionalProperties": false
      }
    }
  }
}
