{"openapi":"3.1.0","info":{"title":"NZQA Resources API","description":"\n## NZQA Resources API v5\n\nEnterprise-grade REST API for accessing New Zealand Qualifications Authority (NZQA) educational resources, standards, and attainment data.\n\n### Overview\n\nThe NZQA Resources API provides programmatic access to:\n- **Standards**: Complete NZQA achievement standards with descriptions, credits, and metadata\n- **Files**: Examination papers, assessment schedules, exemplars, and other educational resources with CDN links\n- **Subjects**: Subject catalogs with levels, years, and keyword associations\n- **Attainment Data**: Historical achievement statistics and trends\n- **Search**: Advanced full-text search with intelligent relevance ranking\n- **MCP**: Streamable HTTP at `/mcp` (and `/mcp/`) for Claude.ai / Cursor — Typesense tutoring tools; see `docs/MCP.md`\n- **Account Management**: User account registration, authentication, and profile management\n- **File Downloads**: Direct CDN links for fast and secure file access (CDN-based)\n\n### Authentication\n\nThe API uses tier-based authentication:\n- **Public Tier**: Limited rate limits, basic access\n- **Authenticated Tier**: Higher rate limits, full feature access\n- **Admin Tier**: Administrative endpoints and analytics\n\n**Authentication Methods:**\n- **API Keys**: Use an API key via the `X-API-Key` header (supports IP whitelisting and rotation)\n- **Sessions**: Create a session via `POST /v5/auth/session` (supports refresh token rotation)\n- **User Accounts**: Register and login via `POST /v5/accounts/register` and `POST /v5/accounts/login`\n\n**Security Features:**\n- **IP Whitelisting**: Restrict API key usage to specific IP addresses or CIDR ranges\n- **API Key Rotation**: Rotate API keys with configurable grace periods\n- **Session Refresh Token Rotation**: Automatic token rotation on refresh for enhanced security\n- **Account Lockout**: Automatic account lockout after failed login attempts\n\n**Authentication Headers:**\n\nUse one of the following authentication methods:\n\n```http\nX-API-Key: sk_live_xxx\n```\n\nor\n\n```http\nAuthorization: Bearer xxx\n```\n\nThe `X-API-Key` header is preferred for API key authentication. The `Authorization: Bearer` header can be used with session tokens or API keys.\n\n### Rate Limiting\n\nRate limits are enforced per authentication tier. Check your current limits via `GET /v5/info/rate-limit-info`.\n\n### Response Format\n\nAll endpoints return standardized JSON responses with:\n- `status`: \"success\" or \"error\"\n- `status_code`: HTTP status code\n- `message`: Human-readable message\n- `data`: Response payload\n- `timestamp`: ISO 8601 timestamp\n- `execution_time_ms`: Request processing time\n\n**Pagination Metadata:**\n\nPaginated endpoints include pagination information in the `data` object. The exact field names vary by endpoint:\n- **Files endpoint**: `total_files` - Total number of files matching the query\n- **Standards endpoint**: `total_standards` - Total number of standards matching the query\n- **Search endpoint**: `total_results` - Total number of search results\n- **Subjects endpoint**: `total_subjects` - Total number of subjects\n\nTo determine if more results are available, compare the returned count with the total:\n- `has_more = (offset + returned_count) < total_count`\n\n**Example Paginated Response (Files):**\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Retrieved 50 files\",\n  \"api_version\": config.api.version,\n  \"data\": {\n    \"files\": [...],\n    \"total_files\": 245,\n    \"filters_applied\": {\n      \"subject\": [\"Physics\"],\n      \"level\": [3]\n    },\n    \"sort_by\": \"name\",\n    \"sort_order\": \"asc\"\n  },\n  \"cached\": false,\n  \"timestamp\": \"2025-12-21T12:00:00.000000+00:00\",\n  \"execution_time_ms\": 45.2,\n  \"request_id\": \"abc123-def456-ghi789\"\n}\n```\n\n**Pagination Calculation:**\n\nFor the above example with `limit=50` and `offset=0`:\n- Total available: 245 files\n- Returned in this page: 50 files\n- Has more: `(0 + 50) < 245` = `true`\n- Next page: Use `offset=50&limit=50` to get files 51-100\n\n### Error Handling\n\nErrors follow a consistent format:\n- `400 Bad Request`: Invalid parameters or validation errors\n- `401 Unauthorized`: Missing or invalid authentication\n- `404 Not Found`: Resource not found\n- `429 Too Many Requests`: Rate limit exceeded\n- `500 Internal Server Error`: Server-side errors\n\n**Error Response Format:**\n\nAll error responses follow this structure:\n\n```json\n{\n  \"status\": \"error\",\n  \"status_code\": 400,\n  \"message\": \"Invalid year format. Year must be a 4-digit number between 2000 and 2030.\",\n  \"data\": null,\n  \"timestamp\": \"2025-12-21T12:00:00.000000+00:00\",\n  \"execution_time_ms\": 2.1\n}\n```\n\n**Example Error Responses:**\n\n**400 Bad Request (Validation Error):**\n```json\n{\n  \"status\": \"error\",\n  \"status_code\": 400,\n  \"message\": \"Invalid year format. Year must be a 4-digit number between 2000 and 2030.\",\n  \"data\": null,\n  \"timestamp\": \"2025-12-21T12:00:00.000000+00:00\",\n  \"execution_time_ms\": 1.5\n}\n```\n\n**401 Unauthorized (Authentication Error):**\n```json\n{\n  \"status\": \"error\",\n  \"status_code\": 401,\n  \"message\": \"Authentication required. Please provide a valid API key or session token.\",\n  \"data\": null,\n  \"timestamp\": \"2025-12-21T12:00:00.000000+00:00\",\n  \"execution_time_ms\": 0.8\n}\n```\n\n**404 Not Found:**\n```json\n{\n  \"status\": \"error\",\n  \"status_code\": 404,\n  \"message\": \"Standard not found\",\n  \"data\": null,\n  \"timestamp\": \"2025-12-21T12:00:00.000000+00:00\",\n  \"execution_time_ms\": 3.2\n}\n```\n\n**429 Too Many Requests:**\n```json\n{\n  \"status\": \"error\",\n  \"status_code\": 429,\n  \"message\": \"Rate limit exceeded. Please try again later.\",\n  \"data\": null,\n  \"timestamp\": \"2025-12-21T12:00:00.000000+00:00\",\n  \"execution_time_ms\": 0.5\n}\n```\n\n### Examples\n\n**Search for Physics resources:**\n```\nGET /v5/search?q=physics&subject=Physics&level=3&limit=10\n```\n\n**Get standard details:**\n```\nGET /v5/standards/91524\n```\n\n**List files with filters:**\n```\nGET /v5/files?subject=Mathematics&level=2&file_type=Exam&year=2023&limit=50\n```\n\n**Get attainment data:**\n```\nGET /v5/attainment?subject=Physics&level=3&year=2023\n```\n\n**Register a new account:**\n```\nPOST /v5/accounts/register\nContent-Type: application/json\n{\n  \"email\": \"user@example.com\",\n  \"username\": \"username\",\n  \"password\": \"secure_password\",\n  \"tier\": \"session\"\n}\n```\n\n**Rotate an API key:**\n\n```\nPOST /admin/keys/{key_id}/rotate\n{\n  \"grace_period_days\": 7\n}\n```\n\n### New Features\n\n**User Account Management:**\n- Register and manage user accounts\n- Email verification\n- Account lockout protection\n- Profile management\n\n**Enhanced Security:**\n- IP whitelisting for API keys\n- API key rotation with grace periods\n- Session refresh token rotation\n- Account lockout after failed login attempts\n\n**File Downloads:**\n- Direct CDN links for fast and secure file access (CDN-based)\n- File integrity verification with checksums (MD5, SHA256)\n\n\n**Monitoring & Analytics:**\n- Database connection pool monitoring\n- Enhanced error recovery suggestions\n- Query execution time breakdown\n- Comprehensive rate limit headers\n\n### Support\n\nFor API support, documentation, and updates, visit https://api-nzqa.toasting.me\n    ","version":"5.1.0.2"},"paths":{"/":{"get":{"tags":["Info"],"summary":"Root","description":"Root endpoint providing API information and navigation links.","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v5/health":{"get":{"tags":["Info"],"summary":"Health Check Alias","description":"Health check endpoint (aliased from /v5/info/health)","operationId":"health_check_alias_v5_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v5/info":{"get":{"tags":["Info"],"summary":"Root","description":"Root endpoint providing API information and navigation links.\n\nReturns basic API metadata including version, status, and links to documentation endpoints.\nThis endpoint does not require authentication and can be used to verify API availability.\n\n## Response Data\n\n- `message`: API identification message\n- `version`: Current API version (e.g., \"5.1.0.2\")\n- `status`: Current operational status (\"operational\")\n- `docs`: URL path to Swagger UI documentation (\"/docs\")\n- `redoc`: URL path to ReDoc documentation (\"/redoc\")\n- `health`: URL path to health check endpoint (\"/v5/info/health\")\n\n## Example Request\n\n```\nGET /v5/info\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Welcome to NZQA Resources API v5\",\n  \"data\": {\n    \"message\": \"NZQA Resources API v5\",\n    \"version\": \"5.1.0.2\",\n    \"status\": \"operational\",\n    \"docs\": \"/docs\",\n    \"redoc\": \"/redoc\",\n    \"health\": \"/v5/info/health\"\n  }\n}\n```","operationId":"root_v5_info_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}}}}},"/v5/info/health":{"get":{"tags":["Info"],"summary":"Health Check","description":"Health check endpoint for monitoring API availability and component status.\n\nPerforms comprehensive checks on critical system components including:\n- Database connectivity (all pools: nzqa, security, analytics)\n- Actual database queries to verify functionality\n- Cache availability and functionality\n- Connection pool health\n\nReturns detailed health status for each component with specific error messages.\n\n## Health Status Values\n\n- `healthy`: All components operational\n- `degraded`: Some components unavailable but API is functional\n\n## Response Fields\n\n- `status`: Overall health status (\"healthy\" or \"degraded\")\n- `timestamp`: ISO 8601 timestamp of health check\n- `version`: API version\n- `database`: Boolean indicating database connectivity (all pools)\n- `cache`: Boolean indicating cache availability\n- `uptime_seconds`: Server uptime in seconds\n- `details`: Optional dict with detailed component status (if degraded)\n\n## Example Request\n\n```\nGET /v5/info/health\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"System healthy\",\n  \"data\": {\n    \"status\": \"healthy\",\n    \"timestamp\": \"2025-12-21T11:00:00.000000+00:00\",\n    \"version\": \"5.1.0.2\",\n    \"database\": true,\n    \"cache\": true,\n    \"uptime_seconds\": 86400.5\n  },\n  \"meta\": {\n    \"api_version\": \"5.1.0.2\",\n    \"request_id\": \"...\",\n    \"cached\": false,\n    \"execution_time_ms\": 5.2,\n    \"timestamp\": \"2025-12-21T11:00:00+00:00\"\n  }\n}\n```\n\n## Use Cases\n\n- Monitoring and alerting systems\n- Load balancer health checks\n- Service availability verification\n- System diagnostics","operationId":"health_check_v5_info_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}}}}},"/v5/info/stats":{"get":{"tags":["Info"],"summary":"Get Stats","description":"Retrieve comprehensive API statistics and resource counts.\n\nReturns aggregated statistics about the API's data resources including total counts\nof files, standards, subjects, and keywords. Also includes analytics overview data\nand cache statistics. Results are cached for 5 minutes to improve performance.\n\n## Response Data\n\n- `total_files`: Total number of files in the database\n- `total_standards`: Total number of NZQA standards\n- `total_subjects`: Total number of distinct subjects\n- `total_keywords`: Total number of indexed keywords\n- `analytics_overview`: Usage analytics and resource popularity statistics\n- `cache_stats`: Current cache statistics (hits, misses, size)\n\n## Example Request\n\n```\nGET /v5/info/stats\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Statistics retrieved successfully\",\n  \"data\": {\n    \"total_files\": 12543,\n    \"total_standards\": 2847,\n    \"total_subjects\": 45,\n    \"total_keywords\": 89234,\n    \"analytics_overview\": {\n      \"popular_subjects\": [...],\n      \"popular_standards\": [...]\n    },\n    \"cache_stats\": {\n      \"hits\": 1234,\n      \"misses\": 567,\n      \"size\": 45.2\n    }\n  }\n}\n```\n\n## Performance\n\nResults are cached for 5 minutes. Subsequent requests within the cache window\nwill return cached data with improved response times.","operationId":"get_stats_v5_info_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_StatsResponse_"}}}}}}},"/v5/info/metadata":{"get":{"tags":["Info"],"summary":"Get Metadata","description":"Retrieve API metadata, configuration, and system information.\n\nReturns detailed metadata about the API including resource counts, version information,\ncache statistics, and database information. Useful for understanding API capabilities\nand current system state.\n\n## Response Data\n\n- `total_files`: Total number of files available\n- `total_standards`: Total number of standards\n- `total_subjects`: Total number of distinct subjects\n- `total_keywords`: Total number of indexed keywords\n- `api_version`: Current API version\n- `cache_stats`: Cache performance statistics\n- `last_updated`: Last database update timestamp (if available)\n- `database_size_mb`: Database size in megabytes (if available)\n\n## Example Request\n\n```\nGET /v5/info/metadata\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Metadata retrieved successfully\",\n  \"data\": {\n    \"total_files\": 12543,\n    \"total_standards\": 2847,\n    \"total_subjects\": 45,\n    \"total_keywords\": 89234,\n    \"api_version\": \"5.1.0.2\",\n    \"cache_stats\": {\n      \"hits\": 1234,\n      \"misses\": 567\n    }\n  }\n}\n```","operationId":"get_metadata_v5_info_metadata_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_MetadataResponse_"}}}}}}},"/v5/info/rate-limit-info":{"get":{"tags":["Info"],"summary":"Get Rate Limit Info Endpoint","description":"Retrieve rate limit information for the current authenticated user or session.\n\nReturns detailed information about the current rate limit tier, remaining requests,\nreset time, and limit configuration. Useful for implementing client-side rate limit\nmanagement and understanding current API access capabilities.\n\n## Response Data\n\n- `tier`: Current authentication tier (\"public\", \"authenticated\", \"admin\")\n- `limit`: Maximum requests per time window (null for unlimited)\n- `remaining`: Remaining requests in current window (null if unlimited)\n- `reset_time`: ISO 8601 timestamp when rate limit resets (null if unlimited)\n- `cached`: Whether this request was served from cache\n\n## Example Request\n\n```\nGET /v5/info/rate-limit-info\nAuthorization: Bearer YOUR_API_KEY\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Rate limit information retrieved successfully\",\n  \"data\": {\n    \"tier\": \"authenticated\",\n    \"limit\": 1000,\n    \"remaining\": 847,\n    \"reset_time\": \"2025-12-21T12:00:00.000000+00:00\",\n    \"cached\": false\n  }\n}\n```\n\n## Rate Limit Tiers\n\n- **Public**: Limited rate limits for unauthenticated requests\n- **Authenticated**: Higher rate limits for API key or session-based authentication\n- **Admin**: Unlimited or very high rate limits for administrative access\n\n## Notes\n\n- This endpoint does not count toward rate limits\n- Reset times are provided in UTC\n- Null values indicate unlimited access for that tier","operationId":"get_rate_limit_info_endpoint_v5_info_rate_limit_info_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}}}}},"/v5/info/cache-stats":{"get":{"tags":["Info"],"summary":"Get Cache Stats","description":"Retrieve cache performance and usage statistics.\n\nReturns detailed information about the API's caching system including hit/miss ratios,\ncache size, and performance metrics. Useful for monitoring cache effectiveness and\nsystem performance optimization.\n\n## Response Data\n\n- `hits`: Number of cache hits\n- `misses`: Number of cache misses\n- `size`: Current cache size (if available)\n- `hit_rate`: Cache hit rate percentage (if calculated)\n\n## Example Request\n\n```\nGET /v5/info/cache-stats\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Cache statistics retrieved successfully\",\n  \"data\": {\n    \"hits\": 12345,\n    \"misses\": 2345,\n    \"size\": 45.2,\n    \"hit_rate\": 84.0\n  }\n}\n```\n\n## Use Cases\n\n- Performance monitoring\n- Cache optimization analysis\n- System health diagnostics\n- Capacity planning","operationId":"get_cache_stats_v5_info_cache_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}}}}},"/v5/search":{"get":{"tags":["Search"],"summary":"Search Resources","description":"Advanced full-text search across files, standards, subjects, and keywords with intelligent relevance ranking.\n\nThis endpoint provides comprehensive search capabilities with support for complex query operators, \nmultiple filters, and intelligent ranking using SmartRanker technology.\n\n## Query Parameter (q)\n\nThe search query supports advanced operators:\n\n- `subject:Physics` or `s:Physics` - Filter results by subject name\n- `level:3` or `l3` or `l 3` - Filter by NCEA level (1, 2, or 3)\n- `year:2023` or `y:2023` - Filter by year\n- `type:Exam` or `t:Exam` - Filter by file type\n- `standard:91524` or `std:91524` - Filter by standard number\n- `d:mechanical` or `desc:mechanical` - Search in standard descriptions only\n- `-exam` - Exclude results containing the term \"exam\"\n- `\"exact phrase\"` - Match exact phrase (use double quotes)\n- `phy*` - Wildcard/prefix search\n\n## Filter Parameters\n\nFilters can be applied via query parameters or query operators. When both are provided, \nthey are combined with AND logic.\n\n## Examples\n\n**Basic search:**\n```\nGET /v5/search?q=physics\n```\n\n**Search with filters:**\n```\nGET /v5/search?q=exam&subject=Physics&level=3&year=2023\n```\n\n**Advanced query with operators:**\n```\nGET /v5/search?q=physics exam d:mechanical l3 2022\n```\n\n**Filter-only search (no query term):**\n```\nGET /v5/search?subject=Mathematics&level=2&file_type=Exam&limit=50\n```\n\n**Exclude certain terms:**\n```\nGET /v5/search?q=physics -report -schedule\n```\n\n**Exact phrase search:**\n```\nGET /v5/search?q=\"demonstrate understanding of mechanical systems\"\n```\n\n**Wildcard prefix search:**\n```\nGET /v5/search?q=phy* level 2\n```\n\n**Description-only search:**\n```\nGET /v5/search?q=d:differentiation\n```\n\n**Sort by hardest (lowest overall pass rate first):**\n```\nGET /v5/search?q=accounting%20exams%20that%20are%20level%203%20and%20sorted%20by%20hardest\nGET /v5/search?q=accounting%20exams&level=3&sort_by=hardest\n```\n\n## Response Structure\n\nReturns a paginated list of search results with:\n- Relevance scores and ranking information\n- File metadata (name, type, size, path, year)\n- Standard information (number, description, credits, level)\n- Subject and level associations\n- Filter metadata and query information\n- `highlights`: field -> matched tokens for UI wrapping (subject, year, level, file_type, etc.)\n- `snippets`: optional Typesense excerpts with `<mark>` tags (sanitize before render)\n\nResults are ranked by relevance using multi-factor scoring:\n- Term frequency matching\n- Exact field matches\n- Recency (year-based)\n- File type specificity\n- Keyword density\n\n`subject` on results is the browse folder (Physics), not NZQA Field.\nDuplicate copies of the same exam under bilingual/Generic folders are\ncollapsed to one hit. Māori Exam / Maori Resource (`mex` / `mre`) are\nomitted; Te Reo Māori subject folders are not.\n\nOptional Typesense path (same parse / sort / highlights): `GET /v5/typesense`.\nSee `docs/TYPESENSE.md`.\n\n## Example Response\n\n```\nGET /v5/search?q=physics&limit=1\n```\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Found 1 results for 'physics' (showing 1 of 50)\",\n  \"data\": {\n    \"results\": [\n      {\n        \"file\": {\n          \"id\": 29163,\n          \"file_name\": \"92046-spc-2025.pdf\",\n          \"file_path\": \"physics/level-01/92046-spc-2025.pdf\",\n          \"file_type\": \"Assessment Specifications\",\n          \"cdn_url\": \"https://cdn.toasting.me/direct/92046-spc-2025.pdf\",\n          \"subject\": \"Physics\",\n          \"level\": 1,\n          \"year\": \"2025\",\n          \"standard_number\": \"92046\"\n        },\n        \"standard\": {\n          \"number\": \"92046\",\n          \"title\": \"External\",\n          \"subject\": \"Physics\",\n          \"level\": 1,\n          \"credits\": 5,\n          \"status\": \"active\"\n        },\n        \"subject\": {\n          \"name\": \"Physics\",\n          \"canonical_name\": \"Physics\",\n          \"level\": 1\n        },\n        \"score\": 47.0,\n        \"match_type\": \"file\",\n        \"highlights\": {\n          \"subject\": [\"physics\"]\n        }\n      }\n    ]\n  }\n}\n```\n\n## Performance\n\n- Results are cached for improved performance\n- Supports pagination via `limit` and `offset` parameters\n- Field selection available via `fields` parameter to reduce response size\n- Maximum 500 results per request\n\n## Error Handling\n\n- Returns 400 if neither query nor filters are provided\n- Returns 401 if authentication is required but not provided\n- Returns 429 if rate limit is exceeded\n- Returns 500 for server errors (with generic message for security)","operationId":"search_resources_v5_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"description":"Search query string with advanced operators. Supports operators: subject:Physics, level:3, d:description, -exclude, \"exact phrase\", wildcard*. Optional if at least one filter parameter is provided.","title":"Q","type":"string"},"description":"Search query string with advanced operators. Supports operators: subject:Physics, level:3, d:description, -exclude, \"exact phrase\", wildcard*. Optional if at least one filter parameter is provided.","example":"physics exam d:mechanical l3"},{"name":"subject","in":"query","required":false,"schema":{"description":"Filter results by subject name. Case-insensitive. Supports exact match ('Physics'), hyphen-prefix match ('Accounting' matches 'Accounting - Generic'), and word-prefix match ('English' matches 'English Oral Language', 'English Written Language', etc.). Can be combined with other filters.","title":"Subject","type":"string"},"description":"Filter results by subject name. Case-insensitive. Supports exact match ('Physics'), hyphen-prefix match ('Accounting' matches 'Accounting - Generic'), and word-prefix match ('English' matches 'English Oral Language', 'English Written Language', etc.). Can be combined with other filters.","example":"Physics"},{"name":"level","in":"query","required":false,"schema":{"description":"Filter results by NCEA level. Valid values: 1, 2, or 3. Can be combined with other filters.","title":"Level","type":"integer","maximum":3,"minimum":1},"description":"Filter results by NCEA level. Valid values: 1, 2, or 3. Can be combined with other filters.","example":3},{"name":"year","in":"query","required":false,"schema":{"description":"Filter results by year. Must be 4-digit format (2000-2030). Examples: '2022', '2023', '2024'. Can be combined with other filters.","title":"Year","type":"string"},"description":"Filter results by year. Must be 4-digit format (2000-2030). Examples: '2022', '2023', '2024'. Can be combined with other filters.","example":"2022"},{"name":"file_type","in":"query","required":false,"schema":{"description":"Filter results by file type. Common values: 'Exam', 'Report', 'Answer Schedule', 'Exemplar'. Also accepts exam+answers phrases ('Exam + answers', 'Exam+Answer Schedule') and comma-separated lists. Matching is exact against the expanded concrete types (not substring). Māori Exam / Maori Resource are never returned.","title":"File Type","type":"string"},"description":"Filter results by file type. Common values: 'Exam', 'Report', 'Answer Schedule', 'Exemplar'. Also accepts exam+answers phrases ('Exam + answers', 'Exam+Answer Schedule') and comma-separated lists. Matching is exact against the expanded concrete types (not substring). Māori Exam / Maori Resource are never returned.","example":"Exam + answers"},{"name":"standard_number","in":"query","required":false,"schema":{"description":"Filter results by standard number. Must be exactly 5 digits. Examples: '91524', '91525'. Can be combined with other filters.","title":"Standard Number","type":"string"},"description":"Filter results by standard number. Must be exactly 5 digits. Examples: '91524', '91525'. Can be combined with other filters.","example":"91524"},{"name":"sort_by","in":"query","required":false,"schema":{"description":"Sort order for results. Valid values: 'relevance' (default, uses SmartRanker scoring), 'date' (by file date), 'name' (alphabetical), 'level' (by NCEA level), 'hardest' (lowest overall pass rate first), 'easiest' (highest overall pass rate first). Queries like 'sorted by hardest' also set this.","default":"relevance","title":"Sort By","type":"string","pattern":"^(relevance|date|name|level|hardest|easiest)$"},"description":"Sort order for results. Valid values: 'relevance' (default, uses SmartRanker scoring), 'date' (by file date), 'name' (alphabetical), 'level' (by NCEA level), 'hardest' (lowest overall pass rate first), 'easiest' (highest overall pass rate first). Queries like 'sorted by hardest' also set this.","example":"relevance"},{"name":"normalize_subjects","in":"query","required":false,"schema":{"description":"Normalize subject names to canonical forms. When true, variant names (e.g., 'Mahi Kaute (Accounting)') are mapped to canonical names (e.g., 'Accounting') for consistent filtering and display.","default":false,"title":"Normalize Subjects","type":"boolean"},"description":"Normalize subject names to canonical forms. When true, variant names (e.g., 'Mahi Kaute (Accounting)') are mapped to canonical names (e.g., 'Accounting') for consistent filtering and display."},{"name":"limit","in":"query","required":false,"schema":{"description":"Maximum number of results to return per page. Valid range: 1-500. Default: 50. Use with offset for pagination.","default":50,"title":"Limit","type":"integer","maximum":500,"minimum":1},"description":"Maximum number of results to return per page. Valid range: 1-500. Default: 50. Use with offset for pagination.","example":50},{"name":"offset","in":"query","required":false,"schema":{"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=50 with limit=50 returns results 51-100.","default":0,"title":"Offset","type":"integer","minimum":0},"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=50 with limit=50 returns results 51-100.","example":0},{"name":"fields","in":"query","required":false,"schema":{"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'id,file_name,file_path' returns only those fields. If omitted, all fields are returned.","title":"Fields","type":"string"},"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'id,file_name,file_path' returns only those fields. If omitted, all fields are returned.","example":"id,file_name,file_path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/search/track-click":{"post":{"tags":["Search"],"summary":"Track Search Click","description":"Track when a user clicks on a search result.\n\nRecords click events to understand user behavior and improve search ranking.\nThis data enables:\n- Click-through rate analysis (which results users actually click)\n- Position-based ranking validation (are top results actually useful?)\n- Score correlation analysis (do high-scoring results get clicked more?)\n- Search → action conversion tracking\n\nThe tracking is non-blocking - if analytics tracking fails, the request still succeeds.\nThis ensures user experience is never impacted by analytics issues.\n\n**Usage:**\nCall this endpoint when a user clicks on a search result in your frontend.\nThe data collected helps tune the SmartRanker scoring algorithm with real usage patterns.","operationId":"track_search_click_v5_search_track_click_post","parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string","description":"Original search query string that produced the results. Used to correlate clicks with search patterns.","title":"Query"},"description":"Original search query string that produced the results. Used to correlate clicks with search patterns.","example":"physics level 3 exam"},{"name":"file_id","in":"query","required":true,"schema":{"type":"integer","description":"ID of the clicked file. Must be a valid file ID from the search results.","title":"File Id"},"description":"ID of the clicked file. Must be a valid file ID from the search results.","example":7588},{"name":"file_name","in":"query","required":true,"schema":{"type":"string","description":"Name of the clicked file. Used for analytics and debugging.","title":"File Name"},"description":"Name of the clicked file. Used for analytics and debugging.","example":"91524-exm-2022.pdf"},{"name":"position","in":"query","required":true,"schema":{"type":"integer","minimum":1,"description":"Position of the clicked result in the search results (1-based). Position 1 is the first/top result.","title":"Position"},"description":"Position of the clicked result in the search results (1-based). Position 1 is the first/top result.","example":1},{"name":"score","in":"query","required":false,"schema":{"description":"Relevance score of the clicked result. Optional but recommended for ranking analysis.","title":"Score","type":"number"},"description":"Relevance score of the clicked result. Optional but recommended for ranking analysis.","example":945.82}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/search/autocomplete":{"get":{"tags":["Search"],"summary":"Get Autocomplete Suggestions","description":"Retrieve autocomplete suggestions for search queries with intelligent keyword matching.\n\nReturns a list of suggested keywords, terms, or phrases that match the provided\nquery prefix. Suggestions are based on indexed keywords in the database and are\nranked by relevance. Supports optional filtering by subject for context-aware\nsuggestions. Results are cached for 1 hour to improve performance.\n\n## Query Parameter\n\n- **q**: Search query prefix (required, 1-100 characters)\n  - Provides the prefix for matching suggestions\n  - Case-insensitive matching\n  - Supports partial word matching\n\n## Filter Parameters\n\n- **subject**: Optional subject filter for context-aware suggestions\n  - Limits suggestions to keywords associated with the specified subject\n  - Case-insensitive matching\n  - Improves relevance for subject-specific searches\n\n- **limit**: Maximum number of suggestions to return (1-50, default: 10)\n\n## Examples\n\n**Basic autocomplete:**\n```\nGET /v5/search/autocomplete?q=phy\n```\n\n**With subject filter:**\n```\nGET /v5/search/autocomplete?q=mech&subject=Physics&limit=15\n```\n\n**Get more suggestions:**\n```\nGET /v5/search/autocomplete?q=math&limit=25\n```\n\n## Response Structure\n\nReturns an autocomplete response object containing:\n- `suggestions`: Array of suggested strings, ranked by relevance\n- `query`: The original query prefix\n- `total_suggestions`: Total number of suggestions returned\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Found 10 suggestions\",\n  \"data\": {\n    \"suggestions\": [\n      \"Ahupungao (Physics)\",\n      \"Physical Education\",\n      \"Physics\",\n      \"Physics Ahupungao\"\n    ],\n    \"query\": \"phy\",\n    \"total_suggestions\": 10\n  }\n}\n```\n\n## Performance\n\n- Results are cached for 1 hour\n- Uses FTS5 prefix search for fast matching\n- Supports efficient subject filtering\n- Optimized for real-time autocomplete use cases\n\n## Use Cases\n\n- Search box autocomplete functionality\n- Query suggestion features\n- Keyword discovery\n- Improving search UX with intelligent suggestions","operationId":"get_autocomplete_suggestions_v5_search_autocomplete_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":100,"description":"Search query prefix for autocomplete matching. Minimum 1 character, maximum 100 characters. Case-insensitive prefix matching against indexed keywords. Examples: 'phy' matches 'physics', 'physical', 'physical education'.","title":"Q"},"description":"Search query prefix for autocomplete matching. Minimum 1 character, maximum 100 characters. Case-insensitive prefix matching against indexed keywords. Examples: 'phy' matches 'physics', 'physical', 'physical education'.","example":"phy"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Maximum number of autocomplete suggestions to return. Valid range: 1-50. Default: 10. Suggestions are ranked by relevance.","default":10,"title":"Limit"},"description":"Maximum number of autocomplete suggestions to return. Valid range: 1-50. Default: 10. Suggestions are ranked by relevance.","example":10},{"name":"subject","in":"query","required":false,"schema":{"description":"Optional subject filter for context-aware suggestions. Limits suggestions to keywords associated with the specified subject. Case-insensitive matching. Examples: 'Physics', 'Mathematics'. Improves relevance for subject-specific autocomplete.","title":"Subject","type":"string"},"description":"Optional subject filter for context-aware suggestions. Limits suggestions to keywords associated with the specified subject. Case-insensitive matching. Examples: 'Physics', 'Mathematics'. Improves relevance for subject-specific autocomplete.","example":"Physics"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/core__response__StandardResponse_AutocompleteResponse___1"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/typesense/health":{"get":{"tags":["Typesense"],"summary":"Typesense Health","description":"Typesense backend health check (optional component).\n\nReports whether Typesense is configured, reachable, and indexed. Typesense\nbeing down does **not** take primary `/v5/search` offline — that path uses\nSQLite FTS5. Use this endpoint (and `/v5/info/health` details) before\npipeline alias-flip promote.\n\n## Query Parameters\n\n- **deep**: Include collection/alias document counts (default true)\n\n## Example Request\n\n```\nGET /v5/typesense/health\n```\n\n## Example Response (healthy)\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Typesense healthy\",\n  \"data\": {\n    \"status\": \"healthy\",\n    \"healthy\": true,\n    \"configured\": true,\n    \"search_collection\": \"nzqa_files_v5\",\n    \"details\": {\n      \"health_endpoint\": {\"ok\": true},\n      \"collection\": {\"name\": \"nzqa_files_v5\", \"num_documents\": 12000, \"exists\": true}\n    }\n  }\n}\n```\n\n## Status Values\n\n- `healthy` — configured, `/health` ok, collection has documents\n- `degraded` — reachable but empty/missing collection (restore needed)\n- `unhealthy` — configured but unreachable\n- `disabled` — env not set (expected; FTS-only mode)\n- `unavailable` — httpx/client missing\n\nSee `docs/TYPESENSE.md` for restore and alias-flip steps.","operationId":"typesense_health_v5_typesense_health_get","parameters":[{"name":"deep","in":"query","required":false,"schema":{"type":"boolean","description":"When true, also verify the search collection/alias exists and has documents. Default: true.","default":true,"title":"Deep"},"description":"When true, also verify the search collection/alias exists and has documents. Default: true.","example":true}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/typesense":{"get":{"tags":["Typesense"],"summary":"Search Typesense","description":"Search files through Typesense (optional backend). Primary `/v5/search` stays FTS5.\n\nNatural language is parsed the same way as `/v5/search`: filler words are\ndropped, then subject / level / year / file type / standard are classified\nas filters. Leftover topic words (e.g. mechanics) are the Typesense `q`.\nFive-digit standard numbers stay in `q` (not `q=*`) so the matching PDF\nranks first. Level is matched as an integer (Typesense `level` is int32).\n\nNicknames in `q`: `maths`/`math` → Mathematics, `chem` → Chemistry,\n`bio` → Biology.\n\n`sort_by=hardest` / `easiest` (or phrases like \"sorted by hardest\") order\nresults by overall pass rate: `AVG(achieved_rate)` across all years for\nthat standard. Lowest pass rate is hardest. Those hits include\n`overall_pass_rate`.\n\nEach result includes `highlights` (field → matched tokens) so the UI can\nwrap subject, year, level, file type, file name, and description.\nFilter-only queries still get highlights from the applied filters.\nTypesense text hits may also include `snippets` with `<mark>` tags —\nsanitize before rendering.\n\n`subject` on results is the **browse folder** (Physics), not NZQA Field /\n`primary_subject` (Sciences). Duplicate copies of the same exam under\nbilingual or Generic folders are collapsed to one hit.\n\nMāori Exam and Maori Resource (`mex` / `mre`) are omitted. Te Reo Māori\n**subject** folders are not blocked. See `docs/TYPESENSE.md`.\n\n## Query Parameter (q)\n\n- `subject:Physics` or `s:Physics` — filter by folder subject\n- `level:3` or `l3` or `l 3` — NCEA level\n- `year:2023` or `y:2023` — year\n- `type:Exam` or `t:Exam` — file type\n- `exam answers` / `exam + answers` — Exam, Answer Schedule, and Exam+Answer Schedule\n- `standard:91524` or `std:91524` — standard number\n- `d:mechanical` — description terms\n- `-exam` — exclude\n- `\"exact phrase\"` — phrase match\n- leftover words — Typesense text query\n\n## Examples\n\n```\nGET /v5/typesense?q=physics\nGET /v5/typesense?q=physics%20level%203%20exam%20mechanics&limit=5\nGET /v5/typesense?q=show%20me%20l%203%20physics%20exams%20for%20like%202022&limit=5\nGET /v5/typesense?q=exam&subject=Physics&level=3&year=2022\nGET /v5/typesense?q=physics%20exam%20answers&level=3\nGET /v5/typesense?file_type=Exam%20%2B%20answers&subject=Physics&level=3\nGET /v5/typesense?q=91524\nGET /v5/typesense?q=accounting%20exams%20from%202024%20level%203\nGET /v5/typesense?q=accounting%20exams%20that%20are%20level%203%20and%20sorted%20by%20hardest\nGET /v5/typesense?q=accounting%20exams&level=3&sort_by=hardest\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"data\": {\n    \"results\": [\n      {\n        \"file\": {\n          \"file_name\": \"91524-exm-2022.pdf\",\n          \"file_type\": \"Exam\",\n          \"subject\": \"Physics\",\n          \"level\": 3,\n          \"year\": \"2022\",\n          \"standard_number\": \"91524\"\n        },\n        \"score\": 47.0,\n        \"match_type\": \"file\",\n        \"highlights\": {\n          \"subject\": [\"Physics\"],\n          \"file_type\": [\"Exam\"],\n          \"year\": [\"2022\"],\n          \"level\": [\"3\"]\n        },\n        \"snippets\": {\n          \"description\": \"Demonstrate understanding of <mark>mechanical</mark> systems\"\n        }\n      }\n    ],\n    \"total_results\": 1,\n    \"ranking_method\": \"typesense+smart_ranker\",\n    \"sort_by\": \"relevance\"\n  }\n}\n```","operationId":"search_typesense_v5_typesense_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"description":"Natural-language or operator query. Filler words are dropped; subject/level/year/file type/standard become filters; leftover topic words are the Typesense text query. Optional if at least one filter is provided. Max 500 characters.","title":"Q","type":"string"},"description":"Natural-language or operator query. Filler words are dropped; subject/level/year/file type/standard become filters; leftover topic words are the Typesense text query. Optional if at least one filter is provided. Max 500 characters.","example":"physics level 3 exam mechanics"},{"name":"subject","in":"query","required":false,"schema":{"description":"Filter by browse-folder subject (e.g. Physics). Related bilingual/generic folders are included; results are labelled with the main folder. Case-insensitive.","title":"Subject","type":"string"},"description":"Filter by browse-folder subject (e.g. Physics). Related bilingual/generic folders are included; results are labelled with the main folder. Case-insensitive.","example":"Physics"},{"name":"level","in":"query","required":false,"schema":{"description":"Filter by NCEA level. Valid values: 1, 2, or 3. Compared as integers (Typesense `level` is int32).","title":"Level","type":"integer","maximum":3,"minimum":1},"description":"Filter by NCEA level. Valid values: 1, 2, or 3. Compared as integers (Typesense `level` is int32).","example":3},{"name":"year","in":"query","required":false,"schema":{"description":"Filter by year. Must be 4-digit format (2000-2030).","title":"Year","type":"string"},"description":"Filter by year. Must be 4-digit format (2000-2030).","example":"2022"},{"name":"file_type","in":"query","required":false,"schema":{"description":"Filter by file type. Single values (Exam, Report, Answer Schedule, Exemplar), comma-separated lists (Exam,Answer Schedule), or exam+answers phrases ('Exam + answers', 'Exam+Answer Schedule') expand to the matching indexed types. Māori Exam / Maori Resource are never returned.","title":"File Type","type":"string"},"description":"Filter by file type. Single values (Exam, Report, Answer Schedule, Exemplar), comma-separated lists (Exam,Answer Schedule), or exam+answers phrases ('Exam + answers', 'Exam+Answer Schedule') expand to the matching indexed types. Māori Exam / Maori Resource are never returned.","example":"Exam + answers"},{"name":"standard_number","in":"query","required":false,"schema":{"description":"Filter by standard number. Must be exactly 5 digits. Also used as Typesense q so the matching PDF ranks first (not q=*).","title":"Standard Number","type":"string"},"description":"Filter by standard number. Must be exactly 5 digits. Also used as Typesense q so the matching PDF ranks first (not q=*).","example":"91524"},{"name":"sort_by","in":"query","required":false,"schema":{"description":"Sort order: relevance (SmartRanker), date, name, level, hardest (lowest overall pass rate first), easiest (highest overall pass rate first). Phrases like 'sorted by hardest' in q also set this unless sort_by is already set.","default":"relevance","title":"Sort By","type":"string","pattern":"^(relevance|date|name|level|hardest|easiest)$"},"description":"Sort order: relevance (SmartRanker), date, name, level, hardest (lowest overall pass rate first), easiest (highest overall pass rate first). Phrases like 'sorted by hardest' in q also set this unless sort_by is already set.","example":"relevance"},{"name":"normalize_subjects","in":"query","required":false,"schema":{"description":"Expand subject name variants when filtering (e.g. bilingual labels). Display still uses the main folder name.","default":false,"title":"Normalize Subjects","type":"boolean"},"description":"Expand subject name variants when filtering (e.g. bilingual labels). Display still uses the main folder name."},{"name":"limit","in":"query","required":false,"schema":{"description":"Maximum number of results to return per page. Valid range: 1-500. Default: 50.","default":50,"title":"Limit","type":"integer","maximum":500,"minimum":1},"description":"Maximum number of results to return per page. Valid range: 1-500. Default: 50.","example":50},{"name":"offset","in":"query","required":false,"schema":{"description":"Number of results to skip for pagination. Use with limit.","default":0,"title":"Offset","type":"integer","minimum":0},"description":"Number of results to skip for pagination. Use with limit.","example":0},{"name":"fields","in":"query","required":false,"schema":{"description":"Comma-separated fields to include in each result. If omitted, all fields are returned.","title":"Fields","type":"string"},"description":"Comma-separated fields to include in each result. If omitted, all fields are returned.","example":"id,file_name,file_path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/typesense/autocomplete":{"get":{"tags":["Typesense"],"summary":"Typesense Autocomplete","description":"Retrieve Google-style query completions via Typesense prefix search.\n\nCompletes the query the user is typing — subjects, file types, description\nphrases, and standard numbers — instead of returning PDF filenames.\nResults are cached for 1 hour.\n\n## Query Parameter\n\n- **q**: Search query prefix (required, 1-100 characters)\n  - Case-insensitive\n  - Multi-word completions (`physics e` → `Physics Exam`)\n  - Nicknames: `maths`/`math` → Mathematics, `chem` → Chemistry, `bio` → Biology\n  - Five-digit prefixes complete to standard numbers\n\n## Filter Parameters\n\n- **subject**: Optional browse-folder filter (case-insensitive)\n- **limit**: Maximum suggestions (1-50, default: 10)\n\n## Examples\n\n```\nGET /v5/typesense/autocomplete?q=phy\nGET /v5/typesense/autocomplete?q=maths\nGET /v5/typesense/autocomplete?q=physics%20e\nGET /v5/typesense/autocomplete?q=915\nGET /v5/typesense/autocomplete?q=ex\nGET /v5/typesense/autocomplete?q=mech&subject=Physics&limit=15\n```\n\n## Response Structure\n\n- `suggestions`: Suggested query strings, ranked by relevance\n- `query`: Original prefix\n- `total_suggestions`: Count returned\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Found 4 suggestions\",\n  \"data\": {\n    \"suggestions\": [\n      \"Physics\",\n      \"Physical Education\",\n      \"Physics Exam\",\n      \"Physics Report\"\n    ],\n    \"query\": \"phy\",\n    \"total_suggestions\": 4\n  }\n}\n```\n\n## Notes\n\n- Suggestions continue the typed prefix (after nickname expansion)\n- File names are not returned\n- Māori Exam / Maori Resource are not suggested (Te Reo subject folders are)\n- Keywords are not scanned (too slow for keystroke latency)\n\nSee `docs/TYPESENSE.md`.","operationId":"typesense_autocomplete_v5_typesense_autocomplete_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":100,"description":"Prefix for Google-style completions (not PDF filenames). Case-insensitive. Multi-word: 'physics e' → 'Physics Exam'. Nicknames: 'maths' → 'Mathematics'. Five-digit prefixes complete standard numbers. Māori Exam / Maori Resource are never suggested. Examples: 'phy' → 'Physics', 'Physics Exam'; 'ex' → 'Exam'; 'mech' → 'mechanics'.","title":"Q"},"description":"Prefix for Google-style completions (not PDF filenames). Case-insensitive. Multi-word: 'physics e' → 'Physics Exam'. Nicknames: 'maths' → 'Mathematics'. Five-digit prefixes complete standard numbers. Māori Exam / Maori Resource are never suggested. Examples: 'phy' → 'Physics', 'Physics Exam'; 'ex' → 'Exam'; 'mech' → 'mechanics'.","example":"phy"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Maximum number of autocomplete suggestions to return. Valid range: 1-50. Default: 10.","default":10,"title":"Limit"},"description":"Maximum number of autocomplete suggestions to return. Valid range: 1-50. Default: 10.","example":10},{"name":"subject","in":"query","required":false,"schema":{"description":"Optional subject filter for context-aware suggestions. Limits candidate documents to the specified subject before extracting completions. Case-insensitive matching. Examples: 'Physics', 'Mathematics'.","title":"Subject","type":"string"},"description":"Optional subject filter for context-aware suggestions. Limits candidate documents to the specified subject before extracting completions. Case-insensitive matching. Examples: 'Physics', 'Mathematics'.","example":"Physics"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/core__response__StandardResponse_AutocompleteResponse___2"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/pdf/text":{"get":{"tags":["PDF"],"summary":"Get Pdf Text","description":"Extract plain text from a PDF via CDN URL or safe local path.\n\nMarkdown conversion is **disabled**. `as_markdown=true` returns HTTP 415\n(`markdown_not_supported`).\n\n## Query Parameters\n\n- **url**: CDN URL under `/direct/` (exclusive with `path`)\n- **path**: Local path under allowed roots (exclusive with `url`)\n- **pages**: Optional comma-separated 1-based page list\n- **max_pages**: Cap when `pages` omitted (1-200, default 50)\n- **as_markdown**: Must be false or omitted\n- **use_cache**: diskcache toggle\n\n## Example Request\n\n```\nGET /v5/pdf/text?url=https://cdn.toasting.me/direct/91524-exm-2023.pdf&max_pages=5\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Extracted text from 5 page(s)\",\n  \"data\": {\n    \"source\": \"cdn_url\",\n    \"url\": \"https://cdn.toasting.me/direct/91524-exm-2023.pdf\",\n    \"page_count\": 12,\n    \"pages_extracted\": [1, 2, 3, 4, 5],\n    \"text\": \"...\",\n    \"markdown\": null,\n    \"extractor\": \"pymupdf\",\n    \"cache_hit\": false\n  }\n}\n```\n\n## Error Responses\n\n- **400**: Missing or invalid url/path\n- **415**: `as_markdown=true` (markdown conversion is not supported)\n- **503**: PyMuPDF unavailable\n\n## Notes\n\n- Local paths are validated against `PDF_ALLOWED_ROOTS` (path traversal blocked).\n- OCR for image-only PDFs is out of scope.\n- Requires `pymupdf` (and optionally `diskcache`).","operationId":"get_pdf_text_v5_pdf_text_get","parameters":[{"name":"url","in":"query","required":false,"schema":{"description":"CDN URL to extract (must be https://cdn.toasting.me/direct/...). Mutually exclusive with path.","title":"Url","type":"string"},"description":"CDN URL to extract (must be https://cdn.toasting.me/direct/...). Mutually exclusive with path.","example":"https://cdn.toasting.me/direct/91524-exm-2023.pdf"},{"name":"path","in":"query","required":false,"schema":{"description":"Safe local PDF path under PDF_ALLOWED_ROOTS. Mutually exclusive with url.","title":"Path","type":"string"},"description":"Safe local PDF path under PDF_ALLOWED_ROOTS. Mutually exclusive with url.","example":"files/91524-exm-2023.pdf"},{"name":"pages","in":"query","required":false,"schema":{"description":"Comma-separated 1-based page numbers (e.g. '1,2,3'). Default: first max_pages pages.","title":"Pages","type":"string"},"description":"Comma-separated 1-based page numbers (e.g. '1,2,3'). Default: first max_pages pages.","example":"1,2"},{"name":"max_pages","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Maximum pages to extract when pages is omitted. Valid range: 1-200. Default: 50.","default":50,"title":"Max Pages"},"description":"Maximum pages to extract when pages is omitted. Valid range: 1-200. Default: 50.","example":50},{"name":"as_markdown","in":"query","required":false,"schema":{"type":"boolean","description":"Must be false or omitted. Markdown conversion is disabled and returns HTTP 415.","default":false,"title":"As Markdown"},"description":"Must be false or omitted. Markdown conversion is disabled and returns HTTP 415.","example":false},{"name":"use_cache","in":"query","required":false,"schema":{"type":"boolean","description":"Use diskcache for repeated extractions. Default: true.","default":true,"title":"Use Cache"},"description":"Use diskcache for repeated extractions. Default: true.","example":true}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["PDF"],"summary":"Post Pdf Text","description":"Extract plain text from a PDF (POST body variant of `GET /v5/pdf/text`).\n\nMarkdown conversion is **disabled**. `as_markdown: true` returns HTTP 415\n(`markdown_not_supported`).\n\n## Example Request\n\n```\nPOST /v5/pdf/text\nContent-Type: application/json\n\n{\n  \"url\": \"https://cdn.toasting.me/direct/91524-exm-2023.pdf\",\n  \"max_pages\": 5\n}\n```\n\n## Example Response\n\nSame shape as `GET /v5/pdf/text`.","operationId":"post_pdf_text_v5_pdf_text_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PdfTextBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/files":{"get":{"tags":["Files"],"summary":"Get Files","description":"Retrieve files with comprehensive filtering, sorting, and pagination capabilities.\n\nThis endpoint provides access to NZQA educational files including examination papers,\nassessment schedules, exemplars, reports, and other resources. Supports multiple\nfilter combinations, various sorting options, and flexible pagination.\n\n## Filter Parameters\n\nAll filters support both single values and multiple values (comma-separated or repeated parameters):\n\n- **subjects**: Filter by one or more subjects (e.g., \"Physics\", \"Mathematics\", \"Chemistry\")\n- **levels**: Filter by NCEA levels (1, 2, or 3)\n- **years**: Filter by year (e.g., \"2022\", \"2023\", \"2024\")\n- **file_types**: Filter by file type (e.g., \"Exam\", \"Report\", \"Schedule\", \"Exemplar\")\n- **standard_numbers**: Filter by standard number (5-digit format, e.g., \"91524\")\n\n## Sorting Options\n\n- **name**: Alphabetical sorting by file name\n- **date**: Sort by file creation or modification date (most recent first with desc)\n- **level**: Sort by NZQA level (1, 2, 3)\n- **subject**: Alphabetical sorting by subject name\n- **file_size**: Sort by file size in bytes\n- **difficulty**: Sort by perceived difficulty based on historical attainment data\n  - Lower average attainment = higher difficulty\n  - Requires `include_attainment=true` parameter\n\n## Examples\n\n**Filter by subject and level:**\n```\nGET /v5/files?subject=Physics&level=3&limit=50\n```\n\n**Multiple subjects and levels:**\n```\nGET /v5/files?subjects=Physics,Chemistry&levels=2,3&limit=100\n```\n\n**Filter by year and file type:**\n```\nGET /v5/files?year=2023&file_type=Exam&limit=25\n```\n\n**Sort by file size (largest first):**\n```\nGET /v5/files?sort_by=file_size&sort_order=desc&limit=20\n```\n\n**Sort by difficulty (requires attainment data):**\n```\nGET /v5/files?subject=Mathematics&level=3&sort_by=difficulty&include_attainment=true&limit=30\n```\n\n**Get recent files:**\n```\nGET /v5/files?sort_by=date&sort_order=desc&limit=10\n```\n\n**Filter by standard number:**\n```\nGET /v5/files?standard_number=91524&limit=50\n```\n\n**Complex filter combination:**\n```\nGET /v5/files?subjects=Physics,Mathematics&levels=2,3&years=2022,2023&file_types=Exam,Report&sort_by=date&sort_order=desc&limit=100\n```\n\n## Response Format\n\nSupports multiple output formats:\n- **json**: Standard JSON response (default)\n- **csv**: Comma-separated values format\n- **text**: Plain text format\n\n## Field Selection\n\nUse the `fields` parameter to limit response data and reduce payload size:\n```\nGET /v5/files?fields=id,file_name,file_path,file_size&limit=50\n```\n\n## Pagination\n\n- Maximum 500 results per request\n- Use `offset` parameter for subsequent pages\n- Total count included in response metadata\n\n## Performance\n\n- Results are cached for improved performance\n- Database queries are optimized with indexes\n- Supports efficient filtering and sorting","operationId":"get_files_v5_files_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"description":"Maximum number of file results to return. Optional - defaults to unlimited (returns all matching results). Valid range: 1-10000. Use with offset for pagination.","title":"Limit","type":"integer","maximum":10000,"minimum":1},"description":"Maximum number of file results to return. Optional - defaults to unlimited (returns all matching results). Valid range: 1-10000. Use with offset for pagination.","example":50},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=50 with limit=50 returns files 51-100.","default":0,"title":"Offset"},"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=50 with limit=50 returns files 51-100.","example":0},{"name":"subjects","in":"query","required":false,"schema":{"description":"Filter by one or more subjects. Can be provided as comma-separated values or repeated parameters. Examples: '?subjects=Physics,Chemistry' or '?subjects=Physics&subjects=Chemistry'. Case-insensitive matching.","title":"Subjects","type":"array","items":{"type":"string"}},"description":"Filter by one or more subjects. Can be provided as comma-separated values or repeated parameters. Examples: '?subjects=Physics,Chemistry' or '?subjects=Physics&subjects=Chemistry'. Case-insensitive matching.","example":["Physics","Mathematics"]},{"name":"subject","in":"query","required":false,"schema":{"description":"Filter by a single subject name. Backward compatibility parameter. For multiple subjects, use 'subjects' parameter instead. Examples: 'Physics', 'Mathematics', 'Chemistry'.","title":"Subject","type":"string"},"description":"Filter by a single subject name. Backward compatibility parameter. For multiple subjects, use 'subjects' parameter instead. Examples: 'Physics', 'Mathematics', 'Chemistry'.","example":"Physics"},{"name":"levels","in":"query","required":false,"schema":{"description":"Filter by one or more NCEA levels. Can be provided as comma-separated values or repeated parameters. Valid values: 1, 2, or 3. Examples: '?levels=1,2' or '?levels=2&levels=3'. FIXED: Now accepts string for comma-separated values.","title":"Levels","type":"string"},"description":"Filter by one or more NCEA levels. Can be provided as comma-separated values or repeated parameters. Valid values: 1, 2, or 3. Examples: '?levels=1,2' or '?levels=2&levels=3'. FIXED: Now accepts string for comma-separated values.","example":"2,3"},{"name":"level","in":"query","required":false,"schema":{"description":"Filter by a single NCEA level. Backward compatibility parameter. Valid values: 1, 2, or 3. For multiple levels, use 'levels' parameter instead.","title":"Level","type":"integer","maximum":3,"minimum":1},"description":"Filter by a single NCEA level. Backward compatibility parameter. Valid values: 1, 2, or 3. For multiple levels, use 'levels' parameter instead.","example":3},{"name":"years","in":"query","required":false,"schema":{"description":"Filter by one or more years. Can be provided as comma-separated values or repeated parameters. Must be 4-digit format (2000-2030). Examples: '?years=2022,2023' or '?years=2022&years=2023'.","title":"Years","type":"array","items":{"type":"string"}},"description":"Filter by one or more years. Can be provided as comma-separated values or repeated parameters. Must be 4-digit format (2000-2030). Examples: '?years=2022,2023' or '?years=2022&years=2023'.","example":["2022","2023"]},{"name":"year","in":"query","required":false,"schema":{"description":"Filter by a single year. Backward compatibility parameter. Must be 4-digit format (2000-2030). Example: '2023'. For multiple years, use 'years' parameter instead.","title":"Year","type":"string"},"description":"Filter by a single year. Backward compatibility parameter. Must be 4-digit format (2000-2030). Example: '2023'. For multiple years, use 'years' parameter instead.","example":"2023"},{"name":"subject_exact","in":"query","required":false,"schema":{"type":"boolean","description":"If true, match subjects exactly and do not expand to canonical variants","default":false,"title":"Subject Exact"},"description":"If true, match subjects exactly and do not expand to canonical variants","example":false},{"name":"file_types","in":"query","required":false,"schema":{"description":"Filter by one or more file types. Can be provided as comma-separated values or repeated parameters. Common values: 'Exam', 'Report', 'Schedule', 'Exemplar'. Case-insensitive matching. Examples: '?file_types=Exam,Report' or '?file_types=Exam&file_types=Report'.","title":"File Types","type":"array","items":{"type":"string"}},"description":"Filter by one or more file types. Can be provided as comma-separated values or repeated parameters. Common values: 'Exam', 'Report', 'Schedule', 'Exemplar'. Case-insensitive matching. Examples: '?file_types=Exam,Report' or '?file_types=Exam&file_types=Report'.","example":["Exam","Report"]},{"name":"file_type","in":"query","required":false,"schema":{"description":"Filter by a single file type. Backward compatibility parameter. Common values: 'Exam', 'Report', 'Schedule', 'Exemplar'. For multiple file types, use 'file_types' parameter instead.","title":"File Type","type":"string"},"description":"Filter by a single file type. Backward compatibility parameter. Common values: 'Exam', 'Report', 'Schedule', 'Exemplar'. For multiple file types, use 'file_types' parameter instead.","example":"Exam"},{"name":"standard_numbers","in":"query","required":false,"schema":{"description":"Filter by one or more standard numbers. Can be provided as comma-separated values or repeated parameters. Must be exactly 5 digits each. Examples: '?standard_numbers=91524,91525' or '?standard_numbers=91524&standard_numbers=91525'.","title":"Standard Numbers","type":"array","items":{"type":"string"}},"description":"Filter by one or more standard numbers. Can be provided as comma-separated values or repeated parameters. Must be exactly 5 digits each. Examples: '?standard_numbers=91524,91525' or '?standard_numbers=91524&standard_numbers=91525'.","example":["91524","91525"]},{"name":"standard_number","in":"query","required":false,"schema":{"description":"Filter by a single standard number. Backward compatibility parameter. Must be exactly 5 digits. Example: '91524'. For multiple standard numbers, use 'standard_numbers' parameter instead.","title":"Standard Number","type":"string"},"description":"Filter by a single standard number. Backward compatibility parameter. Must be exactly 5 digits. Example: '91524'. For multiple standard numbers, use 'standard_numbers' parameter instead.","example":"91524"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","pattern":"^(name|date|level|subject|file_size|difficulty|difficulty_desc|difficulty_asc)$","description":"Sort order for file results. Valid values: 'name' (alphabetical by file name, default), 'date' (by creation/modification date), 'level' (by NCEA level), 'subject' (alphabetical by subject), 'file_size' (by file size in bytes), 'difficulty' (by perceived difficulty based on attainment data). Use 'difficulty_desc' or 'difficulty_asc' for explicit difficulty sorting direction.","default":"name","title":"Sort By"},"description":"Sort order for file results. Valid values: 'name' (alphabetical by file name, default), 'date' (by creation/modification date), 'level' (by NCEA level), 'subject' (alphabetical by subject), 'file_size' (by file size in bytes), 'difficulty' (by perceived difficulty based on attainment data). Use 'difficulty_desc' or 'difficulty_asc' for explicit difficulty sorting direction.","example":"name"},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","description":"Sort direction for results. Valid values: 'asc' (ascending, default) or 'desc' (descending). Applies to all sort_by options except 'difficulty' which has its own direction options.","default":"asc","title":"Sort Order"},"description":"Sort direction for results. Valid values: 'asc' (ascending, default) or 'desc' (descending). Applies to all sort_by options except 'difficulty' which has its own direction options.","example":"asc"},{"name":"include_attainment","in":"query","required":false,"schema":{"type":"boolean","description":"Include attainment data in response. Required when using 'difficulty' sorting. When true, adds average attainment rates to file metadata for difficulty-based sorting. Default: false.","default":false,"title":"Include Attainment"},"description":"Include attainment data in response. Required when using 'difficulty' sorting. When true, adds average attainment rates to file metadata for difficulty-based sorting. Default: false.","example":false},{"name":"fields","in":"query","required":false,"schema":{"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'id,file_name,file_size' returns only those fields. Note: file path is not returned in list responses.","title":"Fields","type":"string"},"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'id,file_name,file_size' returns only those fields. Note: file path is not returned in list responses.","example":"id,file_name,file_size"},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|text)$","description":"Response format for file data. Valid values: 'json' (default, standard JSON response), 'csv' (comma-separated values, downloadable file), 'text' (plain text format).","default":"json","title":"Format"},"description":"Response format for file data. Valid values: 'json' (default, standard JSON response), 'csv' (comma-separated values, downloadable file), 'text' (plain text format).","example":"json"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/files/{file_id}":{"get":{"tags":["Files"],"summary":"Get File By Id","description":"Retrieve a single file by its unique file ID.\n\nReturns comprehensive details for a specific file including name, path, size, type,\nassociated standard, subject, level, year, and metadata. Useful for getting complete\nfile information after searching or listing files.\n\n## Path Parameter\n\n- **file_id**: Unique integer file identifier\n  - Must be a positive integer\n  - Validated before processing\n\n## Examples\n\n**Get file details:**\n```\nGET /v5/files/12345\n```\n\n## Response Structure\n\nReturns a file object containing:\n- `id`: Unique file identifier\n- `file_name`: Name of the file\n- `file_path`: Full path to the file\n- `file_link`: URL or link to access the file\n- `file_type`: Type of file (e.g., \"Exam\", \"Report\", \"Schedule\", \"Exemplar\")\n- `file_size`: File size in bytes\n- `file_format`: File format (e.g., \"PDF\", \"DOCX\")\n- `mime_type`: MIME type of the file\n- `subject`: Associated subject name\n- `level`: Associated NCEA level (1, 2, or 3)\n- `year`: Year associated with the file\n- `standard_number`: Associated standard number (5-digit)\n- `relative_path`: Relative path to the file\n- `last_modified_iso`: Last modification timestamp (ISO 8601)\n- `created_at`: Creation timestamp\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"File retrieved successfully\",\n  \"data\": {\n    \"id\": 12345,\n    \"file_name\": \"91524-2023-Exam.pdf\",\n    \"file_path\": \"/files/Physics/Level3/91524-2023-Exam.pdf\",\n    \"file_type\": \"Exam\",\n    \"file_size\": 2456789,\n    \"subject\": \"Physics\",\n    \"level\": 3,\n    \"year\": \"2023\",\n    \"standard_number\": \"91524\"\n  }\n}\n```\n\n## Error Handling\n\n- Returns 404 if file not found\n- Returns 400 if file ID format is invalid\n- Returns 500 for server errors (with generic message)","operationId":"get_file_by_id_v5_files__file_id__get","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"integer","description":"Unique integer identifier for the file. Must be a positive integer. Example: 12345","title":"File Id"},"description":"Unique integer identifier for the file. Must be a positive integer. Example: 12345","example":12345}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_File_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/subjects":{"get":{"tags":["Subjects"],"summary":"List Subjects","description":"Retrieve a literal taxonomy tree using NZQA hierarchy levels.\n\nReturns a paginated list of top-level **Field** nodes. Each Field contains nested\n`sub_subjects` for **Subfields**, and each Subfield contains nested `sub_subjects`\nfor **Domains**.\n\nFile and standard counts are rolled up at every level. Pagination (`limit`/`offset`)\napplies to the top-level Field list.\n\n## Include Options\n\nThe `include` parameter accepts comma-separated values and enriches each tree node:\n\n- **levels**: Available NCEA levels (1, 2, 3) per node (also rolled up to parent)\n- **years**: Available years per node based on associated files (also rolled up)\n- **keywords**: Top keywords (max 10 per node)\n- **summary**: Comprehensive summary statistics per node\n\n## Examples\n\n**Basic hierarchical subject list:**\n```\nGET /v5/subjects?limit=50\n```\n\n**Include available levels and years:**\n```\nGET /v5/subjects?include=levels,years&limit=100\n```\n\n**Include all enrichment data:**\n```\nGET /v5/subjects?include=levels,years,keywords,summary&limit=50\n```\n\n**Pagination:**\n```\nGET /v5/subjects?limit=50&offset=50\n```\n\n## Response Structure\n\n```json\n{\n  \"subjects\": [\n    {\n                \"name\": \"Service Sector\",\n                \"file_count\": 12345,\n                \"standard_count\": 678,\n      \"sub_subjects\": [\n                    {\n                        \"name\": \"Aviation\",\n                        \"file_count\": 1234,\n                        \"standard_count\": 56,\n                        \"sub_subjects\": [\n                            { \"name\": \"Air Cargo\", \"file_count\": 345, \"standard_count\": 12 }\n                        ]\n                    }\n      ]\n    }\n  ],\n        \"total_subjects\": 18,\n  \"pagination\": { ... }\n}\n```\n\n    - **subjects**: Array of top-level Field nodes\n    - **total_subjects**: Total number of Field nodes\n- **pagination**: Standard pagination metadata (limit, offset, total, has_next, etc.)\n\n    Each node contains:\n    - `name` : taxonomy label (Field, Subfield, or Domain)\n    - `file_count` : rolled-up total across descendants\n    - `standard_count` : rolled-up total across descendants\n    - `sub_subjects` : child nodes (Field -> Subfield -> Domain)\n- `levels` : merged unique levels (if `include=levels`)\n- `years` : merged unique years descending (if `include=years`)\n\n## Performance\n\n- Results are cached for 1 hour\n- Single optimised query groups all subjects server-side before enrichment\n- Batch queries used for optional enrichment data","operationId":"list_subjects_v5_subjects_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum number of subject results to return per page. Valid range: 1-1000. Default: 100. Use with offset for pagination.","default":100,"title":"Limit"},"description":"Maximum number of subject results to return per page. Valid range: 1-1000. Default: 100. Use with offset for pagination.","example":100},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=100 with limit=100 returns subjects 101-200.","default":0,"title":"Offset"},"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=100 with limit=100 returns subjects 101-200.","example":0},{"name":"normalize","in":"query","required":false,"schema":{"type":"boolean","description":"Normalize subject names to canonical forms. When true, variant names (e.g., 'Mahi Kaute (Accounting)') are mapped to canonical names (e.g., 'Accounting') for consistent display and filtering. Default: false.","default":false,"title":"Normalize"},"description":"Normalize subject names to canonical forms. When true, variant names (e.g., 'Mahi Kaute (Accounting)') are mapped to canonical names (e.g., 'Accounting') for consistent display and filtering. Default: false."},{"name":"include","in":"query","required":false,"schema":{"description":"Comma-separated list of related data to include with each subject. Valid values: 'levels' (available NCEA levels), 'years' (available years), 'keywords' (top keywords, limited to 10 per subject), 'summary' (comprehensive summary statistics). Can combine multiple values: 'levels,years,keywords'.","title":"Include","type":"string"},"description":"Comma-separated list of related data to include with each subject. Valid values: 'levels' (available NCEA levels), 'years' (available years), 'keywords' (top keywords, limited to 10 per subject), 'summary' (comprehensive summary statistics). Can combine multiple values: 'levels,years,keywords'.","example":"levels,years,keywords"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/subjects/{subject}":{"get":{"tags":["Subjects"],"summary":"Get Subject","description":"Retrieve a single subject by name with optional enrichment data.\n\nReturns comprehensive details for a specific subject including name, canonical name,\ndescription, file count, standard count, and optional related data such as available\nlevels, years, keywords, and summary statistics.\n\n## Path Parameter\n\n- **subject**: Subject name (case-insensitive matching)\n  - Can be canonical name or variant name\n  - Examples: \"Physics\", \"Mathematics\", \"Mahi Kaute (Accounting)\"\n\n## Query Parameters\n\n- **include**: Optional comma-separated list of related data to include\n  - `levels`: Include available NCEA levels (1, 2, 3) for this subject\n  - `years`: Include available years for this subject based on associated files\n  - `keywords`: Include top keywords (limited to 10) associated with this subject\n  - `summary`: Include comprehensive summary statistics for this subject\n    - Total file count\n    - Total standard count\n    - Available levels\n    - Year range\n\n## Examples\n\n**Get basic subject information:**\n```\nGET /v5/subjects/Physics\n```\n\n**Include available levels:**\n```\nGET /v5/subjects/Physics?include=levels\n```\n\n**Include levels and years:**\n```\nGET /v5/subjects/Mathematics?include=levels,years\n```\n\n**Include all enrichment data:**\n```\nGET /v5/subjects/Chemistry?include=levels,years,keywords,summary\n```\n\n## Response Structure\n\nReturns a subject object containing:\n- `name`: Subject name\n- `canonical_name`: Canonical subject name (normalized)\n- `level`: Primary level (if applicable)\n- `description`: Subject description (if available)\n\nReturns 404 if the subject is not found in the database.\n- `file_count`: Total number of files for this subject\n- `standard_count`: Total number of standards for this subject\n- `keywords`: Array of keywords (if `include=keywords`)\n- `variants`: Array of variant subject names\n- Optional enrichment data based on `include` parameter\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Subject retrieved\",\n  \"data\": {\n    \"subject\": {\n      \"name\": \"Physics\",\n      \"canonical_name\": \"Physics\",\n      \"file_count\": 1245,\n      \"standard_count\": 23,\n      \"levels\": [1, 2, 3],\n      \"years\": [\"2020\", \"2021\", \"2022\", \"2023\", \"2024\"],\n      \"top_keywords\": [\"mechanics\", \"waves\", \"electricity\", \"thermodynamics\"],\n      \"summary\": {\n        \"total_files\": 1245,\n        \"total_standards\": 23,\n        \"available_levels\": [1, 2, 3],\n        \"year_range\": {\"min\": \"2020\", \"max\": \"2024\"}\n      }\n    }\n  }\n}\n```\n\n## Error Handling\n\n- Returns 404 if subject not found\n- Returns 400 if subject name format is invalid\n- Returns 500 for server errors (with generic message)\n\n## Performance\n\n- Enrichment data is fetched on-demand\n- Batch queries are used when multiple enrichment options are requested\n- Results are cached when enrichment data is included","operationId":"get_subject_v5_subjects__subject__get","parameters":[{"name":"subject","in":"path","required":true,"schema":{"type":"string","description":"Subject name for which to retrieve details. Case-insensitive matching. Can be canonical name or variant name. Examples: 'Physics', 'Mathematics', 'Mahi Kaute (Accounting)'.","title":"Subject"},"description":"Subject name for which to retrieve details. Case-insensitive matching. Can be canonical name or variant name. Examples: 'Physics', 'Mathematics', 'Mahi Kaute (Accounting)'.","example":"Physics"},{"name":"include","in":"query","required":false,"schema":{"description":"Comma-separated list of related data to include with the subject. Valid values: 'levels' (available NCEA levels), 'years' (available years), 'keywords' (top keywords, limited to 10), 'summary' (comprehensive summary statistics). Can combine multiple values: 'levels,years,keywords'.","title":"Include","type":"string"},"description":"Comma-separated list of related data to include with the subject. Valid values: 'levels' (available NCEA levels), 'years' (available years), 'keywords' (top keywords, limited to 10), 'summary' (comprehensive summary statistics). Can combine multiple values: 'levels,years,keywords'.","example":"levels,years,keywords"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_Subject_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/browse/{subject}":{"get":{"tags":["Browse"],"summary":"Browse Subject","description":"Browse a subject (optionally filtered by NCEA level) in a single fast call.\n\nReplaces the N+1 pattern of listing subject files then fetching attainment\nstats per standard. Returns standards and files together, with optional\nrates-only attainment summaries (no student/assessed headcounts).\n\n## Path Parameters\n\n- **subject**: Subject name (e.g. `Accounting`, `Physics`)\n\n## Query Parameters\n\n- **level**: Optional NCEA level (1–3)\n- **include_attainment**: Attach rates-only attainment with per-year + weighted (default true)\n- **limit_files** / **limit_standards**: Caps for large subjects\n\n## Response Data\n\n- `subject`: Requested subject name\n- `level`: Level filter applied (or null)\n- `standards`: Standards for the subject/level, each optionally with\n  `attainment.by_year[]` rates and `attainment.weighted` summary (no student headcounts)\n- `files`: Files for the subject/level\n- `total_standards` / `total_files`: Counts in this response\n\nUnknown subjects return **404** (`status_code` in the body) with close-name\nsuggestions when possible (e.g. `Caclulus` → `Calculus`). The error payload\nincludes `error.context.suggestions` and a `try_suggested_name` recovery action.\n\n## Example Request\n\n```\nGET /v5/browse/Accounting?level=3\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Browse package for Accounting (level 3)\",\n  \"data\": {\n    \"subject\": \"Accounting\",\n    \"level\": 3,\n    \"standards\": [\n      {\n        \"standard_number\": \"91404\",\n        \"title\": \"Demonstrate understanding of accounting concepts for a New Zealand reporting entity\",\n        \"subject\": \"Accounting - Generic\",\n        \"primary_subject\": \"Business\",\n        \"level\": 3,\n        \"credits\": 4,\n        \"assessment\": \"External\",\n        \"version\": \"2\",\n        \"standard_type\": \"Achievement\",\n        \"status\": \"Current\",\n        \"expired\": false,\n        \"literacy\": false,\n        \"numeracy\": false,\n        \"te_reo_matatini\": false,\n        \"attainment\": {\n          \"year_range\": {\"from\": \"2015\", \"to\": \"2024\"},\n          \"years_with_data\": 10,\n          \"by_year\": [\n            {\n              \"year\": \"2023\",\n              \"excellence_rate\": 0.098,\n              \"merit_rate\": 0.261,\n              \"achieved_rate\": 0.326,\n              \"not_achieved_rate\": 0.315\n            }\n          ],\n          \"weighted\": {\n            \"excellence_rate\": 0.094,\n            \"merit_rate\": 0.186,\n            \"achieved_rate\": 0.369,\n            \"not_achieved_rate\": 0.351\n          }\n        }\n      }\n    ],\n    \"files\": [\n      {\n        \"file_id\": 3898,\n        \"file_name\": \"91404-exm-2023.pdf\",\n        \"relative_path\": \"accounting/level-03/91404-exm-2023.pdf\",\n        \"file_link\": \"https://cdn.toasting.me/direct/91404-exm-2023.pdf\",\n        \"standard_number\": \"91404\",\n        \"year\": \"2023\",\n        \"level\": 3,\n        \"subject\": \"Accounting\",\n        \"file_type\": \"Exam\"\n      }\n    ],\n    \"total_standards\": 6,\n    \"total_files\": 398\n  },\n  \"meta\": {\n    \"api_version\": \"5.0.1\",\n    \"cached\": true\n  }\n}\n```\n\n## Example 404 (unknown subject / typo)\n\n```\nGET /v5/browse/Caclulus\n```\n\n```json\n{\n  \"status\": \"error\",\n  \"status_code\": 404,\n  \"message\": \"subject 'Caclulus' not found. Did you mean 'Calculus'?\",\n  \"error_code\": \"not_found\",\n  \"data\": null,\n  \"error\": {\n    \"type\": \"not_found\",\n    \"context\": {\n      \"resource\": {\"type\": \"subject\", \"id\": \"Caclulus\"},\n      \"suggestions\": [\"Calculus\"]\n    },\n    \"recovery_suggestions\": [\n      {\n        \"action\": \"try_suggested_name\",\n        \"description\": \"Close match found - try: 'Calculus'\",\n        \"suggestions\": [\"Calculus\"],\n        \"endpoint\": \"/v5/browse/Calculus\"\n      }\n    ]\n  }\n}\n```\n\n## Notes\n\n- Existing `/api/v3` multi-call paths remain available for legacy clients.\n- Attainment payloads are rates-only (no `total_students` / `total_assessed`).\n- Responses are cached for **6 hours**. On cache hits, `meta.cached` is `true`.\n- Standards and files include additional metadata fields beyond the abbreviated example.","operationId":"browse_subject_v5_browse__subject__get","parameters":[{"name":"subject","in":"path","required":true,"schema":{"type":"string","description":"Subject name to browse. Case-sensitive match against files/standards subject and primary_subject, with Generic-variant resolution (e.g. 'Accounting' matches 'Accounting - Generic').","title":"Subject"},"description":"Subject name to browse. Case-sensitive match against files/standards subject and primary_subject, with Generic-variant resolution (e.g. 'Accounting' matches 'Accounting - Generic').","example":"Accounting"},{"name":"level","in":"query","required":false,"schema":{"description":"Optional NCEA level filter. Valid values: 1, 2, or 3. When omitted, returns standards and files for all levels under the subject.","title":"Level","type":"integer","maximum":3,"minimum":1},"description":"Optional NCEA level filter. Valid values: 1, 2, or 3. When omitted, returns standards and files for all levels under the subject.","example":3},{"name":"include_attainment","in":"query","required":false,"schema":{"type":"boolean","description":"When true (default), attach rates-only weighted attainment summary per standard. Student/assessed headcounts are never included.","default":true,"title":"Include Attainment"},"description":"When true (default), attach rates-only weighted attainment summary per standard. Student/assessed headcounts are never included.","example":true},{"name":"limit_files","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":1,"description":"Maximum number of files to return. Valid range: 1-10000. Default: 5000.","default":5000,"title":"Limit Files"},"description":"Maximum number of files to return. Valid range: 1-10000. Default: 5000.","example":5000},{"name":"limit_standards","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"Maximum number of standards to return. Valid range: 1-5000. Default: 2000.","default":2000,"title":"Limit Standards"},"description":"Maximum number of standards to return. Valid range: 1-5000. Default: 2000.","example":2000}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/standards":{"get":{"tags":["Standards"],"summary":"List Standards","description":"Retrieve NZQA achievement standards with comprehensive filtering, sorting, and pagination.\n\nThis endpoint provides access to NZQA achievement standards including descriptions,\ncredits, levels, subjects, and related metadata. Supports multiple filter combinations,\ndifficulty-based sorting using attainment data, and flexible pagination.\n\n## Filter Parameters\n\nAll filters support both single values and multiple values (comma-separated or repeated parameters):\n\n- **subjects**: Filter by one or more subjects. Three matching modes (all case-insensitive):\n  - Exact: `subject=Physics` matches standards with `subject = 'Physics'`\n  - Hyphen-prefix: `subject=Accounting` matches `Accounting - Generic`, `Accounting - Mahi Kaute`, etc.\n  - Word-prefix: `subject=English` matches `English Oral Language`, `English Written Language`, `English Visual Language`, etc.\n- **levels**: Filter by NCEA levels (1, 2, or 3)\n- **years**: Filter by year based on associated files (e.g., \"2022\", \"2023\", \"2024\")\n\n## Sorting Options\n\n- **number**: Alphabetical sorting by standard number (e.g., \"91524\", \"91525\")\n- **level**: Sort by NZQA level (1, 2, 3)\n- **subject**: Alphabetical sorting by subject name\n- **difficulty**: Sort by perceived difficulty based on historical attainment data\n  - Lower average attainment = higher difficulty\n  - Requires `include_attainment=true` parameter\n  - Uses `achieved_rate` from attainment data\n\n## Examples\n\n**Filter by subject and level (exact match):**\n```\nGET /v5/standards?subject=Physics&level=3&limit=50\n```\n\n**Hyphen-prefix match (subject group):**\n```\nGET /v5/standards?subject=Accounting&level=3&limit=50\n```\n*(Matches standards with subject 'Accounting - Generic', 'Accounting - Mahi Kaute', etc.)*\n\n**Word-prefix match (subject family):**\n```\nGET /v5/standards?subject=English&level=3&limit=50\n```\n*(Matches 'English Oral Language', 'English Written Language', 'English Visual Language', etc.)*\n\n**Multiple subjects and levels:**\n```\nGET /v5/standards?subjects=Physics,Chemistry&levels=2,3&limit=100\n```\n\n**Filter by year:**\n```\nGET /v5/standards?year=2023&limit=50\n```\n\n**Sort by difficulty (requires attainment data):**\n```\nGET /v5/standards?subject=Mathematics&level=3&sort_by=difficulty&include_attainment=true&limit=30\n```\n\n**Get standards sorted by number:**\n```\nGET /v5/standards?subject=Physics&sort_by=number&sort_order=asc&limit=100\n```\n\n**Complex filter combination:**\n```\nGET /v5/standards?subjects=Physics,Mathematics&levels=2,3&years=2022,2023&sort_by=level&sort_order=asc&limit=200\n```\n\n## Response Format\n\nSupports multiple output formats:\n- **json**: Standard JSON response (default)\n- **csv**: Comma-separated values format\n- **text**: Plain text format\n\n## Field Selection\n\nUse the `fields` parameter to limit response data:\n```\nGET /v5/standards?fields=number,description,subject,level,credits&limit=50\n```\n\n## Pagination\n\n- Maximum 500 results per request\n- Use `offset` parameter for subsequent pages\n- Total count included in response metadata\n\n## Difficulty Sorting\n\nWhen using `sort_by=difficulty`, the API:\n1. Retrieves attainment data for standards matching your filters\n2. Calculates average `achieved_rate` per standard\n3. Sorts by attainment (ascending = most difficult first)\n4. Falls back to level-based sorting if attainment data unavailable\n\n## Performance\n\n- Results are cached for improved performance\n- Database queries are optimized with indexes\n- Supports efficient filtering and sorting","operationId":"list_standards_v5_standards_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum number of standard results to return per page. Valid range: 1-500. Default: 50. Use with offset for pagination.","default":50,"title":"Limit"},"description":"Maximum number of standard results to return per page. Valid range: 1-500. Default: 50. Use with offset for pagination.","example":50},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=50 with limit=50 returns standards 51-100.","default":0,"title":"Offset"},"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=50 with limit=50 returns standards 51-100.","example":0},{"name":"subjects","in":"query","required":false,"schema":{"description":"Filter by one or more subjects. Can be provided as comma-separated values or repeated parameters. Examples: '?subjects=Physics,Chemistry' or '?subjects=Physics&subjects=Chemistry'. Case-insensitive. Supports exact match, hyphen-prefix match ('Accounting' matches 'Accounting - Generic'), and word-prefix match ('English' matches 'English Oral Language', 'English Written Language', etc.).","title":"Subjects","type":"array","items":{"type":"string"}},"description":"Filter by one or more subjects. Can be provided as comma-separated values or repeated parameters. Examples: '?subjects=Physics,Chemistry' or '?subjects=Physics&subjects=Chemistry'. Case-insensitive. Supports exact match, hyphen-prefix match ('Accounting' matches 'Accounting - Generic'), and word-prefix match ('English' matches 'English Oral Language', 'English Written Language', etc.).","example":["Physics","Mathematics"]},{"name":"subject","in":"query","required":false,"schema":{"description":"Filter by a single subject name. Backward compatibility parameter. For multiple subjects, use 'subjects' parameter instead. Case-insensitive. Supports exact match, hyphen-prefix match ('Accounting' matches 'Accounting - Generic'), and word-prefix match ('English' matches 'English Oral Language', 'English Written Language', etc.).","title":"Subject","type":"string"},"description":"Filter by a single subject name. Backward compatibility parameter. For multiple subjects, use 'subjects' parameter instead. Case-insensitive. Supports exact match, hyphen-prefix match ('Accounting' matches 'Accounting - Generic'), and word-prefix match ('English' matches 'English Oral Language', 'English Written Language', etc.).","example":"Physics"},{"name":"levels","in":"query","required":false,"schema":{"description":"Filter by one or more NCEA levels. Can be provided as comma-separated values or repeated parameters. Valid values: 1, 2, or 3. Examples: '?levels=1,2' or '?levels=1&levels=2'.","title":"Levels","type":"array","items":{"type":"integer"}},"description":"Filter by one or more NCEA levels. Can be provided as comma-separated values or repeated parameters. Valid values: 1, 2, or 3. Examples: '?levels=1,2' or '?levels=1&levels=2'.","example":[2,3]},{"name":"level","in":"query","required":false,"schema":{"description":"Filter by a single NCEA level. Backward compatibility parameter. Valid values: 1, 2, or 3. For multiple levels, use 'levels' parameter instead.","title":"Level","type":"integer","maximum":3,"minimum":1},"description":"Filter by a single NCEA level. Backward compatibility parameter. Valid values: 1, 2, or 3. For multiple levels, use 'levels' parameter instead.","example":3},{"name":"years","in":"query","required":false,"schema":{"description":"Filter by one or more years based on associated files. Can be provided as comma-separated values or repeated parameters. Must be 4-digit format (2000-2030). Examples: '?years=2022,2023' or '?years=2022&years=2023'.","title":"Years","type":"array","items":{"type":"string"}},"description":"Filter by one or more years based on associated files. Can be provided as comma-separated values or repeated parameters. Must be 4-digit format (2000-2030). Examples: '?years=2022,2023' or '?years=2022&years=2023'.","example":["2022","2023"]},{"name":"year","in":"query","required":false,"schema":{"description":"Filter by a single year based on associated files. Backward compatibility parameter. Must be 4-digit format (2000-2030). Example: '2023'. For multiple years, use 'years' parameter instead.","title":"Year","type":"string"},"description":"Filter by a single year based on associated files. Backward compatibility parameter. Must be 4-digit format (2000-2030). Example: '2023'. For multiple years, use 'years' parameter instead.","example":"2023"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","pattern":"^(number|level|subject|difficulty|difficulty_desc|difficulty_asc)$","description":"Sort order for standard results. Valid values: 'number' (alphabetical by standard number, default), 'level' (by NCEA level), 'subject' (alphabetical by subject), 'difficulty' (by perceived difficulty based on attainment data). Use 'difficulty_desc' or 'difficulty_asc' for explicit difficulty sorting direction.","default":"number","title":"Sort By"},"description":"Sort order for standard results. Valid values: 'number' (alphabetical by standard number, default), 'level' (by NCEA level), 'subject' (alphabetical by subject), 'difficulty' (by perceived difficulty based on attainment data). Use 'difficulty_desc' or 'difficulty_asc' for explicit difficulty sorting direction.","example":"number"},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","description":"Sort direction for results. Valid values: 'asc' (ascending, default) or 'desc' (descending). Applies to all sort_by options except 'difficulty' which has its own direction options.","default":"asc","title":"Sort Order"},"description":"Sort direction for results. Valid values: 'asc' (ascending, default) or 'desc' (descending). Applies to all sort_by options except 'difficulty' which has its own direction options.","example":"asc"},{"name":"include_attainment","in":"query","required":false,"schema":{"type":"boolean","description":"Include attainment data in response. Required when using 'difficulty' sorting. When true, adds average attainment rates to standard metadata for difficulty-based sorting. Default: false.","default":false,"title":"Include Attainment"},"description":"Include attainment data in response. Required when using 'difficulty' sorting. When true, adds average attainment rates to standard metadata for difficulty-based sorting. Default: false.","example":false},{"name":"fields","in":"query","required":false,"schema":{"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level,credits' returns only those fields. If omitted, all fields are returned.","title":"Fields","type":"string"},"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level,credits' returns only those fields. If omitted, all fields are returned.","example":"number,description,subject,level"},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|text)$","description":"Response format for standard data. Valid values: 'json' (default, standard JSON response), 'csv' (comma-separated values, downloadable file), 'text' (plain text format).","default":"json","title":"Format"},"description":"Response format for standard data. Valid values: 'json' (default, standard JSON response), 'csv' (comma-separated values, downloadable file), 'text' (plain text format).","example":"json"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/standards/{standard_number}":{"get":{"tags":["Standards"],"summary":"Get Standard","description":"Retrieve one or more NZQA achievement standards by standard number(s).\n\nReturns comprehensive details for specified standard(s) including description, credits,\nlevel, subject, version information, and associated metadata. Supports field selection\nto limit response data. Can fetch multiple standards in a single request.\n\n## Path Parameter\n\n- **standard_number**: 5-digit standard number or comma-separated list (e.g., \"91524\" or \"91524,91525\")\n  - Each number must be exactly 5 digits\n  - Maximum 10 standards per request\n  - Validated before processing\n\n## Query Parameters\n\n- **fields**: Optional comma-separated list of fields to include in response\n  - Reduces response payload size\n  - Example: \"number,description,subject,level,credits\"\n\n## Examples\n\n**Get standard details:**\n```\nGET /v5/standards/91524\n```\n\n**Get multiple standards:**\n```\nGET /v5/standards/91524,91525,91526\n```\n\n**Get specific fields only:**\n```\nGET /v5/standards/91524?fields=number,description,subject,level,credits\n```\n\n## Response Structure\n\nReturns a standard object containing:\n- `number`: Standard number (5-digit)\n- `description`: Full standard description\n- `subject`: Subject name\n- `level`: NCEA level (1, 2, or 3)\n- `credits`: Number of credits\n- `version`: Standard version\n- `version_status`: Version status (e.g., \"current\", \"expired\")\n- `expired`: Boolean indicating if standard is expired\n- `type`: Standard type\n- `assessment`: Assessment information\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Standard retrieved\",\n  \"data\": {\n    \"standard\": {\n      \"number\": \"91524\",\n      \"description\": \"Demonstrate understanding of mechanical systems\",\n      \"subject\": \"Physics\",\n      \"level\": 3,\n      \"credits\": 6,\n      \"version\": \"1\",\n      \"version_status\": \"current\",\n      \"expired\": false\n    }\n  }\n}\n```\n\n## Error Handling\n\n- Returns 404 if standard not found\n- Returns 400 if standard number format is invalid\n- Returns 500 for server errors (with generic message)","operationId":"get_standard_v5_standards__standard_number__get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","description":"5-digit NZQA standard number or comma-separated list of standard numbers. Examples: '91524', '91524,91525,91526'. Maximum 10 standards per request.","title":"Standard Number"},"description":"5-digit NZQA standard number or comma-separated list of standard numbers. Examples: '91524', '91524,91525,91526'. Maximum 10 standards per request.","example":"91524"},{"name":"fields","in":"query","required":false,"schema":{"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level,credits' returns only those fields. If omitted, all fields are returned.","title":"Fields","type":"string"},"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level,credits' returns only those fields. If omitted, all fields are returned.","example":"number,description,subject,level"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_Standard_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/standards/{standard_number}/related":{"get":{"tags":["Standards"],"summary":"Related Standards","description":"Retrieve related standards for a given standard number.\n\nReturns standards that share the same subject and level as the specified standard.\nUseful for discovering similar or related standards within the same subject area\nand difficulty level.\n\n## Path Parameter\n\n- **standard_number**: 5-digit standard number (e.g., \"91524\")\n  - Must be exactly 5 digits\n  - Validated before processing\n\n## Query Parameters\n\n- **limit**: Maximum number of related standards to return (1-100, default: 10)\n- **fields**: Optional comma-separated list of fields to include in response\n\n## Examples\n\n**Get related standards:**\n```\nGET /v5/standards/91524/related\n```\n\n**Get more related standards:**\n```\nGET /v5/standards/91524/related?limit=25\n```\n\n**Get specific fields only:**\n```\nGET /v5/standards/91524/related?fields=number,description,subject,level\n```\n\n## Response Structure\n\nReturns an array of related standard objects. Each standard shares the same\nsubject and level as the requested standard but has a different standard number.\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Related standards\",\n  \"data\": {\n    \"related_standards\": [\n      {\n        \"number\": \"91525\",\n        \"description\": \"Demonstrate understanding of wave systems\",\n        \"subject\": \"Physics\",\n        \"level\": 3,\n        \"credits\": 6\n      },\n      {\n        \"number\": \"91526\",\n        \"description\": \"Demonstrate understanding of electrical systems\",\n        \"subject\": \"Physics\",\n        \"level\": 3,\n        \"credits\": 6\n      }\n    ]\n  }\n}\n```\n\n## Error Handling\n\n- Returns 404 if the specified standard is not found\n- Returns 400 if standard number format is invalid\n- Returns 500 for server errors (with generic message)\n\n## Use Cases\n\n- Discovering similar standards for curriculum planning\n- Finding alternative standards in the same subject area\n- Building study packs with related content\n- Exploring subject-specific standard collections","operationId":"related_standards_v5_standards__standard_number__related_get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","description":"5-digit NZQA standard number for which to find related standards. Must be exactly 5 digits. Examples: '91524', '91525', '91526'.","title":"Standard Number"},"description":"5-digit NZQA standard number for which to find related standards. Must be exactly 5 digits. Examples: '91524', '91525', '91526'.","example":"91524"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of related standards to return. Valid range: 1-100. Default: 10. Related standards are those sharing the same subject and level as the specified standard.","default":10,"title":"Limit"},"description":"Maximum number of related standards to return. Valid range: 1-100. Default: 10. Related standards are those sharing the same subject and level as the specified standard.","example":10},{"name":"fields","in":"query","required":false,"schema":{"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level,credits' returns only those fields. If omitted, all fields are returned.","title":"Fields","type":"string"},"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level,credits' returns only those fields. If omitted, all fields are returned.","example":"number,description,subject,level"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/analytics":{"get":{"tags":["Analytics"],"summary":"Analytics Root","description":"Public analytics API root.\n\n**Status**: This endpoint is currently under development and temporarily disabled. It will return a 410 Gone response.\n\n## Planned Functionality (When Enabled)\n\nWhen this endpoint is enabled, it will provide usage analytics and resource\npopularity summaries for the public API.\n\n## Example Request (When Enabled)\n\n```\nGET /v5/analytics\n```\n\n## Current Response\n\nReturns 410 Gone with message indicating the endpoint is under development.","operationId":"analytics_root_v5_analytics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}}}}},"/v5/analytics/popular":{"get":{"tags":["Analytics"],"summary":"Popular Resources","description":"Retrieve popular resources based on keyword frequency and usage patterns.\n\n**Status**: This endpoint is currently under development and temporarily disabled. It will return a 410 Gone response.\n\n## Planned Functionality (When Enabled)\n\nWhen this endpoint is enabled, it will return a ranked list of popular\nresources (standards, files, or keywords) with optional filters:\n\n- **subject**: Filter by subject name (case-insensitive)\n- **level**: Filter by NCEA level (1, 2, or 3)\n- **year**: Filter by year (4-digit format, e.g. \"2023\")\n- **days**: Legacy parameter (does not filter by date)\n- **limit**: Maximum number of results (1-100, default: 50)\n\n## Example Request (When Enabled)\n\n```\nGET /v5/analytics/popular?subject=Physics&limit=30\n```\n\n## Current Response\n\nReturns 410 Gone with message indicating the endpoint is under development.","operationId":"popular_resources_v5_analytics_popular_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":3650,"minimum":1,"description":"Legacy parameter maintained for backward compatibility. Does not filter results by date. Use 'limit' parameter to control number of results. Valid range: 1-3650. Default: 30.","default":30,"title":"Days"},"description":"Legacy parameter maintained for backward compatibility. Does not filter results by date. Use 'limit' parameter to control number of results. Valid range: 1-3650. Default: 30."},{"name":"subject","in":"query","required":false,"schema":{"description":"Filter popular resources by subject name. Case-insensitive matching. Examples: 'Physics', 'Mathematics', 'Chemistry'. Can be combined with other filters.","title":"Subject","type":"string"},"description":"Filter popular resources by subject name. Case-insensitive matching. Examples: 'Physics', 'Mathematics', 'Chemistry'. Can be combined with other filters.","example":"Physics"},{"name":"level","in":"query","required":false,"schema":{"description":"Filter popular resources by NCEA level. Valid values: 1, 2, or 3. Can be combined with other filters.","title":"Level","type":"integer","maximum":3,"minimum":1},"description":"Filter popular resources by NCEA level. Valid values: 1, 2, or 3. Can be combined with other filters.","example":3},{"name":"year","in":"query","required":false,"schema":{"description":"Filter popular resources by year. Must be 4-digit format (2000-2030). Examples: '2022', '2023', '2024'. Can be combined with other filters.","title":"Year","type":"string"},"description":"Filter popular resources by year. Must be 4-digit format (2000-2030). Examples: '2022', '2023', '2024'. Can be combined with other filters.","example":"2023"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of popular resources to return. Valid range: 1-100. Default: 50. Results are ranked by popularity based on keyword frequency.","default":50,"title":"Limit"},"description":"Maximum number of popular resources to return. Valid range: 1-100. Default: 50. Results are ranked by popularity based on keyword frequency.","example":50}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_list_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/attainment":{"get":{"tags":["Attainment"],"summary":"Get Attainment","description":"Retrieve historical attainment data for NZQA standards with optional filtering.\n\nReturns achievement **rates only** (achieved / merit / excellence / not-achieved\npercentages). Student and assessed headcounts are omitted from v5 payloads.\nData can be filtered by subject, level, and year. Returns 204 No Content when no\ndata matches the specified filters.\n\n## Filter Parameters\n\n- **subject**: Filter by subject name. Three matching modes (all case-insensitive):\n  - Exact: `subject=Physics` matches records tagged `Physics`\n  - Hyphen-prefix: `subject=Accounting` matches `Accounting - Generic`, `Accounting - Mahi Kaute`, etc.\n  - Word-prefix: `subject=English` matches `English Oral Language`, `English Written Language`, etc.\n  - Also matches via the standard's subject field, so `subject=Accounting - Generic` will find attainment records even when NZQA tagged them under just `Accounting`.\n- **level**: Filter by NCEA level (1, 2, or 3)\n- **year**: Filter by year (4-digit format, e.g., \"2023\")\n\nAll filters are optional and can be combined. When multiple filters are provided,\nthey are combined with AND logic.\n\n## Response Data\n\nEach attainment record includes:\n- `standard_number`: 5-digit standard number\n- `subject`: Subject name\n- `level`: NCEA level (1, 2, or 3)\n- `year`: Year of the data\n- `attainment_percentage`: Achieved rate\n- `merit_percentage`: Merit rate\n- `excellence_percentage`: Excellence rate\n- `not_achieved_percentage`: Not-achieved rate (when available)\n\nHeadcount fields (`total_entries`, `total_students`, `total_assessed`, etc.) are\n**not** included in v5 responses.\n\n## Examples\n\n**Get all attainment data for a subject:**\n```\nGET /v5/attainment?subject=Physics&limit=100\n```\n\n**Filter by subject and level:**\n```\nGET /v5/attainment?subject=Mathematics&level=3&limit=50\n```\n\n**Filter by year:**\n```\nGET /v5/attainment?year=2023&limit=200\n```\n\n**Complex filter:**\n```\nGET /v5/attainment?subject=Chemistry&level=2&year=2022&limit=100\n```\n\n**Pagination:**\n```\nGET /v5/attainment?subject=Physics&limit=50&offset=50\n```\n\n## Example Response\n\n```\nGET /v5/attainment?subject=Accounting&level=3&year=2023&limit=2\n```\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Retrieved 2 attainment records\",\n  \"data\": [\n    {\n      \"standard_number\": \"91408\",\n      \"year\": \"2023\",\n      \"subject\": \"Accounting\",\n      \"level\": 3,\n      \"attainment_percentage\": 0.418,\n      \"merit_percentage\": 0.231,\n      \"excellence_percentage\": 0.08,\n      \"not_achieved_percentage\": 0.272\n    },\n    {\n      \"standard_number\": \"91404\",\n      \"year\": \"2023\",\n      \"subject\": \"Accounting\",\n      \"level\": 3,\n      \"attainment_percentage\": 0.326,\n      \"merit_percentage\": 0.261,\n      \"excellence_percentage\": 0.098,\n      \"not_achieved_percentage\": 0.315\n    }\n  ]\n}\n```\n\n## Response Status Codes\n\n- **200 OK**: Data found and returned\n- **204 No Content**: Request successful but no data matches the filters\n- **400 Bad Request**: Invalid filter parameters\n- **500 Internal Server Error**: Server error (generic message returned)\n\n## Pagination\n\n- Maximum 1000 results per request\n- Use `offset` parameter for subsequent pages\n- Default limit is 100 results\n\n## Use Cases\n\n- Analyzing achievement trends over time\n- Comparing attainment across subjects or levels\n- Identifying standards with high/low achievement rates\n- Research and statistical analysis\n- Educational planning and curriculum development","operationId":"get_attainment_v5_attainment_get","parameters":[{"name":"subject","in":"query","required":false,"schema":{"description":"Filter attainment data by subject name. Case-insensitive. Supports exact match, hyphen-prefix match ('Accounting' matches 'Accounting - Generic'), and word-prefix match ('English' matches 'English Oral Language', etc.). Can be combined with other filters.","title":"Subject","type":"string"},"description":"Filter attainment data by subject name. Case-insensitive. Supports exact match, hyphen-prefix match ('Accounting' matches 'Accounting - Generic'), and word-prefix match ('English' matches 'English Oral Language', etc.). Can be combined with other filters.","example":"Physics"},{"name":"level","in":"query","required":false,"schema":{"description":"Filter attainment data by NCEA level. Valid values: 1, 2, or 3. Can be combined with other filters.","title":"Level","type":"integer","maximum":3,"minimum":1},"description":"Filter attainment data by NCEA level. Valid values: 1, 2, or 3. Can be combined with other filters.","example":3},{"name":"year","in":"query","required":false,"schema":{"description":"Filter attainment data by year. Must be 4-digit format (2000-2030). Examples: '2022', '2023', '2024'. Can be combined with other filters.","title":"Year","type":"string"},"description":"Filter attainment data by year. Must be 4-digit format (2000-2030). Examples: '2022', '2023', '2024'. Can be combined with other filters.","example":"2023"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum number of attainment records to return per page. Valid range: 1-1000. Default: 100. Use with offset for pagination.","default":100,"title":"Limit"},"description":"Maximum number of attainment records to return per page. Valid range: 1-1000. Default: 100. Use with offset for pagination.","example":100},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=100 with limit=100 returns records 101-200.","default":0,"title":"Offset"},"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=100 with limit=100 returns records 101-200.","example":0}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/combination/standard/{standard_number}/complete":{"get":{"tags":["Combinations"],"summary":"Get Standard Complete Package","description":"Retrieve a complete standard package including details, files, attainment data, keywords, and optionally related standards.\n\n**Status**: This endpoint is currently under development and temporarily disabled. It will return a 410 Gone response.\n\n## Planned Functionality (When Enabled)\n\nWhen this endpoint is enabled, it will provide:\n\n- **Standard Details**: Complete standard information including description, credits, level, subject\n- **Files**: All associated files for the standard with optional filtering\n- **Attainment Data**: Historical attainment statistics for all years or filtered years\n- **Keywords**: Associated keywords for the standard\n- **Related Standards**: Optionally include related standards (same subject/level)\n\n## Path Parameter\n\n- **standard_number**: 5-digit standard number (e.g., \"91524\")\n  - Must be exactly 5 digits\n  - Validated before processing\n\n## Query Parameters (Planned)\n\n- **include_related**: Include related standards (same subject/level)\n- **include_keywords**: Include associated keywords\n- **include_attainment**: Include attainment data\n- **fields**: Comma-separated fields to include in response\n\n## Example Request (When Enabled)\n\n```\nGET /v5/combination/standard/91524/complete?include_related=true&include_attainment=true\n```\n\n## Current Response\n\nReturns 410 Gone with message indicating the endpoint is under development.","operationId":"get_standard_complete_package_v5_combination_standard__standard_number__complete_get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","description":"5-digit NZQA standard number. Must be exactly 5 digits. Examples: '91524', '91525', '91526'.","title":"Standard Number"},"description":"5-digit NZQA standard number. Must be exactly 5 digits. Examples: '91524', '91525', '91526'.","example":"91524"},{"name":"include_related","in":"query","required":false,"schema":{"type":"boolean","description":"Include related standards in the response. When true, adds standards sharing the same subject and level as the specified standard. Default: false.","default":false,"title":"Include Related"},"description":"Include related standards in the response. When true, adds standards sharing the same subject and level as the specified standard. Default: false."},{"name":"include_keywords","in":"query","required":false,"schema":{"type":"boolean","description":"Include associated keywords in the response. When true, adds keywords associated with the standard. Default: true.","default":true,"title":"Include Keywords"},"description":"Include associated keywords in the response. When true, adds keywords associated with the standard. Default: true."},{"name":"include_attainment","in":"query","required":false,"schema":{"type":"boolean","description":"Include attainment data in the response. When true, adds historical attainment statistics for the standard. Default: true.","default":true,"title":"Include Attainment"},"description":"Include attainment data in the response. When true, adds historical attainment statistics for the standard. Default: true."},{"name":"fields","in":"query","required":false,"schema":{"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level' returns only those fields. If omitted, all fields are returned.","title":"Fields","type":"string"},"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level' returns only those fields. If omitted, all fields are returned.","example":"number,description,subject,level"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/combination/subject/{subject_name}/resources":{"get":{"tags":["Combinations"],"summary":"Get Subject Resources Package","description":"Retrieve a comprehensive resource package for a subject including standards, files, and aggregated statistics.\n\n**Status**: This endpoint is currently under development and temporarily disabled. It will return a 410 Gone response.\n\n## Planned Functionality (When Enabled)\n\nWhen this endpoint is enabled, it will provide:\n\n- **Standards**: All standards in the subject with optional level filtering\n- **Files**: All files for the subject with optional year and file type filtering\n- **Attainment Summary**: Aggregated attainment statistics (not full records)\n- **Summary Statistics**: Comprehensive statistics about the subject's resources\n\n## Path Parameter\n\n- **subject_name**: Subject name (case-insensitive matching)\n  - Can be canonical name or variant name\n  - Examples: \"Physics\", \"Mathematics\", \"Chemistry\"\n\n## Query Parameters (Planned)\n\n- **level**: Filter by NCEA level (1, 2, or 3)\n- **year**: Filter by year (4-digit format)\n- **file_type**: Filter by file type (e.g., \"Exam\", \"Report\")\n- **include_attainment_summary**: Include aggregated attainment statistics\n- **fields**: Comma-separated fields to include in response\n\n## Example Request (When Enabled)\n\n```\nGET /v5/combination/subject/Physics/resources?level=3&include_attainment_summary=true\n```\n\n## Current Response\n\nReturns 410 Gone with message indicating the endpoint is under development.","operationId":"get_subject_resources_package_v5_combination_subject__subject_name__resources_get","parameters":[{"name":"subject_name","in":"path","required":true,"schema":{"type":"string","description":"Subject name for which to retrieve resources. Case-insensitive matching. Can be canonical name or variant name. Examples: 'Physics', 'Mathematics', 'Chemistry'.","title":"Subject Name"},"description":"Subject name for which to retrieve resources. Case-insensitive matching. Can be canonical name or variant name. Examples: 'Physics', 'Mathematics', 'Chemistry'.","example":"Physics"},{"name":"level","in":"query","required":false,"schema":{"description":"Filter resources by NCEA level. Valid values: 1, 2, or 3. Can be combined with other filters.","title":"Level","type":"integer","maximum":3,"minimum":1},"description":"Filter resources by NCEA level. Valid values: 1, 2, or 3. Can be combined with other filters.","example":3},{"name":"year","in":"query","required":false,"schema":{"description":"Filter resources by year. Must be 4-digit format (2000-2030). Examples: '2022', '2023', '2024'. Can be combined with other filters.","title":"Year","type":"string"},"description":"Filter resources by year. Must be 4-digit format (2000-2030). Examples: '2022', '2023', '2024'. Can be combined with other filters.","example":"2023"},{"name":"file_type","in":"query","required":false,"schema":{"description":"Filter resources by file type. Common values: 'Exam', 'Report', 'Schedule', 'Exemplar'. Case-insensitive matching. Can be combined with other filters.","title":"File Type","type":"string"},"description":"Filter resources by file type. Common values: 'Exam', 'Report', 'Schedule', 'Exemplar'. Case-insensitive matching. Can be combined with other filters.","example":"Exam"},{"name":"include_attainment_summary","in":"query","required":false,"schema":{"type":"boolean","description":"Include aggregated attainment summary statistics in the response. When true, adds summary statistics about achievement rates for the subject. Default: true.","default":true,"title":"Include Attainment Summary"},"description":"Include aggregated attainment summary statistics in the response. When true, adds summary statistics about achievement rates for the subject. Default: true."},{"name":"fields","in":"query","required":false,"schema":{"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level' returns only those fields. If omitted, all fields are returned.","title":"Fields","type":"string"},"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level' returns only those fields. If omitted, all fields are returned.","example":"number,description,subject,level"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/combination/exam/package":{"post":{"tags":["Combinations"],"summary":"Create Exam Package","description":"Generate a comprehensive exam package for a subject and level.\n\n**Status**: This endpoint is currently under development and temporarily disabled. It will return a 410 Gone response.\n\n## Planned Functionality (When Enabled)\n\nWhen this endpoint is enabled, it will generate a structured exam package containing:\n\n- **Exam Papers**: Examination papers for the specified subject and level\n- **Answer Schedules**: Answer schedules (if requested via request body)\n- **Assessment Schedules**: Assessment schedules (if requested via request body)\n- **Exemplars**: Exemplar materials (if requested via request body)\n\n## Request Body Structure (Planned)\n\n```json\n{\n  \"subject\": \"Physics\",\n  \"level\": 3,\n  \"years\": 3,\n  \"include_answers\": true,\n  \"include_schedules\": true,\n  \"include_exemplars\": false\n}\n```\n\n## Response Structure (Planned)\n\nReturns a structured package with files grouped by:\n- Year (most recent first)\n- File type (Exam, Schedule, Exemplar, etc.)\n\n## Current Response\n\nReturns 410 Gone with message indicating the endpoint is under development.","operationId":"create_exam_package_v5_combination_exam_package_post","parameters":[{"name":"fields","in":"query","required":false,"schema":{"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level' returns only those fields. If omitted, all fields are returned.","title":"Fields","type":"string"},"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level' returns only those fields. If omitted, all fields are returned.","example":"number,description,subject,level"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExamPackageRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/combination/study/pack":{"post":{"tags":["Combinations"],"summary":"Create Study Pack","description":"Generate a comprehensive study pack for multiple standards.\n\n**Status**: This endpoint is currently under development and temporarily disabled. It will return a 410 Gone response.\n\n## Planned Functionality (When Enabled)\n\nWhen this endpoint is enabled, it will generate a structured study pack containing:\n\n- **Files**: All relevant files for the specified standards\n- **File Type Filtering**: Optional filtering by file types\n- **Year Limiting**: Limits to most recent N years (default: 5)\n- **Grouping**: Files grouped by standard number for organized structure\n- **Difficulty Insights**: Optional attainment-based difficulty scores (if requested)\n- **Preferences**: Applies user preferences for file prioritization\n\n## Request Body Structure (Planned)\n\n```json\n{\n  \"standard_numbers\": [\"91524\", \"91525\", \"91526\"],\n  \"file_types\": [\"Exam\", \"Report\"],\n  \"years\": 5,\n  \"include_difficulty_insights\": true,\n  \"preferences\": {\n    \"prioritize_recent_years\": true,\n    \"max_files_per_standard\": 10\n  }\n}\n```\n\n## Response Structure (Planned)\n\nReturns a structured study pack with:\n- Files organized by standard number\n- Optional difficulty insights based on attainment data\n- Metadata about included resources\n- Summary statistics\n\n## Current Response\n\nReturns 410 Gone with message indicating the endpoint is under development.","operationId":"create_study_pack_v5_combination_study_pack_post","parameters":[{"name":"fields","in":"query","required":false,"schema":{"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level' returns only those fields. If omitted, all fields are returned.","title":"Fields","type":"string"},"description":"Comma-separated list of fields to include in response. Reduces payload size by limiting returned data. Example: 'number,description,subject,level' returns only those fields. If omitted, all fields are returned.","example":"number,description,subject,level"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudyPackRequestV2"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/bulk/standards":{"get":{"tags":["Bulk"],"summary":"Bulk Get Standards Query","description":"Retrieve multiple standards via GET request with comma-separated standard numbers.\n\nThis is a convenience endpoint for bulk standard retrieval using GET requests.\nFor more complex bulk operations, use the POST endpoint.\n\n## Query Parameters\n\n- **standard_numbers**: Comma-separated list of 5-digit standard numbers (e.g., \"91524,91525,91526\")\n  - Required parameter\n  - Maximum 100 standards per request\n  - Invalid standard numbers are filtered out\n\n- **fields**: Comma-separated list of fields to include in response (optional)\n  - Example: \"number,description,subject,level,credits\"\n  - Reduces payload size\n\n- **include_files**: Include associated files for each standard (default: true)\n  - When true, returns files array with each standard\n  - When false, files field is null\n\n## Examples\n\n**Get multiple standards:**\n```\nGET /v5/bulk/standards?standard_numbers=91524,91525,91526\n```\n\n**With field selection:**\n```\nGET /v5/bulk/standards?standard_numbers=91524,91525&fields=number,description,subject,level\n```\n\n**Without files:**\n```\nGET /v5/bulk/standards?standard_numbers=91524,91525&include_files=false\n```\n\n## Response Structure\n\nReturns the same structure as the POST endpoint with standards array and metadata.","operationId":"bulk_get_standards_query_v5_bulk_standards_get","parameters":[{"name":"standard_numbers","in":"query","required":false,"schema":{"type":"string","title":"Standard Numbers"}},{"name":"fields","in":"query","required":false,"schema":{"type":"string","title":"Fields"}},{"name":"include_files","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include Files"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Bulk"],"summary":"Bulk Get Standards","description":"Retrieve multiple standards in a single request by providing a list of standard numbers.\n\n**SECURITY**: This endpoint has stricter rate limiting (20 requests/minute) due to resource intensity.\nBulk requests are validated for size limits to prevent abuse.\n\n**Rate Limiting**: \n- Public: 20 requests/minute\n- Authenticated: 20 requests/minute (stricter than regular endpoints)\n- Admin: 20 requests/minute\n\n**Request Size Limits**:\n- Maximum 100 standard numbers per request\n- Requests exceeding limits return 400 error with helpful message\n\nThis endpoint allows efficient retrieval of multiple standards without making individual\n\nThis endpoint allows efficient retrieval of multiple standards without making individual\nAPI calls. Supports error handling with optional continuation on errors, field selection,\nand detailed response metadata including success/failure counts.\n\n## Request Body\n\nThe request body must be JSON with the following structure:\n\n```json\n{\n  \"standard_numbers\": [\"91524\", \"91525\", \"91526\"],\n  \"continue_on_error\": true\n}\n```\n\n- **standard_numbers**: Array of standard numbers (5-digit format, e.g., \"91524\")\n  - Required field\n  - Maximum 100 standards per request\n  - Invalid standard numbers are filtered out before processing\n- **continue_on_error**: Boolean flag (default: true)\n  - If `true`: Continues processing remaining standards even if some fail\n  - If `false`: Stops processing on first error (not recommended for bulk operations)\n\n## Response Structure\n\nReturns a comprehensive response including:\n- `standards`: Array of successfully retrieved standard objects\n- `total_requested`: Total number of standard numbers in the request\n- `total_processed`: Total number of standards processed (successful + failed)\n- `successful`: Count of successfully retrieved standards\n- `failed`: Count of failed standard retrievals\n- `errors`: Array of error objects for failed standards (if any)\n- `processing_time_ms`: Request processing time in milliseconds\n- `unsupported_fields`: Array of field names that were requested but not supported (if field selection used)\n\n## Examples\n\n**Basic bulk request:**\n```json\nPOST /v5/bulk/standards\nContent-Type: application/json\n\n{\n  \"standard_numbers\": [\"91524\", \"91525\", \"91526\"]\n}\n```\n\n**With error handling:**\n```json\nPOST /v5/bulk/standards\nContent-Type: application/json\n\n{\n  \"standard_numbers\": [\"91524\", \"91525\", \"99999\", \"91526\"],\n  \"continue_on_error\": true\n}\n```\n\n**With field selection:**\n```json\nPOST /v5/bulk/standards?fields=number,description,subject,level\nContent-Type: application/json\n\n{\n  \"standard_numbers\": [\"91524\", \"91525\", \"91526\"]\n}\n```\n\n## Error Handling\n\nWhen `continue_on_error` is `true`:\n- Invalid standard numbers are skipped\n- Standards that don't exist are included in the `errors` array\n- Processing continues for remaining valid standards\n- Response includes both successful results and error details\n\nEach error object includes:\n- `standard_number`: The standard number that failed\n- `error`: Error message describing the failure reason\n\n## Field Selection\n\nUse the `fields` query parameter to limit response data:\n```\nPOST /v5/bulk/standards?fields=number,description,subject,level,credits\n```\n\n## Performance\n\n- Batch database queries are used for efficiency\n- Processing time is included in response metadata\n- Maximum 100 standards per request to prevent timeouts\n- Invalid standard numbers are filtered before processing\n\n## Use Cases\n\n- Retrieving multiple standards for a study pack\n- Building subject-specific standard lists\n- Batch data processing and analysis\n- Efficient API usage when multiple standards are needed","operationId":"bulk_get_standards_v5_bulk_standards_post","parameters":[{"name":"fields","in":"query","required":false,"schema":{"type":"string","title":"Fields"}},{"name":"include_files","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include Files"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkStandardsRequestV2"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/bulk/search":{"post":{"tags":["Bulk"],"summary":"Bulk Search","description":"Execute multiple search queries in a single request for efficient batch searching.\n\nThis endpoint allows executing multiple search queries without making individual\nAPI calls. Supports error handling with optional continuation on errors, field selection,\nand detailed response metadata including success/failure counts for each query.\n\n## Request Body\n\nThe request body must be JSON with the following structure:\n\n```json\n{\n  \"queries\": [\"physics exam\", \"mathematics level 3\", \"chemistry 2023\"],\n  \"continue_on_error\": true\n}\n```\n\n- **queries**: Array of search query strings\n  - Required field\n  - Maximum 50 queries per request\n  - Each query supports the same advanced operators as the main search endpoint\n  - Invalid or empty queries are filtered out before processing\n- **continue_on_error**: Boolean flag (default: true)\n  - If `true`: Continues processing remaining queries even if some fail\n  - If `false`: Stops processing on first error (not recommended for bulk operations)\n\n## Response Structure\n\nReturns a comprehensive response including:\n- `queries`: Array of query result objects, each containing:\n  - `query`: The original search query\n  - `results`: Array of search results for that query\n  - `total_results`: Total number of results found\n  - `filters_applied`: Filters that were applied to the query\n- `total_requested`: Total number of queries in the request\n- `total_processed`: Total number of queries processed (successful + failed)\n- `successful`: Count of successfully executed queries\n- `failed`: Count of failed queries\n- `errors`: Array of error objects for failed queries (if any)\n- `processing_time_ms`: Total request processing time in milliseconds\n- `unsupported_fields`: Array of field names that were requested but not supported (if field selection used)\n\n## Examples\n\n**Basic bulk search:**\n```json\nPOST /v5/bulk/search\nContent-Type: application/json\n\n{\n  \"queries\": [\"physics exam\", \"mathematics level 3\", \"chemistry\"]\n}\n```\n\n**With error handling:**\n```json\nPOST /v5/bulk/search\nContent-Type: application/json\n\n{\n  \"queries\": [\"physics exam\", \"\", \"mathematics level 3\"],\n  \"continue_on_error\": true\n}\n```\n\n**With field selection:**\n```json\nPOST /v5/bulk/search?fields=id,file_name,file_path\nContent-Type: application/json\n\n{\n  \"queries\": [\"physics\", \"mathematics\", \"chemistry\"]\n}\n```\n\n## Error Handling\n\nWhen `continue_on_error` is `true`:\n- Invalid queries are skipped\n- Queries that fail are included in the `errors` array\n- Processing continues for remaining valid queries\n- Response includes both successful results and error details\n\nEach error object includes:\n- `query`: The query that failed\n- `error`: Error message describing the failure reason\n\n## Field Selection\n\nUse the `fields` query parameter to limit response data for all queries:\n```\nPOST /v5/bulk/search?fields=id,file_name,file_path,subject\n```\n\n## Performance\n\n- Queries are processed sequentially to maintain result quality\n- Processing time is included in response metadata\n- Maximum 50 queries per request to prevent timeouts\n- Invalid queries are filtered before processing\n\n## Use Cases\n\n- Batch searching across multiple topics\n- Comparing search results for different queries\n- Efficient API usage when multiple searches are needed\n- Building search result collections","operationId":"bulk_search_v5_bulk_search_post","parameters":[{"name":"fields","in":"query","required":false,"schema":{"type":"string","title":"Fields"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkSearchRequestV2"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/accounts/register":{"post":{"tags":["Accounts"],"summary":"Register Account","description":"Register a new user account.\n\nCreates a new user account with the provided information. Email must be unique.\nPassword will be securely hashed using bcrypt. Account is created with 'active' status\nand 'session' tier by default (unless specified otherwise).\n\n## Request Body\n\n- `email`: Account email address (required, must be valid email format)\n- `password`: Account password (required, minimum 8 characters)\n- `username`: Optional username (3-50 characters, alphanumeric, underscores, hyphens)\n- `full_name`: Optional full name\n- `organization`: Optional organization name\n- `tier`: Account tier (default: \"session\", valid: session, basic, premium, unlimited, admin)\n\n## Response Data\n\n- `account_id`: Unique account identifier\n- `email`: Account email address\n- `username`: Account username (if provided)\n- `tier`: Account tier\n- `status`: Account status (always \"active\" for new accounts)\n- `email_verified`: Email verification status (always false for new accounts)\n- `created_at`: Account creation timestamp\n\n## Example Request\n\n```json\n{\n  \"email\": \"user@example.com\",\n  \"password\": \"securepassword123\",\n  \"username\": \"johndoe\",\n  \"full_name\": \"John Doe\",\n  \"organization\": \"Example Corp\",\n  \"tier\": \"session\"\n}\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Account created successfully\",\n  \"data\": {\n    \"account_id\": \"acc_abc123def456\",\n    \"email\": \"user@example.com\",\n    \"username\": \"johndoe\",\n    \"full_name\": \"John Doe\",\n    \"organization\": \"Example Corp\",\n    \"tier\": \"session\",\n    \"status\": \"active\",\n    \"email_verified\": false,\n    \"created_at\": \"2025-12-21T12:00:00.000000+00:00\"\n  }\n}\n```\n\n## Error Responses\n\n- **400 Bad Request**: Invalid input data or validation errors\n- **409 Conflict**: Email or username already exists\n- **500 Internal Server Error**: Server error (generic message)","operationId":"register_account_v5_accounts_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/accounts/login":{"post":{"tags":["Accounts"],"summary":"Login Account","description":"Authenticate and login to an account.\n\nVerifies email and password credentials. On successful authentication, updates\nthe account's last login timestamp. Returns account information (excluding\npassword hash).\n\n## Request Body\n\n- `email`: Account email address (required)\n- `password`: Account password (required)\n\n## Response Data\n\n- `account_id`: Unique account identifier\n- `email`: Account email address\n- `username`: Account username\n- `tier`: Account tier\n- `status`: Account status\n- `last_login`: Updated last login timestamp\n\n## Example Request\n\n```json\n{\n  \"email\": \"user@example.com\",\n  \"password\": \"securepassword123\"\n}\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Login successful\",\n  \"data\": {\n    \"account_id\": \"acc_abc123def456\",\n    \"email\": \"user@example.com\",\n    \"username\": \"johndoe\",\n    \"tier\": \"session\",\n    \"status\": \"active\",\n    \"last_login\": \"2025-12-21T12:00:00.000000+00:00\"\n  }\n}\n```\n\n## Error Responses\n\n- **401 Unauthorized**: Invalid email or password, or account is not active\n- **500 Internal Server Error**: Server error (generic message)","operationId":"login_account_v5_accounts_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountLogin"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/accounts/me":{"get":{"tags":["Accounts"],"summary":"Get Current Account","description":"Get current authenticated account information.\n\nReturns the account information for the currently authenticated user.\nRequires authentication via API key or session token. The account ID is\nextracted from the authentication information.\n\n## Authentication\n\nRequires authentication via:\n- API Key: `X-API-Key` header or `Authorization: Bearer` header\n- Session Token: `Authorization: Bearer` header or session cookie\n\n## Response Data\n\nReturns full account information including:\n- Account ID, email, username\n- Full name, organization\n- Tier, status\n- Email verification status\n- Creation and update timestamps\n- Last login timestamp\n- Total requests count\n- Account preferences\n\n## Example Request\n\n```\nGET /v5/accounts/me\nAuthorization: Bearer YOUR_API_KEY\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Account retrieved successfully\",\n  \"data\": {\n    \"account_id\": \"acc_abc123def456\",\n    \"email\": \"user@example.com\",\n    \"username\": \"johndoe\",\n    \"full_name\": \"John Doe\",\n    \"organization\": \"Example Corp\",\n    \"tier\": \"session\",\n    \"status\": \"active\",\n    \"email_verified\": false,\n    \"created_at\": \"2025-12-21T12:00:00.000000+00:00\",\n    \"updated_at\": \"2025-12-21T12:00:00.000000+00:00\",\n    \"last_login\": \"2025-12-21T12:00:00.000000+00:00\",\n    \"total_requests\": 42,\n    \"preferences\": {}\n  }\n}\n```\n\n## Error Responses\n\n- **401 Unauthorized**: Authentication required\n- **404 Not Found**: Account not found (should not happen if auth is valid)\n- **500 Internal Server Error**: Server error (generic message)\n\n## Notes\n\n- Account ID is extracted from authentication token/key\n- Password hash is never included in response\n- Deleted accounts are not returned","operationId":"get_current_account_v5_accounts_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_Account_"}}}}}},"put":{"tags":["Accounts"],"summary":"Update Current Account","description":"Update current authenticated account information.\n\nUpdates the account information for the currently authenticated user.\nOnly provided fields will be updated. Email and username changes require\nuniqueness validation.\n\n## Authentication\n\nRequires authentication via API key or session token.\n\n## Request Body\n\nAll fields are optional. Only provided fields will be updated:\n\n- `email`: New email address (must be unique)\n- `username`: New username (must be unique)\n- `password`: New password (will be hashed)\n- `full_name`: New full name\n- `organization`: New organization name\n- `tier`: New tier (admin only)\n- `status`: New status (admin only)\n- `email_verified`: Email verification status (admin only)\n- `preferences`: Account preferences JSON object\n\n## Example Request\n\n```json\n{\n  \"full_name\": \"John A. Doe\",\n  \"organization\": \"New Corp\"\n}\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Account updated successfully\",\n  \"data\": {\n    \"account_id\": \"acc_abc123def456\",\n    \"email\": \"user@example.com\",\n    \"full_name\": \"John A. Doe\",\n    \"organization\": \"New Corp\",\n    ...\n  }\n}\n```\n\n## Error Responses\n\n- **400 Bad Request**: Invalid input data or validation errors\n- **401 Unauthorized**: Authentication required\n- **404 Not Found**: Account not found\n- **409 Conflict**: Email or username already in use\n- **500 Internal Server Error**: Server error (generic message)","operationId":"update_current_account_v5_accounts_me_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_Account_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/accounts/{account_id}":{"get":{"tags":["Accounts"],"summary":"Get Account By Id","description":"Get account by ID.\n\nReturns account information for the specified account ID. Users can only\naccess their own account unless they have admin privileges.\n\n## Path Parameter\n\n- `account_id`: Account ID (must start with \"acc_\")\n\n## Authentication\n\nRequires authentication. Users can only view their own account unless admin.\n\n## Example Request\n\n```\nGET /v5/accounts/acc_abc123def456\nAuthorization: Bearer YOUR_API_KEY\n```\n\n## Error Responses\n\n- **401 Unauthorized**: Authentication required\n- **403 Forbidden**: Not authorized to view this account\n- **404 Not Found**: Account not found\n- **500 Internal Server Error**: Server error (generic message)","operationId":"get_account_by_id_v5_accounts__account_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"Account ID to retrieve. Must start with 'acc_' prefix.","title":"Account Id"},"description":"Account ID to retrieve. Must start with 'acc_' prefix.","example":"acc_abc123def456"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_Account_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"Api Root V3 Compat","operationId":"api_root_v3_compat_api_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v3":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Info Endpoint","operationId":"v3_info_endpoint_v3_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Info Endpoint","operationId":"v3_info_endpoint_api_v3_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/live":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Live Endpoint","operationId":"v3_live_endpoint_v3_live_get","parameters":[{"name":"detailed","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Detailed"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/live":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Live Endpoint","operationId":"v3_live_endpoint_api_v3_live_get","parameters":[{"name":"detailed","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Detailed"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/files":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Files Endpoint","operationId":"v3_files_endpoint_v3_files_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"search","in":"query","required":false,"schema":{"title":"Search","type":"string"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"standard_number","in":"query","required":false,"schema":{"title":"Standard Number","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"string"}},{"name":"assessment_type","in":"query","required":false,"schema":{"title":"Assessment Type","type":"string"}},{"name":"file_type","in":"query","required":false,"schema":{"title":"File Type","type":"string"}},{"name":"format_filter","in":"query","required":false,"schema":{"title":"Format Filter","type":"string"}},{"name":"min_size","in":"query","required":false,"schema":{"title":"Min Size","type":"integer"}},{"name":"max_size","in":"query","required":false,"schema":{"title":"Max Size","type":"integer"}},{"name":"expired","in":"query","required":false,"schema":{"title":"Expired","type":"string"}},{"name":"literacy","in":"query","required":false,"schema":{"title":"Literacy","type":"string"}},{"name":"numeracy","in":"query","required":false,"schema":{"title":"Numeracy","type":"string"}},{"name":"te_reo_matatini","in":"query","required":false,"schema":{"title":"Te Reo Matatini","type":"string"}},{"name":"has_errors","in":"query","required":false,"schema":{"title":"Has Errors","type":"string"}},{"name":"has_warnings","in":"query","required":false,"schema":{"title":"Has Warnings","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/files":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Files Endpoint","operationId":"v3_files_endpoint_api_v3_files_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"search","in":"query","required":false,"schema":{"title":"Search","type":"string"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"standard_number","in":"query","required":false,"schema":{"title":"Standard Number","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"string"}},{"name":"assessment_type","in":"query","required":false,"schema":{"title":"Assessment Type","type":"string"}},{"name":"file_type","in":"query","required":false,"schema":{"title":"File Type","type":"string"}},{"name":"format_filter","in":"query","required":false,"schema":{"title":"Format Filter","type":"string"}},{"name":"min_size","in":"query","required":false,"schema":{"title":"Min Size","type":"integer"}},{"name":"max_size","in":"query","required":false,"schema":{"title":"Max Size","type":"integer"}},{"name":"expired","in":"query","required":false,"schema":{"title":"Expired","type":"string"}},{"name":"literacy","in":"query","required":false,"schema":{"title":"Literacy","type":"string"}},{"name":"numeracy","in":"query","required":false,"schema":{"title":"Numeracy","type":"string"}},{"name":"te_reo_matatini","in":"query","required":false,"schema":{"title":"Te Reo Matatini","type":"string"}},{"name":"has_errors","in":"query","required":false,"schema":{"title":"Has Errors","type":"string"}},{"name":"has_warnings","in":"query","required":false,"schema":{"title":"Has Warnings","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/files/{standard_number}":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Files By Standard","operationId":"v3_files_by_standard_v3_files__standard_number__get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","title":"Standard Number"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/files/{standard_number}":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Files By Standard","operationId":"v3_files_by_standard_api_v3_files__standard_number__get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","title":"Standard Number"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/files/{standard_number}/{year}":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 File By Standard Year","operationId":"v3_file_by_standard_year_v3_files__standard_number___year__get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","title":"Standard Number"}},{"name":"year","in":"path","required":true,"schema":{"type":"integer","title":"Year"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/files/{standard_number}/{year}":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 File By Standard Year","operationId":"v3_file_by_standard_year_api_v3_files__standard_number___year__get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","title":"Standard Number"}},{"name":"year","in":"path","required":true,"schema":{"type":"integer","title":"Year"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/files/{standard_number}/cdn-urls":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Cdn Urls By Standard","operationId":"v3_cdn_urls_by_standard_v3_files__standard_number__cdn_urls_get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","title":"Standard Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/files/{standard_number}/cdn-urls":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Cdn Urls By Standard","operationId":"v3_cdn_urls_by_standard_api_v3_files__standard_number__cdn_urls_get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","title":"Standard Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/subjects":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Subjects Endpoint","operationId":"v3_subjects_endpoint_v3_subjects_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":2000,"title":"Limit"}},{"name":"include_level_range","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Level Range"}},{"name":"include_file_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include File Count"}},{"name":"include_standard_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include Standard Count"}},{"name":"search","in":"query","required":false,"schema":{"title":"Search","type":"string"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/subjects":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Subjects Endpoint","operationId":"v3_subjects_endpoint_api_v3_subjects_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":2000,"title":"Limit"}},{"name":"include_level_range","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Level Range"}},{"name":"include_file_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include File Count"}},{"name":"include_standard_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include Standard Count"}},{"name":"search","in":"query","required":false,"schema":{"title":"Search","type":"string"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/subjects/{subject}/files":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Subject Files","operationId":"v3_subject_files_v3_subjects__subject__files_get","parameters":[{"name":"subject","in":"path","required":true,"schema":{"type":"string","title":"Subject"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"string"}},{"name":"file_type","in":"query","required":false,"schema":{"title":"File Type","type":"string"}},{"name":"file_format","in":"query","required":false,"schema":{"title":"File Format","type":"string"}},{"name":"min_credits","in":"query","required":false,"schema":{"title":"Min Credits","type":"integer"}},{"name":"max_credits","in":"query","required":false,"schema":{"title":"Max Credits","type":"integer"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/subjects/{subject}/files":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Subject Files","operationId":"v3_subject_files_api_v3_subjects__subject__files_get","parameters":[{"name":"subject","in":"path","required":true,"schema":{"type":"string","title":"Subject"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"string"}},{"name":"file_type","in":"query","required":false,"schema":{"title":"File Type","type":"string"}},{"name":"file_format","in":"query","required":false,"schema":{"title":"File Format","type":"string"}},{"name":"min_credits","in":"query","required":false,"schema":{"title":"Min Credits","type":"integer"}},{"name":"max_credits","in":"query","required":false,"schema":{"title":"Max Credits","type":"integer"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/subjects/{subject}/levels":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Subject Levels","operationId":"v3_subject_levels_v3_subjects__subject__levels_get","parameters":[{"name":"subject","in":"path","required":true,"schema":{"type":"string","title":"Subject"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/subjects/{subject}/levels":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Subject Levels","operationId":"v3_subject_levels_api_v3_subjects__subject__levels_get","parameters":[{"name":"subject","in":"path","required":true,"schema":{"type":"string","title":"Subject"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/standards":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Standards Endpoint","operationId":"v3_standards_endpoint_v3_standards_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"include_file_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include File Count"}},{"name":"include_years","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Years"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"integer"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}},{"name":"search","in":"query","required":false,"schema":{"title":"Search","type":"string"}},{"name":"credits","in":"query","required":false,"schema":{"title":"Credits","type":"integer"}},{"name":"expired","in":"query","required":false,"schema":{"title":"Expired","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/standards":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Standards Endpoint","operationId":"v3_standards_endpoint_api_v3_standards_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"include_file_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include File Count"}},{"name":"include_years","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Years"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"integer"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}},{"name":"search","in":"query","required":false,"schema":{"title":"Search","type":"string"}},{"name":"credits","in":"query","required":false,"schema":{"title":"Credits","type":"integer"}},{"name":"expired","in":"query","required":false,"schema":{"title":"Expired","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/years":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Years Endpoint","operationId":"v3_years_endpoint_v3_years_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"include_file_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include File Count"}},{"name":"include_subject_count","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Subject Count"}},{"name":"include_standard_count","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Standard Count"}},{"name":"min_year","in":"query","required":false,"schema":{"title":"Min Year","type":"integer"}},{"name":"max_year","in":"query","required":false,"schema":{"title":"Max Year","type":"integer"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/years":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Years Endpoint","operationId":"v3_years_endpoint_api_v3_years_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"include_file_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include File Count"}},{"name":"include_subject_count","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Subject Count"}},{"name":"include_standard_count","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Standard Count"}},{"name":"min_year","in":"query","required":false,"schema":{"title":"Min Year","type":"integer"}},{"name":"max_year","in":"query","required":false,"schema":{"title":"Max Year","type":"integer"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/keywords":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Keywords Endpoint","operationId":"v3_keywords_endpoint_v3_keywords_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"include_file_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include File Count"}},{"name":"include_subjects","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Subjects"}},{"name":"include_years","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Years"}},{"name":"include_standards","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Standards"}},{"name":"search","in":"query","required":false,"schema":{"title":"Search","type":"string"}},{"name":"min_files","in":"query","required":false,"schema":{"title":"Min Files","type":"integer"}},{"name":"max_files","in":"query","required":false,"schema":{"title":"Max Files","type":"integer"}},{"name":"year_filter","in":"query","required":false,"schema":{"title":"Year Filter","type":"string"}},{"name":"subject_filter","in":"query","required":false,"schema":{"title":"Subject Filter","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/keywords":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Keywords Endpoint","operationId":"v3_keywords_endpoint_api_v3_keywords_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"include_file_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include File Count"}},{"name":"include_subjects","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Subjects"}},{"name":"include_years","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Years"}},{"name":"include_standards","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Standards"}},{"name":"search","in":"query","required":false,"schema":{"title":"Search","type":"string"}},{"name":"min_files","in":"query","required":false,"schema":{"title":"Min Files","type":"integer"}},{"name":"max_files","in":"query","required":false,"schema":{"title":"Max Files","type":"integer"}},{"name":"year_filter","in":"query","required":false,"schema":{"title":"Year Filter","type":"string"}},{"name":"subject_filter","in":"query","required":false,"schema":{"title":"Subject Filter","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/file-types":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 File Types Endpoint","operationId":"v3_file_types_endpoint_v3_file_types_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"include_file_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include File Count"}},{"name":"include_subjects","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Subjects"}},{"name":"include_years","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Years"}},{"name":"search","in":"query","required":false,"schema":{"title":"Search","type":"string"}},{"name":"min_files","in":"query","required":false,"schema":{"title":"Min Files","type":"integer"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/file-types":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 File Types Endpoint","operationId":"v3_file_types_endpoint_api_v3_file_types_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"include_file_count","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include File Count"}},{"name":"include_subjects","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Subjects"}},{"name":"include_years","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Years"}},{"name":"search","in":"query","required":false,"schema":{"title":"Search","type":"string"}},{"name":"min_files","in":"query","required":false,"schema":{"title":"Min Files","type":"integer"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/search":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Search Endpoint","operationId":"v3_search_endpoint_v3_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","title":"Q"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"include_facets","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Facets"}},{"name":"include_suggestions","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Suggestions"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"integer"}},{"name":"file_type","in":"query","required":false,"schema":{"title":"File Type","type":"string"}},{"name":"min_score","in":"query","required":false,"schema":{"title":"Min Score","type":"number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/search":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Search Endpoint","operationId":"v3_search_endpoint_api_v3_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","title":"Q"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"sort","in":"query","required":false,"schema":{"title":"Sort","type":"string"}},{"name":"include_facets","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Facets"}},{"name":"include_suggestions","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Suggestions"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"integer"}},{"name":"file_type","in":"query","required":false,"schema":{"title":"File Type","type":"string"}},{"name":"min_score","in":"query","required":false,"schema":{"title":"Min Score","type":"number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/metrics":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Endpoint","operationId":"v3_metrics_endpoint_v3_metrics_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"category","in":"query","required":false,"schema":{"type":"string","default":"all","title":"Category"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/metrics":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Endpoint","operationId":"v3_metrics_endpoint_api_v3_metrics_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"category","in":"query","required":false,"schema":{"type":"string","default":"all","title":"Category"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/search/columns":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Search Columns","operationId":"v3_search_columns_v3_search_columns_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/search/columns":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Search Columns","operationId":"v3_search_columns_api_v3_search_columns_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/query":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Query Endpoint","operationId":"v3_query_endpoint_v3_query_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"select","in":"query","required":false,"schema":{"title":"Select","type":"string"}},{"name":"where","in":"query","required":false,"schema":{"title":"Where","type":"string"}},{"name":"order_by","in":"query","required":false,"schema":{"title":"Order By","type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/query":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Query Endpoint","operationId":"v3_query_endpoint_api_v3_query_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"select","in":"query","required":false,"schema":{"title":"Select","type":"string"}},{"name":"where","in":"query","required":false,"schema":{"title":"Where","type":"string"}},{"name":"order_by","in":"query","required":false,"schema":{"title":"Order By","type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/db/stats":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Db Stats","operationId":"v3_db_stats_v3_db_stats_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/db/stats":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Db Stats","operationId":"v3_db_stats_api_v3_db_stats_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/keywords/{keyword}/files":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Keyword Files","operationId":"v3_keyword_files_v3_keywords__keyword__files_get","parameters":[{"name":"keyword","in":"path","required":true,"schema":{"type":"string","title":"Keyword"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/keywords/{keyword}/files":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Keyword Files","operationId":"v3_keyword_files_api_v3_keywords__keyword__files_get","parameters":[{"name":"keyword","in":"path","required":true,"schema":{"type":"string","title":"Keyword"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/cdn/{subject}":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Cdn Subject","operationId":"v3_cdn_subject_v3_cdn__subject__get","parameters":[{"name":"subject","in":"path","required":true,"schema":{"type":"string","title":"Subject"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}},{"name":"type","in":"query","required":false,"schema":{"title":"Type","type":"string"}},{"name":"format","in":"query","required":false,"schema":{"title":"Format","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/cdn/{subject}":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Cdn Subject","operationId":"v3_cdn_subject_api_v3_cdn__subject__get","parameters":[{"name":"subject","in":"path","required":true,"schema":{"type":"string","title":"Subject"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}},{"name":"type","in":"query","required":false,"schema":{"title":"Type","type":"string"}},{"name":"format","in":"query","required":false,"schema":{"title":"Format","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/metrics/warnings":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Warnings","operationId":"v3_metrics_warnings_v3_metrics_warnings_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/metrics/warnings":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Warnings","operationId":"v3_metrics_warnings_api_v3_metrics_warnings_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/metrics/errors":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Errors","operationId":"v3_metrics_errors_v3_metrics_errors_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"include_files","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Files"}},{"name":"error_type","in":"query","required":false,"schema":{"title":"Error Type","type":"string"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/metrics/errors":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Errors","operationId":"v3_metrics_errors_api_v3_metrics_errors_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"include_files","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Files"}},{"name":"error_type","in":"query","required":false,"schema":{"title":"Error Type","type":"string"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/metrics/expired":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Expired","operationId":"v3_metrics_expired_v3_metrics_expired_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"include_files","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Files"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"integer"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/metrics/expired":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Expired","operationId":"v3_metrics_expired_api_v3_metrics_expired_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"include_files","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Files"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"level","in":"query","required":false,"schema":{"title":"Level","type":"integer"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/metrics/unknown-years":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Unknown Years","operationId":"v3_metrics_unknown_years_v3_metrics_unknown_years_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/metrics/unknown-years":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Unknown Years","operationId":"v3_metrics_unknown_years_api_v3_metrics_unknown_years_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/metrics/duplicates/relative-path":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Duplicates Relative Path","operationId":"v3_metrics_duplicates_relative_path_v3_metrics_duplicates_relative_path_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/metrics/duplicates/relative-path":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Duplicates Relative Path","operationId":"v3_metrics_duplicates_relative_path_api_v3_metrics_duplicates_relative_path_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/metrics/duplicates":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Duplicates","operationId":"v3_metrics_duplicates_v3_metrics_duplicates_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"min_duplicates","in":"query","required":false,"schema":{"type":"integer","minimum":2,"default":2,"title":"Min Duplicates"}},{"name":"include_files","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Files"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/metrics/duplicates":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Metrics Duplicates","operationId":"v3_metrics_duplicates_api_v3_metrics_duplicates_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":50,"title":"Limit"}},{"name":"min_duplicates","in":"query","required":false,"schema":{"type":"integer","minimum":2,"default":2,"title":"Min Duplicates"}},{"name":"include_files","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Files"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}},{"name":"year","in":"query","required":false,"schema":{"title":"Year","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/attainment/batch":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Attainment Batch","operationId":"v3_attainment_batch_v3_attainment_batch_get","parameters":[{"name":"standards","in":"query","required":true,"schema":{"type":"string","title":"Standards"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/attainment/batch":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Attainment Batch","operationId":"v3_attainment_batch_api_v3_attainment_batch_get","parameters":[{"name":"standards","in":"query","required":true,"schema":{"type":"string","title":"Standards"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/attainment/summary/{year}":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Attainment Summary Year","operationId":"v3_attainment_summary_year_v3_attainment_summary__year__get","parameters":[{"name":"year","in":"path","required":true,"schema":{"type":"string","title":"Year"}},{"name":"min_students","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Min Students"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/attainment/summary/{year}":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Attainment Summary Year","operationId":"v3_attainment_summary_year_api_v3_attainment_summary__year__get","parameters":[{"name":"year","in":"path","required":true,"schema":{"type":"string","title":"Year"}},{"name":"min_students","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Min Students"}},{"name":"subject","in":"query","required":false,"schema":{"title":"Subject","type":"string"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/attainment/{standard_number}/stats":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Attainment Stats","operationId":"v3_attainment_stats_v3_attainment__standard_number__stats_get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","title":"Standard Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/attainment/{standard_number}/stats":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Attainment Stats","operationId":"v3_attainment_stats_api_v3_attainment__standard_number__stats_get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","title":"Standard Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v3/attainment/{standard_number}":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Attainment Flat","operationId":"v3_attainment_flat_v3_attainment__standard_number__get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","title":"Standard Number"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v3/attainment/{standard_number}":{"get":{"tags":["Legacy v3 Compatibility"],"summary":"V3 Attainment Flat","operationId":"v3_attainment_flat_api_v3_attainment__standard_number__get","parameters":[{"name":"standard_number","in":"path","required":true,"schema":{"type":"string","title":"Standard Number"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|csv|urls)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/auth/session":{"post":{"tags":["Auth"],"summary":"Create Session","description":"Create a new browser session token (public).\n\nIssues a session-tier token (500 requests/minute) and sets the `nzqa_session`\ncookie. School labs sharing one NAT IP can create many sessions at once;\na single browser/device is still capped more tightly.\n\n## Request Body\n\n- `device_name`: Optional label (e.g. \"Chrome on Windows\"), max 200 characters\n- `device_fingerprint`: Optional per-browser id, max 500 characters. Send a\n  unique value per student device — a shared constant will hit the tighter\n  per-device cap. Omit if you do not have one.\n- `tier`: Must be `\"session\"` or `\"free\"` (`free` is mapped to `session`)\n- `custom_rate_limit`: Not allowed on this public endpoint\n- `expires_at`: Optional ISO 8601 expiry; maximum 30 days from now\n\n## Response Data\n\n- `token`: Session JWT — store and send as `Authorization: Bearer <token>`\n  on later API calls (required for cross-origin SPAs; the cookie is host-only\n  on the API host and is not visible to `nzqa.toasting.me`)\n- `access_token` / `session_token`: Same JWT (aliases)\n- `token_type`: Always `\"Bearer\"`\n- `session_id`: Server-side session identifier\n- `expires_at`: Session expiration timestamp\n- `tier`: Always `\"session\"`\n- `rate_limit`: Requests per minute for this session (500)\n- `message`: Confirmation text\n\nThe JWT is also set on the `nzqa_session` cookie (HttpOnly) for same-site\nbrowser traffic. Do **not** rely on that cookie alone from a different\nsubdomain — use `data.token` with Bearer auth.\n\n## Example Request\n\n```\nPOST /v5/auth/session\nContent-Type: application/json\n\n{\n  \"device_name\": \"Chrome on Windows\",\n  \"tier\": \"session\"\n}\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Session created successfully\",\n  \"data\": {\n    \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"session_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"token_type\": \"Bearer\",\n    \"session_id\": \"sess_abc123def456\",\n    \"expires_at\": \"2026-09-17T12:00:00\",\n    \"tier\": \"session\",\n    \"rate_limit\": 500,\n    \"message\": \"Session created successfully\"\n  }\n}\n```\n\n## Rate limits (session creation, not API usage)\n\nDefaults, overridable via env (`SESSION_CREATE_*`):\n\n- Per client IP: 800/minute, 3000/hour, 15000/day (classroom NAT of 400+)\n- Per device fingerprint (when provided): 8/minute, 40/hour, 100/day\n\nClient IP is taken from `CF-Connecting-IP` (when `CF-Ray` is present), else\n`X-Forwarded-For`, else `X-Real-IP`, else the socket peer.\n\n## Error Responses\n\n- **401 Unauthorized**: Invalid tier or custom rate limit on public create\n- **429 Too Many Requests**: Creation cap exceeded (`Retry-After` set)\n- **500 Internal Server Error**: Unexpected server error\n- **503 Service Unavailable**: Security system not initialized","operationId":"create_session_v5_auth_session_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Auth"],"summary":"Logout Session","description":"Logout and revoke the current session token.\n\nImmediately invalidates the session token, preventing further use. The session\nis marked as inactive in the database and the session cookie is cleared from\nthe response. This endpoint is idempotent - calling it multiple times has no\nadditional effect.\n\n## Session Token Sources (first match wins)\n\n1. `nzqa_session` cookie\n2. `Authorization: Bearer <jwt>`\n\n## Response Data\n\n- `message`: Confirmation message\n\n## Example Request\n\n```\nDELETE /v5/auth/session\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Logged out successfully\",\n  \"data\": {\n    \"message\": \"Session revoked successfully\"\n  }\n}\n```\n\n## Security Features\n\n- **Immediate Revocation**: Session is immediately marked as inactive\n- **Cookie Clearing**: Session cookie is removed from response\n- **Idempotent**: Safe to call multiple times\n\n## Error Responses\n\n- **401 Unauthorized**: No session token found or token is invalid\n- **500 Internal Server Error**: Server error (generic message)","operationId":"logout_session_v5_auth_session_delete","parameters":[{"name":"nzqa_session","in":"cookie","required":false,"schema":{"type":"string","title":"Nzqa Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/auth/refresh":{"post":{"tags":["Auth"],"summary":"Refresh Session","description":"Refresh a session token to extend its validity.\n\nGenerates a new session token from an existing valid session token. Supports\nrefresh token rotation for enhanced security. The new token replaces the old\none, and the old token is invalidated (if rotation is enabled).\n\n## Request Body\n\n- `extend_expiry`: Whether to extend the session expiration date (default: true)\n  - If true: Extends expiration by the default session lifetime (30 days)\n  - If false: Keeps original expiration date\n- `access_token`: Optional session JWT when the cookie is unavailable\n  (cross-origin SPA). Prefer `Authorization: Bearer` instead.\n\n## Session Token Sources (first match wins)\n\n1. `nzqa_session` cookie (same-site browsers)\n2. `Authorization: Bearer <jwt>`\n3. `access_token` in the JSON body\n\n## Response Data\n\n- `access_token` / `session_token` / `token`: Rotated JWT (use for subsequent Bearer calls)\n- `token_type`: `\"Bearer\"`\n- `message`: Confirmation message\n- `extend_expiry`: Whether expiry was extended\n\n## Example Request\n\n```json\nPOST /v5/auth/refresh\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n{\n  \"extend_expiry\": true\n}\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Session refreshed successfully\",\n  \"data\": {\n    \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"session_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"token_type\": \"Bearer\",\n    \"message\": \"Session refreshed successfully\",\n    \"extend_expiry\": true\n  }\n}\n```\n\n## Security Features\n\n- **Refresh Token Rotation**: Old token is invalidated when new token is issued\n- **Automatic Cookie Update**: New session token is automatically set in response cookie\n- **Expiry Extension**: Optionally extends session expiration date\n\n## Error Responses\n\n- **401 Unauthorized**: No session token found or token is invalid/expired\n- **500 Internal Server Error**: Server error (generic message)","operationId":"refresh_session_v5_auth_refresh_post","parameters":[{"name":"nzqa_session","in":"cookie","required":false,"schema":{"type":"string","title":"Nzqa Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionRefresh"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/auth/session/info":{"get":{"tags":["Auth"],"summary":"Get Session Info","description":"Retrieve detailed information about the current active session.\n\nReturns comprehensive session metadata including tier, rate limits, expiration,\nand remaining requests. Useful for client-side session management and displaying\ncurrent authentication status to users.\n\n## Authentication\n\nRequires an active session token (via cookie or Authorization header).\nReturns 401 if no valid session is found.\n\n## Response Data\n\n- `token` / `access_token` / `session_token`: Current JWT (for Bearer clients)\n- `token_type`: `\"Bearer\"` when a session JWT is present\n- `session_id`: Unique session identifier\n- `tier`: Session tier (e.g., \"session\", \"premium\", \"unlimited\")\n- `rate_limit`: Maximum requests per time window (null if unlimited)\n- `remaining_requests`: Remaining requests in current window (null if unlimited)\n- `reset_time`: Unix timestamp when rate limit resets (null if unlimited)\n- `expires_at`: ISO 8601 timestamp when session expires\n- `active`: Whether session is currently active\n\n## Example Request\n\n```\nGET /v5/auth/session/info\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Session information retrieved successfully\",\n  \"data\": {\n    \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"session_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"token_type\": \"Bearer\",\n    \"session_id\": \"sess_abc123def456\",\n    \"tier\": \"session\",\n    \"rate_limit\": 200,\n    \"remaining_requests\": 147,\n    \"reset_time\": 1704067200,\n    \"expires_at\": \"2026-01-21T12:00:00.000000+00:00\",\n    \"active\": true\n  }\n}\n```\n\n## Error Responses\n\n- **401 Unauthorized**: No active session found or session is invalid\n- **500 Internal Server Error**: Server error (generic message)","operationId":"get_session_info_v5_auth_session_info_get","parameters":[{"name":"nzqa_session","in":"cookie","required":false,"schema":{"type":"string","title":"Nzqa Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/auth/status":{"get":{"tags":["Auth"],"summary":"Get Auth Status","description":"Get current authentication status (PUBLIC ENDPOINT).\n\nReturns authentication status information for the current request. This endpoint\ndoes not require authentication and can be used to check if a request is authenticated\nand what tier/rate limits apply. Useful for client-side authentication state management.\n\n## Response Data (Authenticated)\n\nWhen authenticated, returns:\n- `authenticated`: true\n- `identifier_type`: Type of authentication (\"api_key\", \"session\", or \"ip\")\n- `tier`: Authentication tier\n- `rate_limit`: Maximum requests per time window\n- `remaining_requests`: Remaining requests in current window\n- `reset_time`: Unix timestamp when rate limit resets\n- `expires_at`: ISO 8601 timestamp when authentication expires (if applicable)\n- `active`: Whether authentication is currently active\n- `token` / `access_token` / `session_token`: Present when authenticated via session JWT\n  (cookie or Bearer) so cross-origin clients can re-store the token\n\n## Response Data (Not Authenticated)\n\nWhen not authenticated, returns:\n- `authenticated`: false\n- `identifier_type`: null\n- `tier`: \"public\"\n\n## Example Request\n\n```\nGET /v5/auth/status\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n```\n\n## Example Response (Authenticated session)\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Authentication status retrieved successfully\",\n  \"data\": {\n    \"authenticated\": true,\n    \"identifier_type\": \"session\",\n    \"tier\": \"session\",\n    \"rate_limit\": 200,\n    \"remaining_requests\": 147,\n    \"reset_time\": 1704067200,\n    \"expires_at\": \"2026-01-21T12:00:00.000000+00:00\",\n    \"active\": true,\n    \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"session_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"token_type\": \"Bearer\"\n  }\n}\n```\n\n## Example Response (Not Authenticated)\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Not authenticated\",\n  \"data\": {\n    \"authenticated\": false,\n    \"identifier_type\": null,\n    \"tier\": \"public\"\n  }\n}\n```\n\n## Use Cases\n\n- Check authentication status before making authenticated requests\n- Display current rate limit information to users\n- Implement client-side authentication state management\n- Debug authentication issues\n\n## Notes\n\n- This endpoint does not require authentication\n- Does not count toward rate limits\n- Always returns 200 OK (even when not authenticated)","operationId":"get_auth_status_v5_auth_status_get","parameters":[{"name":"nzqa_session","in":"cookie","required":false,"schema":{"type":"string","title":"Nzqa Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/keys":{"post":{"tags":["Admin - Keys"],"summary":"Create Api Key","description":"Create a new API key (ADMIN ONLY).\n\nGenerates a new API key with the specified tier, rate limits, and security settings.\nSupports key rotation by setting `rotate_from_key_id` in the request body. The API\nkey value is returned only once in the response - store it securely as it cannot be\nretrieved later.\n\n## Request Body\n\n- `name`: Key name/description (required, for identification)\n- `tier`: Key tier (required). Valid values: \"basic\", \"premium\", \"unlimited\", \"admin\"\n  - \"basic\": 300 requests/minute\n  - \"premium\": 1000 requests/minute\n  - \"unlimited\": No rate limit\n  - \"admin\": 100 requests/minute (admin endpoints only)\n- `ip_whitelist`: Optional list of allowed IP addresses or CIDR ranges\n  - Example: [\"192.168.1.0/24\", \"10.0.0.1\"]\n  - Empty list or null allows all IPs\n- `expires_at`: Optional expiration date/time (ISO 8601 format)\n  - If not provided, key never expires\n  - Maximum: 10 years from creation\n- `rotate_from_key_id`: Optional key ID to rotate from\n  - Creates new key with same settings as old key\n  - Old key remains active during grace period\n  - Old key automatically expires after grace period\n- `rotation_grace_period_days`: Grace period for rotation (1-30 days, default: 7)\n  - Only used when `rotate_from_key_id` is set\n  - Both keys are valid during grace period\n\n## Response Data\n\n- `api_key`: The generated API key value (store securely - only shown once)\n- `key_id`: Unique key identifier (use this for future operations)\n- `name`: Key name\n- `tier`: Key tier\n- `expires_at`: Expiration timestamp (null if no expiration)\n- `rotated_from_key_id`: Key ID this was rotated from (if applicable)\n- `rotation_grace_period_days`: Grace period for rotation (if applicable)\n\n## Example Request (New Key)\n\n```json\nPOST /admin/keys\n{\n  \"name\": \"Production API Key\",\n  \"tier\": \"premium\",\n  \"ip_whitelist\": [\"192.168.1.0/24\"],\n  \"expires_at\": \"2026-12-31T23:59:59Z\"\n}\n```\n\n## Example Request (Key Rotation)\n\n```json\nPOST /admin/keys\n{\n  \"name\": \"Production API Key (rotated)\",\n  \"tier\": \"premium\",\n  \"rotate_from_key_id\": \"nzqa_abc123def456\",\n  \"rotation_grace_period_days\": 7\n}\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"API key created successfully\",\n  \"data\": {\n    \"api_key\": \"nzqa_live_abc123def456ghi789jkl012mno345pqr678stu901vwx234yz\",\n    \"key_id\": \"nzqa_abc123def456\",\n    \"name\": \"Production API Key\",\n    \"tier\": \"premium\",\n    \"expires_at\": \"2026-12-31T23:59:59.000000+00:00\"\n  }\n}\n```\n\n## Security Notes\n\n- **ADMIN ONLY**: Requires admin authentication\n- API key value is returned only once - store it securely\n- Key cannot be retrieved later (only metadata is stored)\n- IP whitelist restrictions take effect immediately\n- Key rotation maintains security while allowing seamless transition\n\n## Error Responses\n\n- **401 Unauthorized**: Missing or invalid admin authentication\n- **400 Bad Request**: Invalid request body or parameters\n- **404 Not Found**: `rotate_from_key_id` does not exist (if rotation requested)\n- **500 Internal Server Error**: Server error (generic message)","operationId":"create_api_key_admin_keys_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Admin - Keys"],"summary":"List Api Keys","description":"List all API keys with pagination (ADMIN ONLY).\n\nReturns a paginated list of all API keys in the system, including active and\ninactive keys. Results are sorted by creation date (newest first). Useful for\nmanaging and monitoring API key usage across the system.\n\n## Query Parameters\n\n- `limit`: Maximum number of keys to return (1-500, default: 50)\n- `offset`: Number of keys to skip for pagination (default: 0)\n\n## Response Data\n\nReturns an array of API key objects, each containing:\n- `key_id`: Unique key identifier\n- `name`: Key name/description\n- `tier`: Key tier (e.g., \"basic\", \"premium\", \"unlimited\")\n- `ip_whitelist`: List of allowed IP addresses/CIDR ranges (if configured)\n- `total_requests`: Total number of requests made with this key\n- `last_used`: Last usage timestamp (ISO 8601)\n- `created_at`: Creation timestamp (ISO 8601)\n- `expires_at`: Expiration timestamp (ISO 8601, null if no expiration)\n- `active`: Whether key is currently active\n- `rotated_from_key_id`: Key ID this was rotated from (if applicable)\n\n## Example Request\n\n```\nGET /admin/keys?limit=25&offset=0\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Retrieved 25 API keys\",\n  \"data\": [\n    {\n      \"key_id\": \"nzqa_abc123def456\",\n      \"name\": \"Production API Key\",\n      \"tier\": \"premium\",\n      \"ip_whitelist\": [\"192.168.1.0/24\"],\n      \"total_requests\": 15420,\n      \"last_used\": \"2025-12-21T11:30:00.000000+00:00\",\n      \"created_at\": \"2025-01-01T00:00:00.000000+00:00\",\n      \"expires_at\": null,\n      \"active\": true,\n      \"rotated_from_key_id\": null\n    }\n  ]\n}\n```\n\n## Security Notes\n\n- **ADMIN ONLY**: Requires admin authentication\n- API key values are never returned (only metadata)\n- Returns all keys regardless of ownership\n\n## Error Responses\n\n- **401 Unauthorized**: Missing or invalid admin authentication\n- **500 Internal Server Error**: Server error (generic message)","operationId":"list_api_keys_admin_keys_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum number of API keys to return per page. Valid range: 1-500. Default: 50. Use with offset for pagination.","default":50,"title":"Limit"},"description":"Maximum number of API keys to return per page. Valid range: 1-500. Default: 50. Use with offset for pagination.","example":50},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=50 with limit=50 returns keys 51-100.","default":0,"title":"Offset"},"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=50 with limit=50 returns keys 51-100.","example":0}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_List_APIKeyInfo__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/keys/{key_id}/rotate":{"post":{"tags":["Admin - Keys"],"summary":"Rotate Api Key","description":"Rotate an API key\n\nCreates a new API key and schedules the old key to expire after a grace period.\nBoth keys are valid during the grace period to allow for seamless transition.\n\n## Path Parameter\n\n- `key_id`: Key ID to rotate from\n\n## Query Parameters\n\n- `new_key_name`: Optional name for new key\n- `grace_period_days`: Grace period in days (1-30, default: 7)\n\n## Example Request\n\n```\nPOST /admin/keys/nzqa_abc123def456/rotate?grace_period_days=7\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"API key rotated successfully\",\n  \"data\": {\n    \"new_api_key\": \"nzqa_new_key_here\",\n    \"new_key_id\": \"nzqa_new_key_id\",\n    \"old_key_id\": \"nzqa_abc123def456\",\n    \"grace_period_days\": 7,\n    \"old_key_expires_at\": \"2024-01-15T00:00:00Z\"\n  }\n}\n```","operationId":"rotate_api_key_admin_keys__key_id__rotate_post","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","description":"Key ID to rotate","title":"Key Id"},"description":"Key ID to rotate","example":"nzqa_abc123def456"},{"name":"new_key_name","in":"query","required":false,"schema":{"description":"Optional name for new key (defaults to old key name + ' (rotated)')","title":"New Key Name","type":"string"},"description":"Optional name for new key (defaults to old key name + ' (rotated)')","example":"My API Key (rotated)"},{"name":"grace_period_days","in":"query","required":false,"schema":{"type":"integer","maximum":30,"minimum":1,"description":"Grace period in days (old key remains valid during this period)","default":7,"title":"Grace Period Days"},"description":"Grace period in days (old key remains valid during this period)","example":7}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/keys/{key_id}":{"get":{"tags":["Admin - Keys"],"summary":"Get Api Key","description":"Get detailed information about a specific API key (ADMIN ONLY).\n\nReturns comprehensive metadata for a single API key, including usage statistics,\nIP whitelist configuration, expiration, and rotation history. The actual API key\nvalue is never returned for security reasons.\n\n## Path Parameter\n\n- `key_id`: API key ID to retrieve (required)\n  - Format: Key identifier (e.g., \"nzqa_abc123def456\")\n  - Must be a valid, existing key ID\n\n## Response Data\n\n- `key_id`: Unique key identifier\n- `name`: Key name/description\n- `tier`: Key tier\n- `ip_whitelist`: List of allowed IP addresses/CIDR ranges\n- `total_requests`: Total number of requests made with this key\n- `last_used`: Last usage timestamp\n- `created_at`: Creation timestamp\n- `expires_at`: Expiration timestamp (null if no expiration)\n- `active`: Whether key is currently active\n- `rotated_from_key_id`: Key ID this was rotated from (if applicable)\n\n## Example Request\n\n```\nGET /admin/keys/nzqa_abc123def456\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"API key retrieved successfully\",\n  \"data\": {\n    \"key_id\": \"nzqa_abc123def456\",\n    \"name\": \"Production API Key\",\n    \"tier\": \"premium\",\n    \"ip_whitelist\": [\"192.168.1.0/24\", \"10.0.0.1\"],\n    \"total_requests\": 15420,\n    \"last_used\": \"2025-12-21T11:30:00.000000+00:00\",\n    \"created_at\": \"2025-01-01T00:00:00.000000+00:00\",\n    \"expires_at\": null,\n    \"active\": true,\n    \"rotated_from_key_id\": null\n  }\n}\n```\n\n## Security Notes\n\n- **ADMIN ONLY**: Requires admin authentication\n- API key value is never returned (only metadata)\n- Can retrieve any key, regardless of ownership\n\n## Error Responses\n\n- **401 Unauthorized**: Missing or invalid admin authentication\n- **404 Not Found**: Key ID does not exist\n- **500 Internal Server Error**: Server error (generic message)","operationId":"get_api_key_admin_keys__key_id__get","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","description":"API key ID to retrieve. Must be a valid key identifier. Example: 'nzqa_abc123def456'.","title":"Key Id"},"description":"API key ID to retrieve. Must be a valid key identifier. Example: 'nzqa_abc123def456'.","example":"nzqa_abc123def456"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_APIKeyInfo_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Admin - Keys"],"summary":"Update Api Key","description":"Update an existing API key's metadata (ADMIN ONLY).\n\nAllows updating API key properties such as name, IP whitelist, and expiration date.\nThe API key value itself cannot be changed - use rotation to create a new key.\nAll fields in the request body are optional; only provided fields will be updated.\n\n## Path Parameter\n\n- `key_id`: API key ID to update (required)\n\n## Request Body\n\nAll fields are optional:\n- `name`: New name/description for the key\n- `ip_whitelist`: List of allowed IP addresses or CIDR ranges (e.g., [\"192.168.1.0/24\", \"10.0.0.1\"])\n  - Empty list removes IP restrictions\n  - null keeps existing whitelist unchanged\n- `expires_at`: New expiration date/time (ISO 8601 format)\n  - null removes expiration (key never expires)\n\n## Example Request\n\n```json\nPUT /admin/keys/nzqa_abc123def456\n{\n  \"name\": \"Updated Production Key\",\n  \"ip_whitelist\": [\"192.168.1.0/24\", \"10.0.0.1\"],\n  \"expires_at\": \"2026-12-31T23:59:59Z\"\n}\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"API key updated successfully\",\n  \"data\": {\n    \"message\": \"API key updated successfully\"\n  }\n}\n```\n\n## Security Notes\n\n- **ADMIN ONLY**: Requires admin authentication\n- Cannot change the API key value (use rotation)\n- Cannot change tier (create new key with different tier)\n- IP whitelist changes take effect immediately\n\n## Error Responses\n\n- **401 Unauthorized**: Missing or invalid admin authentication\n- **404 Not Found**: Key ID does not exist\n- **400 Bad Request**: Invalid request body or parameters\n- **500 Internal Server Error**: Server error (generic message)","operationId":"update_api_key_admin_keys__key_id__put","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","description":"API key ID to update. Must be a valid key identifier. Example: 'nzqa_abc123def456'.","title":"Key Id"},"description":"API key ID to update. Must be a valid key identifier. Example: 'nzqa_abc123def456'.","example":"nzqa_abc123def456"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Admin - Keys"],"summary":"Revoke Api Key","description":"Revoke an API key (ADMIN ONLY).\n\nImmediately deactivates an API key, preventing further use. The key is marked as\ninactive in the database and cannot be reactivated. This is useful for security\npurposes, such as revoking compromised keys or managing access.\n\n## Path Parameter\n\n- `key_id`: API key ID to revoke (required)\n  - Format: Key identifier (e.g., \"nzqa_abc123def456\")\n  - Must be a valid, existing key ID\n\n## Response Data\n\n- `message`: Confirmation message\n\n## Example Request\n\n```\nDELETE /admin/keys/nzqa_abc123def456\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"API key revoked successfully\",\n  \"data\": {\n    \"message\": \"API key revoked successfully\"\n  }\n}\n```\n\n## Security Notes\n\n- **ADMIN ONLY**: Requires admin authentication\n- Key is immediately deactivated (cannot be reactivated)\n- Revoked keys cannot be used for authentication\n- Key metadata is preserved for audit purposes\n\n## Error Responses\n\n- **401 Unauthorized**: Missing or invalid admin authentication\n- **404 Not Found**: Key ID does not exist\n- **500 Internal Server Error**: Server error (generic message)","operationId":"revoke_api_key_admin_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","description":"API key ID to revoke. Must be a valid key identifier. Example: 'nzqa_abc123def456'.","title":"Key Id"},"description":"API key ID to revoke. Must be a valid key identifier. Example: 'nzqa_abc123def456'.","example":"nzqa_abc123def456"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/keys/{key_id}/stats":{"get":{"tags":["Admin - Keys"],"summary":"Get Api Key Stats","description":"Get usage statistics for a specific API key (ADMIN ONLY).\n\nReturns comprehensive usage statistics including total requests, last usage timestamp,\nand key metadata. Useful for monitoring API key usage, identifying inactive keys,\nand analyzing access patterns.\n\n## Path Parameter\n\n- `key_id`: API key ID for which to retrieve statistics (required)\n  - Format: Key identifier (e.g., \"nzqa_abc123def456\")\n  - Must be a valid, existing key ID\n\n## Response Data\n\n- `key_id`: Unique key identifier\n- `name`: Key name/description\n- `tier`: Key tier\n- `total_requests`: Total number of requests made with this key\n- `last_used`: Last usage timestamp (ISO 8601, null if never used)\n- `created_at`: Creation timestamp (ISO 8601)\n- `expires_at`: Expiration timestamp (ISO 8601, null if no expiration)\n- `active`: Whether key is currently active\n\n## Example Request\n\n```\nGET /admin/keys/nzqa_abc123def456/stats\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"API key statistics retrieved successfully\",\n  \"data\": {\n    \"key_id\": \"nzqa_abc123def456\",\n    \"name\": \"Production API Key\",\n    \"tier\": \"premium\",\n    \"total_requests\": 15420,\n    \"last_used\": \"2025-12-21T11:30:00.000000+00:00\",\n    \"created_at\": \"2025-01-01T00:00:00.000000+00:00\",\n    \"expires_at\": null,\n    \"active\": true\n  }\n}\n```\n\n## Security Notes\n\n- **ADMIN ONLY**: Requires admin authentication\n- Returns statistics for any key, regardless of ownership\n- Statistics are aggregated and may not include real-time data\n\n## Error Responses\n\n- **401 Unauthorized**: Missing or invalid admin authentication\n- **404 Not Found**: Key ID does not exist\n- **500 Internal Server Error**: Server error (generic message)","operationId":"get_api_key_stats_admin_keys__key_id__stats_get","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","description":"API key ID for which to retrieve statistics. Must be a valid key identifier. Example: 'nzqa_abc123def456'.","title":"Key Id"},"description":"API key ID for which to retrieve statistics. Must be a valid key identifier. Example: 'nzqa_abc123def456'.","example":"nzqa_abc123def456"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/sessions":{"post":{"tags":["Admin - Sessions"],"summary":"Create Session Admin","description":"Create a session with full administrative control (ADMIN ONLY).\n\nThis endpoint allows administrators to create sessions with complete control over\ntier, rate limits, and expiration dates. Unlike the public session endpoint, this\nbypasses all restrictions and allows any configuration.\n\n## Request Body\n\n- `device_name`: Optional device name for tracking (e.g., \"Chrome on Windows\")\n- `device_fingerprint`: Optional device fingerprint for security tracking\n- `tier`: Session tier (required). Valid values: \"session\", \"basic\", \"premium\", \"unlimited\", \"admin\"\n  - \"session\": 500 requests/minute (default for public)\n  - \"basic\": 300 requests/minute\n  - \"premium\": 1000 requests/minute\n  - \"unlimited\": No rate limit\n  - \"admin\": 100 requests/minute (admin endpoints)\n- `custom_rate_limit`: Optional custom rate limit (requests per minute). Overrides tier limit if provided.\n  - Valid range: 1-10000\n  - If None, uses tier's default rate limit\n- `expires_at`: Optional expiration date/time (ISO 8601 format). If not provided, uses default (30 days).\n  - Maximum: 365 days from creation\n  - Minimum: 1 minute from creation\n\n## Response Data\n\n- `session_id`: Unique session identifier\n- `session_token`: JWT session token (included in response for admin convenience)\n- `expires_at`: Session expiration timestamp (ISO 8601)\n- `tier`: Session tier\n- `custom_rate_limit`: Custom rate limit (if set)\n\n## Example Request\n\n```json\nPOST /admin/sessions\n{\n  \"device_name\": \"Admin Workstation\",\n  \"tier\": \"premium\",\n  \"custom_rate_limit\": 2000,\n  \"expires_at\": \"2026-12-31T23:59:59Z\"\n}\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Admin session created successfully\",\n  \"data\": {\n    \"session_id\": \"sess_abc123def456\",\n    \"session_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n    \"expires_at\": \"2026-12-31T23:59:59.000000+00:00\",\n    \"tier\": \"premium\",\n    \"custom_rate_limit\": 2000,\n    \"message\": \"Admin session created successfully\"\n  }\n}\n```\n\n## Security Notes\n\n- **ADMIN ONLY**: Requires admin authentication via `X-Admin-Key` header\n- Bypasses all public rate limits and restrictions\n- Session token is returned in response (unlike public endpoint)\n- Supports any tier and custom rate limits\n- Session cookie is automatically set in response\n\n## Error Responses\n\n- **401 Unauthorized**: Missing or invalid admin authentication\n- **400 Bad Request**: Invalid request body or parameters\n- **500 Internal Server Error**: Server error (generic message)","operationId":"create_session_admin_admin_sessions_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Admin - Sessions"],"summary":"List Sessions","description":"List all active sessions with pagination (ADMIN ONLY).\n\nReturns a paginated list of all active sessions in the system. Includes session\nmetadata such as tier, expiration, creation date, and device information. Useful\nfor monitoring active sessions and managing user access.\n\n## Query Parameters\n\n- `limit`: Maximum number of sessions to return (1-500, default: 50)\n- `offset`: Number of sessions to skip for pagination (default: 0)\n\n## Response Data\n\nReturns an array of session objects, each containing:\n- `session_id`: Unique session identifier\n- `tier`: Session tier\n- `custom_rate_limit`: Custom rate limit (if set)\n- `expires_at`: Session expiration timestamp\n- `created_at`: Session creation timestamp\n- `device_name`: Device name (if provided)\n- `ip_address`: IP address of session creator\n- `active`: Whether session is currently active\n\n## Example Request\n\n```\nGET /admin/sessions?limit=25&offset=0\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Retrieved 25 sessions\",\n  \"data\": [\n    {\n      \"session_id\": \"sess_abc123def456\",\n      \"tier\": \"premium\",\n      \"custom_rate_limit\": 2000,\n      \"expires_at\": \"2026-12-31T23:59:59.000000+00:00\",\n      \"created_at\": \"2025-12-21T12:00:00.000000+00:00\",\n      \"device_name\": \"Admin Workstation\",\n      \"ip_address\": \"192.168.1.100\",\n      \"active\": true\n    }\n  ]\n}\n```\n\n## Security Notes\n\n- **ADMIN ONLY**: Requires admin authentication\n- Returns all sessions regardless of ownership\n- Sensitive information (token hashes) are excluded from response\n\n## Error Responses\n\n- **401 Unauthorized**: Missing or invalid admin authentication\n- **500 Internal Server Error**: Server error (generic message)","operationId":"list_sessions_admin_sessions_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum number of sessions to return per page. Valid range: 1-500. Default: 50. Use with offset for pagination.","default":50,"title":"Limit"},"description":"Maximum number of sessions to return per page. Valid range: 1-500. Default: 50. Use with offset for pagination.","example":50},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=50 with limit=50 returns sessions 51-100.","default":0,"title":"Offset"},"description":"Number of results to skip for pagination. Use with limit to implement page-based navigation. Example: offset=50 with limit=50 returns sessions 51-100.","example":0}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_list_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/sessions/{session_id}":{"delete":{"tags":["Admin - Sessions"],"summary":"Revoke Session Admin","description":"Revoke a session by ID (ADMIN ONLY).\n\nImmediately deactivates a session, preventing further use of the session token.\nThe session is marked as inactive in the database. This is useful for security\npurposes, such as revoking compromised sessions or managing user access.\n\n## Path Parameter\n\n- `session_id`: Session ID to revoke (required)\n  - Format: Session identifier (e.g., \"sess_abc123def456\")\n  - Must be a valid, existing session ID\n\n## Response Data\n\n- `session_id`: The revoked session ID\n- `message`: Confirmation message\n\n## Example Request\n\n```\nDELETE /admin/sessions/sess_abc123def456\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Session revoked successfully\",\n  \"data\": {\n    \"session_id\": \"sess_abc123def456\"\n  }\n}\n```\n\n## Security Notes\n\n- **ADMIN ONLY**: Requires admin authentication\n- Can revoke any session, regardless of ownership\n- Session is immediately deactivated (cannot be reactivated)\n- Revoked sessions cannot be used for authentication\n\n## Error Responses\n\n- **401 Unauthorized**: Missing or invalid admin authentication\n- **404 Not Found**: Session ID does not exist\n- **500 Internal Server Error**: Server error (generic message)","operationId":"revoke_session_admin_admin_sessions__session_id__delete","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","description":"Session ID to revoke. Must be a valid session identifier. Example: 'sess_abc123def456'.","title":"Session Id"},"description":"Session ID to revoke. Must be a valid session identifier. Example: 'sess_abc123def456'.","example":"sess_abc123def456"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/admin/audit":{"get":{"tags":["Admin - Analytics"],"summary":"Get Admin Audit","description":"Get admin audit log entries\n\nReturns a list of administrative actions performed by administrators,\nincluding API key management, session management, and other admin operations.\nUseful for security auditing and tracking administrative changes.","operationId":"get_admin_audit_v5_admin_audit_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum number of audit entries to return","default":100,"title":"Limit"},"description":"Maximum number of audit entries to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of entries to skip for pagination","default":0,"title":"Offset"},"description":"Number of entries to skip for pagination"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_list_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/admin/analytics/search-queries":{"get":{"tags":["Admin - Analytics"],"summary":"Get Search Analytics","description":"Get comprehensive search query analytics\n\nReturns aggregated statistics about search queries including:\n- Total number of searches\n- Zero-result query count\n- Average execution time\n- Cache hit rate\n\nThis data helps identify search performance issues and optimize query patterns.\n\n## Response Data\n\n- `total_searches`: Total number of search queries in the period\n- `zero_result_queries`: Number of queries that returned no results\n- `avg_execution_time_ms`: Average query execution time in milliseconds\n- `cache_hit_rate`: Percentage of queries served from cache\n- `period_days`: Number of days analyzed\n\n## Example Request\n\n```\nGET /v5/admin/analytics/search-queries?days=7\nADMIN_MASTER_KEY: YOUR_ADMIN_KEY\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Search analytics retrieved for the last 7 days\",\n  \"data\": {\n    \"total_searches\": 15420,\n    \"zero_result_queries\": 342,\n    \"avg_execution_time_ms\": 45.23,\n    \"cache_hit_rate\": 67.5,\n    \"period_days\": 7\n  }\n}\n```","operationId":"get_search_analytics_v5_admin_analytics_search_queries_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"description":"Number of days to analyze. Valid range: 1-90. Default: 7.","default":7,"title":"Days"},"description":"Number of days to analyze. Valid range: 1-90. Default: 7.","example":7}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/admin/analytics/zero-results":{"get":{"tags":["Admin - Analytics"],"summary":"Get Zero Result Queries","description":"Get queries that returned zero results\n\nIdentifies search queries that failed to return any results. This helps:\n- Identify search matching issues\n- Find queries that need better synonym handling\n- Optimize search ranking algorithms\n- Understand user search patterns that aren't being satisfied\n\nResults are sorted by occurrence count (most frequent first).\n\n## Response Data\n\nEach entry contains:\n- `query_hash`: Hashed query identifier (for privacy)\n- `query_sample`: Sample of the original query (first 100 chars)\n- `occurrence_count`: Number of times this query returned zero results\n- `avg_execution_time`: Average execution time for these queries\n- `last_occurrence`: ISO 8601 timestamp of most recent occurrence\n\n## Example Request\n\n```\nGET /v5/admin/analytics/zero-results?days=7&limit=50\nADMIN_MASTER_KEY: YOUR_ADMIN_KEY\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Found 50 zero-result queries in the last 7 days\",\n  \"data\": [\n    {\n      \"query_hash\": \"a1b2c3d4e5f6\",\n      \"query_sample\": \"xyzzy level 3 exam\",\n      \"occurrence_count\": 15,\n      \"avg_execution_time\": 23.45,\n      \"last_occurrence\": \"2025-12-21T12:00:00.000000+00:00\"\n    }\n  ]\n}\n```","operationId":"get_zero_result_queries_v5_admin_analytics_zero_results_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"description":"Number of days to analyze. Valid range: 1-90. Default: 7.","default":7,"title":"Days"},"description":"Number of days to analyze. Valid range: 1-90. Default: 7.","example":7},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum number of queries to return. Valid range: 1-1000. Default: 100.","default":100,"title":"Limit"},"description":"Maximum number of queries to return. Valid range: 1-1000. Default: 100.","example":100}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_list_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/admin/analytics/popular-queries":{"get":{"tags":["Admin - Analytics"],"summary":"Get Popular Queries","description":"Get most popular search queries\n\nReturns the most frequently searched queries with:\n- Total search count\n- Total click count\n- Average result count\n- Click-through rate\n\nThis data helps:\n- Identify popular content for caching optimization\n- Understand user search patterns\n- Tune search ranking based on actual usage\n- Identify queries with high click-through rates (successful searches)\n\nResults are sorted by total searches and click-through rate.\n\n## Response Data\n\nEach entry contains:\n- `query_hash`: Hashed query identifier (for privacy)\n- `query_sample`: Sample of the original query (first 100 chars)\n- `total_searches`: Total number of times this query was executed\n- `total_clicks`: Total number of result clicks for this query\n- `avg_result_count`: Average number of results returned\n- `click_through_rate`: Percentage of searches that resulted in clicks\n- `last_searched`: ISO 8601 timestamp of most recent search\n\n## Example Request\n\n```\nGET /v5/admin/analytics/popular-queries?days=7&limit=50\nADMIN_MASTER_KEY: YOUR_ADMIN_KEY\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Found 50 popular queries in the last 7 days\",\n  \"data\": [\n    {\n      \"query_hash\": \"a1b2c3d4e5f6\",\n      \"query_sample\": \"physics level 3 exam\",\n      \"total_searches\": 1250,\n      \"total_clicks\": 890,\n      \"avg_result_count\": 45.2,\n      \"click_through_rate\": 71.2,\n      \"last_searched\": \"2025-12-21T12:00:00.000000+00:00\"\n    }\n  ]\n}\n```","operationId":"get_popular_queries_v5_admin_analytics_popular_queries_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"description":"Number of days to analyze. Valid range: 1-90. Default: 7.","default":7,"title":"Days"},"description":"Number of days to analyze. Valid range: 1-90. Default: 7.","example":7},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum number of queries to return. Valid range: 1-1000. Default: 100.","default":100,"title":"Limit"},"description":"Maximum number of queries to return. Valid range: 1-1000. Default: 100.","example":100}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_list_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v5/admin/analytics/cleanup":{"post":{"tags":["Admin - Analytics"],"summary":"Cleanup Analytics Data","description":"Manually trigger analytics data cleanup\n\nRemoves old analytics data to prevent database bloat. This operation:\n- Deletes detailed search query records older than the specified days\n- Deletes clicked result records older than the specified days\n- Deletes performance metrics older than the specified days\n- Removes popular queries that haven't been searched in 30+ days\n- Vacuums the database to reclaim disk space\n\nNote: Cleanup runs automatically daily, but this endpoint allows manual triggering.\nAggregated data in popular_queries is preserved longer than detailed records.\n\n## Response Data\n\n- `days_to_keep`: Number of days of data retained\n- `cleanup_completed`: Boolean indicating successful cleanup\n\n## Example Request\n\n```\nPOST /v5/admin/analytics/cleanup?days_to_keep=90\nADMIN_MASTER_KEY: YOUR_ADMIN_KEY\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Analytics cleanup completed. Kept 90 days of data.\",\n  \"data\": {\n    \"days_to_keep\": 90,\n    \"cleanup_completed\": true\n  }\n}\n```\n\n## Notes\n\n- Cleanup is a potentially long-running operation for large databases\n- Database is vacuumed after cleanup to reclaim disk space\n- Popular queries are kept for 30 days after last search (separate from days_to_keep)","operationId":"cleanup_analytics_data_v5_admin_analytics_cleanup_post","parameters":[{"name":"days_to_keep","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":7,"description":"Number of days of data to keep. Valid range: 7-365. Default: 90. Data older than this will be deleted.","default":90,"title":"Days To Keep"},"description":"Number of days of data to keep. Valid range: 7-365. Default: 90. Data older than this will be deleted.","example":90}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/security/ip-stats/{ip_address}":{"get":{"tags":["Admin - Security"],"summary":"Get Ip Stats","description":"Get statistics for an IP address (ADMIN ONLY).\n\nReturns detailed statistics about session creation and abuse detection\nfor the specified IP address. Useful for investigating suspicious activity.\n\n## Path Parameter\n\n- `ip_address`: IP address to get statistics for (IPv4 or IPv6)\n\n## Response Data\n\n- `ip_address`: The queried IP address\n- `session_count`: Number of sessions created from this IP\n- `blocked`: Whether the IP is currently blocked\n- `last_session_creation`: Timestamp of last session creation\n\n## Example Request\n\n```\nGET /admin/security/ip-stats/192.168.1.100\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"IP statistics for 192.168.1.100\",\n  \"data\": {\n    \"ip_address\": \"192.168.1.100\",\n    \"session_count\": 5,\n    \"blocked\": false,\n    \"last_session_creation\": \"2025-12-21T12:00:00.000000+00:00\"\n  }\n}\n```","operationId":"get_ip_stats_admin_security_ip_stats__ip_address__get","parameters":[{"name":"ip_address","in":"path","required":true,"schema":{"type":"string","title":"Ip Address"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/security/block-ip":{"post":{"tags":["Admin - Security"],"summary":"Block Ip","description":"Block an IP address temporarily (ADMIN ONLY).\n\nBlocks the specified IP address for the given duration. Blocked IPs cannot\ncreate sessions or make API requests. Useful for stopping abuse in real-time.\n\n## Request Body\n\n- `ip_address`: IP address to block (IPv4 or IPv6)\n- `duration_minutes`: Duration to block in minutes (1-10080, max 7 days)\n\n## Response Data\n\n- `ip_address`: The blocked IP address\n- `blocked_for_minutes`: Duration of the block\n\n## Example Request\n\n```json\nPOST /admin/security/block-ip\n{\n  \"ip_address\": \"192.168.1.100\",\n  \"duration_minutes\": 60\n}\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"IP 192.168.1.100 blocked for 60 minutes\",\n  \"data\": {\n    \"ip_address\": \"192.168.1.100\",\n    \"blocked_for_minutes\": 60\n  }\n}\n```","operationId":"block_ip_admin_security_block_ip_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_block_ip_admin_security_block_ip_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/security/abuse-stats":{"get":{"tags":["Admin - Security"],"summary":"Get Abuse Stats","description":"Get abuse detection statistics (ADMIN ONLY).\n\nReturns current statistics about the abuse detection system including\nnumber of blocked IPs, tracked IPs, and system status.\n\n## Response Data\n\n- `blocked_ips`: Number of currently blocked IP addresses\n- `tracked_ips`: Number of IPs being tracked for session creation\n- `abuse_detection_active`: Whether the abuse detection system is active\n\n## Example Request\n\n```\nGET /admin/security/abuse-stats\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Abuse detection statistics\",\n  \"data\": {\n    \"blocked_ips\": 5,\n    \"tracked_ips\": 150,\n    \"abuse_detection_active\": true\n  }\n}\n```","operationId":"get_abuse_stats_admin_security_abuse_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}}}}},"/admin/accounts":{"post":{"tags":["Admin - Accounts"],"summary":"Create Account","description":"Create a new user account (admin only).\n\nAdministrators can create accounts with any tier and status. This endpoint\nprovides full control over account creation, including setting tier and status\nat creation time.\n\n## Request Body\n\nSame as public registration endpoint, but admins can set any tier.\n\n## Example Request\n\n```json\n{\n  \"email\": \"admin@example.com\",\n  \"password\": \"securepassword123\",\n  \"username\": \"adminuser\",\n  \"full_name\": \"Admin User\",\n  \"organization\": \"Admin Corp\",\n  \"tier\": \"premium\"\n}\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Account created successfully\",\n  \"data\": {\n    \"account_id\": \"acc_abc123def456\",\n    \"email\": \"admin@example.com\",\n    \"username\": \"adminuser\",\n    \"tier\": \"premium\",\n    \"status\": \"active\",\n    \"created_at\": \"2025-12-21T12:00:00.000000+00:00\"\n  }\n}\n```","operationId":"create_account_admin_accounts_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Admin - Accounts"],"summary":"List Accounts","description":"List all accounts with optional filtering (admin only).\n\nReturns a paginated list of accounts with optional filters. Supports filtering\nby email, username, tier, and status. Results are sorted by creation date (newest first).\n\n## Query Parameters\n\n- `email`: Filter by email (partial match, case-insensitive)\n- `username`: Filter by username (partial match)\n- `tier`: Filter by tier (exact match: free, basic, premium, admin)\n- `status`: Filter by status (exact match: active, suspended, deleted)\n- `limit`: Maximum results per page (1-500, default: 50)\n- `offset`: Results offset for pagination (default: 0)\n\n## Example Request\n\n```\nGET /admin/accounts?tier=premium&status=active&limit=25\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Retrieved 25 accounts\",\n  \"data\": {\n    \"accounts\": [\n      {\n        \"account_id\": \"acc_abc123def456\",\n        \"email\": \"user@example.com\",\n        \"username\": \"johndoe\",\n        \"tier\": \"premium\",\n        \"status\": \"active\",\n        \"created_at\": \"2025-12-21T12:00:00.000000+00:00\"\n      }\n    ],\n    \"total_accounts\": 150,\n    \"returned_accounts\": 25,\n    \"limit\": 25,\n    \"offset\": 0\n  }\n}\n```","operationId":"list_accounts_admin_accounts_get","parameters":[{"name":"email","in":"query","required":false,"schema":{"description":"Filter by email (partial match). Case-insensitive.","title":"Email","type":"string"},"description":"Filter by email (partial match). Case-insensitive.","example":"user@example.com"},{"name":"username","in":"query","required":false,"schema":{"description":"Filter by username (partial match).","title":"Username","type":"string"},"description":"Filter by username (partial match).","example":"johndoe"},{"name":"tier","in":"query","required":false,"schema":{"description":"Filter by tier. Valid values: session, basic, premium, unlimited, admin.","title":"Tier","type":"string","pattern":"^(session|basic|premium|unlimited|admin)$"},"description":"Filter by tier. Valid values: session, basic, premium, unlimited, admin.","example":"session"},{"name":"status","in":"query","required":false,"schema":{"description":"Filter by status. Valid values: active, suspended, deleted.","title":"Status","type":"string","pattern":"^(active|suspended|deleted)$"},"description":"Filter by status. Valid values: active, suspended, deleted.","example":"active"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum number of accounts to return per page. Valid range: 1-500. Default: 50.","default":50,"title":"Limit"},"description":"Maximum number of accounts to return per page. Valid range: 1-500. Default: 50.","example":50},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip for pagination. Use with limit for page-based navigation.","default":0,"title":"Offset"},"description":"Number of results to skip for pagination. Use with limit for page-based navigation.","example":0}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/accounts/{account_id}":{"get":{"tags":["Admin - Accounts"],"summary":"Get Account","description":"Get account by ID (admin only).\n\nReturns full account information including all fields. Admins can view\nany account regardless of status.\n\n## Path Parameter\n\n- `account_id`: Account ID (must start with \"acc_\")\n\n## Example Request\n\n```\nGET /admin/accounts/acc_abc123def456\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Account retrieved successfully\",\n  \"data\": {\n    \"account_id\": \"acc_abc123def456\",\n    \"email\": \"user@example.com\",\n    \"username\": \"johndoe\",\n    \"full_name\": \"John Doe\",\n    \"organization\": \"Example Corp\",\n    \"tier\": \"premium\",\n    \"status\": \"active\",\n    \"email_verified\": true,\n    \"created_at\": \"2025-12-21T12:00:00.000000+00:00\",\n    \"updated_at\": \"2025-12-21T12:00:00.000000+00:00\",\n    \"last_login\": \"2025-12-21T12:00:00.000000+00:00\",\n    \"total_requests\": 1234,\n    \"preferences\": {}\n  }\n}\n```","operationId":"get_account_admin_accounts__account_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"Account ID to retrieve. Must start with 'acc_' prefix.","title":"Account Id"},"description":"Account ID to retrieve. Must start with 'acc_' prefix.","example":"acc_abc123def456"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_Account_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Admin - Accounts"],"summary":"Update Account","description":"Update account (admin only).\n\nAdministrators can update any account field including tier, status, and\nemail verification status. All fields are optional - only provided fields\nwill be updated.\n\n## Path Parameter\n\n- `account_id`: Account ID to update\n\n## Request Body\n\nAll fields are optional:\n\n- `email`: New email address\n- `username`: New username\n- `password`: New password\n- `full_name`: New full name\n- `organization`: New organization\n- `tier`: New tier (free, basic, premium, admin)\n- `status`: New status (active, suspended, deleted)\n- `email_verified`: Email verification status\n- `preferences`: Account preferences JSON\n\n## Example Request\n\n```json\n{\n  \"tier\": \"premium\",\n  \"status\": \"active\",\n  \"email_verified\": true\n}\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Account updated successfully\",\n  \"data\": {\n    \"account_id\": \"acc_abc123def456\",\n    \"tier\": \"premium\",\n    \"status\": \"active\",\n    \"email_verified\": true,\n    ...\n  }\n}\n```","operationId":"update_account_admin_accounts__account_id__put","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"Account ID to update. Must start with 'acc_' prefix.","title":"Account Id"},"description":"Account ID to update. Must start with 'acc_' prefix.","example":"acc_abc123def456"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_Account_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Admin - Accounts"],"summary":"Delete Account","description":"Delete account (admin only).\n\nPerforms a soft delete on the account. The account is marked as deleted\nwith a deleted_at timestamp but data is retained for audit purposes.\n\n## Path Parameter\n\n- `account_id`: Account ID to delete\n\n## Example Request\n\n```\nDELETE /admin/accounts/acc_abc123def456\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Account deleted successfully\",\n  \"data\": {\n    \"deleted\": true,\n    \"account_id\": \"acc_abc123def456\"\n  }\n}\n```","operationId":"delete_account_admin_accounts__account_id__delete","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"Account ID to delete. Must start with 'acc_' prefix.","title":"Account Id"},"description":"Account ID to delete. Must start with 'acc_' prefix.","example":"acc_abc123def456"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/accounts/{account_id}/unlock":{"post":{"tags":["Admin - Accounts"],"summary":"Unlock Account","description":"Unlock a locked account (admin only).\n\nManually unlocks an account that has been locked due to too many failed login attempts.\nThis is an admin override function that resets the lockout and failed attempt counter.\n\n## Path Parameter\n\n- `account_id`: Account ID to unlock\n\n## Example Request\n\n```\nPOST /admin/accounts/acc_abc123def456/unlock\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Account unlocked successfully\",\n  \"data\": {\n    \"unlocked\": true,\n    \"account_id\": \"acc_abc123def456\"\n  }\n}\n```","operationId":"unlock_account_admin_accounts__account_id__unlock_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"Account ID to unlock. Must start with 'acc_' prefix.","title":"Account Id"},"description":"Account ID to unlock. Must start with 'acc_' prefix.","example":"acc_abc123def456"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/accounts/{account_id}/verify-email":{"post":{"tags":["Admin - Accounts"],"summary":"Verify Account Email","description":"Verify account email address (admin only).\n\nMarks the account's email address as verified. This is typically done\nautomatically via email verification links, but admins can manually\nverify emails.\n\n## Path Parameter\n\n- `account_id`: Account ID to verify email for\n\n## Example Request\n\n```\nPOST /admin/accounts/acc_abc123def456/verify-email\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Email verified successfully\",\n  \"data\": {\n    \"verified\": true,\n    \"account_id\": \"acc_abc123def456\"\n  }\n}\n```","operationId":"verify_account_email_admin_accounts__account_id__verify_email_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"Account ID to verify email for. Must start with 'acc_' prefix.","title":"Account Id"},"description":"Account ID to verify email for. Must start with 'acc_' prefix.","example":"acc_abc123def456"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/accounts/{account_id}/stats":{"get":{"tags":["Admin - Accounts"],"summary":"Get Account Stats","description":"Get account usage statistics (admin only).\n\nReturns usage statistics for the specified account including total requests,\naccount age, and other metrics.\n\n## Path Parameter\n\n- `account_id`: Account ID to get stats for\n\n## Example Request\n\n```\nGET /admin/accounts/acc_abc123def456/stats\n```\n\n## Example Response\n\n```json\n{\n  \"status\": \"success\",\n  \"status_code\": 200,\n  \"message\": \"Account statistics retrieved successfully\",\n  \"data\": {\n    \"account_id\": \"acc_abc123def456\",\n    \"email\": \"user@example.com\",\n    \"tier\": \"premium\",\n    \"status\": \"active\",\n    \"total_requests\": 1234,\n    \"created_at\": \"2025-12-21T12:00:00.000000+00:00\",\n    \"last_login\": \"2025-12-21T12:00:00.000000+00:00\",\n    \"account_age_days\": 30\n  }\n}\n```","operationId":"get_account_stats_admin_accounts__account_id__stats_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"Account ID to get statistics for. Must start with 'acc_' prefix.","title":"Account Id"},"description":"Account ID to get statistics for. Must start with 'acc_' prefix.","example":"acc_abc123def456"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"APIKeyCreate":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"API key name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"API key description"},"tier":{"type":"string","pattern":"^(free|basic|premium|unlimited|admin)$","title":"Tier","description":"API key tier","default":"free"},"custom_rate_limit":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Custom Rate Limit","description":"Custom rate limit (requests per minute)"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"API key expiration date"},"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id","description":"Account ID to link this API key to (optional)"},"ip_whitelist":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":100},{"type":"null"}],"title":"Ip Whitelist","description":"List of allowed IP addresses or CIDR ranges (e.g., ['192.168.1.1', '10.0.0.0/8']). If empty or None, allows all IPs."},"rotate_from_key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rotate From Key Id","description":"Key ID to rotate from (creates new key and schedules old key expiration)"},"rotation_grace_period_days":{"anyOf":[{"type":"integer","maximum":30.0,"minimum":1.0},{"type":"null"}],"title":"Rotation Grace Period Days","description":"Grace period in days for key rotation (old key remains valid during this period)","default":7}},"type":"object","required":["name"],"title":"APIKeyCreate","description":"Request model for creating API keys"},"APIKeyInfo":{"properties":{"key_id":{"type":"string","title":"Key Id","description":"API key ID"},"name":{"type":"string","title":"Name","description":"API key name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"API key description"},"tier":{"type":"string","title":"Tier","description":"API key tier"},"custom_rate_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Custom Rate Limit","description":"Custom rate limit"},"active":{"type":"boolean","title":"Active","description":"Whether API key is active"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"Expiration timestamp"},"last_used":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used","description":"Last usage timestamp"},"total_requests":{"type":"integer","title":"Total Requests","description":"Total number of requests"},"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id","description":"Linked account ID"},"ip_whitelist":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Ip Whitelist","description":"List of allowed IP addresses or CIDR ranges"},"rotated_from_key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rotated From Key Id","description":"Key ID this key was rotated from"},"rotation_grace_period_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Rotation Grace Period End","description":"End of rotation grace period (old key expires after this)"},"rotated_to_key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rotated To Key Id","description":"Key ID this key was rotated to"}},"type":"object","required":["key_id","name","tier","active","created_at","total_requests"],"title":"APIKeyInfo","description":"API key information (without sensitive data)"},"APIKeyUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name","description":"API key name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"API key description"},"tier":{"anyOf":[{"type":"string","pattern":"^(free|basic|premium|unlimited|admin)$"},{"type":"null"}],"title":"Tier","description":"API key tier"},"custom_rate_limit":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Custom Rate Limit","description":"Custom rate limit (requests per minute)"},"active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Active","description":"Whether API key is active"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"API key expiration date"},"ip_whitelist":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":100},{"type":"null"}],"title":"Ip Whitelist","description":"List of allowed IP addresses or CIDR ranges. Empty list removes whitelist (allows all)."}},"type":"object","title":"APIKeyUpdate","description":"Request model for updating API keys"},"Account":{"properties":{"account_id":{"type":"string","title":"Account Id","description":"Unique account identifier"},"email":{"type":"string","title":"Email","description":"Account email address"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username","description":"Account username"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name","description":"Full name of the account holder"},"organization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization","description":"Organization name"},"tier":{"type":"string","title":"Tier","description":"Account tier (session, basic, premium, unlimited, admin)"},"status":{"type":"string","title":"Status","description":"Account status (active, suspended, deleted)"},"email_verified":{"type":"boolean","title":"Email Verified","description":"Whether email is verified","default":false},"password_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password Hash","description":"Bcrypt password hash (never returned in responses)"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Account creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"},"last_login":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login","description":"Last login timestamp"},"deleted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deleted At","description":"Soft delete timestamp"},"total_requests":{"type":"integer","title":"Total Requests","description":"Total number of API requests","default":0},"preferences":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Preferences","description":"Account preferences JSON"},"failed_login_attempts":{"type":"integer","title":"Failed Login Attempts","description":"Number of consecutive failed login attempts","default":0},"locked_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Locked Until","description":"Account lockout expiration timestamp"},"last_failed_login":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Failed Login","description":"Timestamp of last failed login attempt"}},"type":"object","required":["account_id","email","tier","status","created_at","updated_at"],"title":"Account","description":"User account entity"},"AccountCreate":{"properties":{"email":{"type":"string","maxLength":255,"minLength":3,"title":"Email","description":"Account email address"},"username":{"anyOf":[{"type":"string","maxLength":50,"minLength":3},{"type":"null"}],"title":"Username","description":"Account username"},"password":{"type":"string","maxLength":128,"minLength":8,"title":"Password","description":"Account password"},"full_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Full Name","description":"Full name"},"organization":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Organization","description":"Organization name"},"tier":{"type":"string","pattern":"^(session|basic|premium|unlimited|admin)$","title":"Tier","description":"Account tier","default":"session"}},"type":"object","required":["email","password"],"title":"AccountCreate","description":"Account creation request model"},"AccountLogin":{"properties":{"email":{"type":"string","title":"Email","description":"Account email address"},"password":{"type":"string","title":"Password","description":"Account password"}},"type":"object","required":["email","password"],"title":"AccountLogin","description":"Account login request model"},"AccountUpdate":{"properties":{"email":{"anyOf":[{"type":"string","maxLength":255,"minLength":3},{"type":"null"}],"title":"Email","description":"Account email address"},"username":{"anyOf":[{"type":"string","maxLength":50,"minLength":3},{"type":"null"}],"title":"Username","description":"Account username"},"password":{"anyOf":[{"type":"string","maxLength":128,"minLength":8},{"type":"null"}],"title":"Password","description":"New password"},"full_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Full Name","description":"Full name"},"organization":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Organization","description":"Organization name"},"tier":{"anyOf":[{"type":"string","pattern":"^(session|basic|premium|unlimited|admin)$"},{"type":"null"}],"title":"Tier","description":"Account tier"},"status":{"anyOf":[{"type":"string","pattern":"^(active|suspended|deleted)$"},{"type":"null"}],"title":"Status","description":"Account status"},"email_verified":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Verified","description":"Email verification status"},"preferences":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Preferences","description":"Account preferences JSON"}},"type":"object","title":"AccountUpdate","description":"Account update request model"},"Body_block_ip_admin_security_block_ip_post":{"properties":{"ip_address":{"type":"string","title":"Ip Address","description":"IP address to block (IPv4 or IPv6)"},"duration_minutes":{"type":"integer","maximum":10080.0,"minimum":1.0,"title":"Duration Minutes","description":"Duration to block in minutes (1-10080, max 7 days)","default":60}},"type":"object","required":["ip_address"],"title":"Body_block_ip_admin_security_block_ip_post"},"BulkSearchRequestV2":{"properties":{"queries":{"items":{"type":"string"},"type":"array","maxItems":50,"minItems":1,"title":"Queries","description":"List of search queries"},"continue_on_error":{"type":"boolean","title":"Continue On Error","description":"Continue processing if individual queries fail","default":true}},"type":"object","required":["queries"],"title":"BulkSearchRequestV2","description":"Bulk search request model (v2 - with continue_on_error)"},"BulkStandardsRequestV2":{"properties":{"standard_numbers":{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1,"title":"Standard Numbers","description":"List of standard numbers"},"continue_on_error":{"type":"boolean","title":"Continue On Error","description":"Continue processing if individual items fail","default":true}},"type":"object","required":["standard_numbers"],"title":"BulkStandardsRequestV2","description":"Bulk standards request model (v2 - with continue_on_error)"},"ErrorDetail":{"properties":{"type":{"type":"string","title":"Type","description":"Error type identifier"},"context":{"additionalProperties":true,"type":"object","title":"Context","description":"Additional error context"},"recovery_suggestions":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Recovery Suggestions","description":"Suggested actions to recover from the error (list of objects with action, description, etc.)"},"documentation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Documentation","description":"Link to relevant documentation"}},"type":"object","required":["type"],"title":"ErrorDetail","description":"Detailed error information with recovery guidance"},"ExamPackageRequest":{"properties":{"subject":{"type":"string","minLength":1,"title":"Subject","description":"Subject name"},"level":{"type":"integer","maximum":3.0,"minimum":1.0,"title":"Level","description":"Level (1, 2, or 3)"},"years":{"type":"integer","maximum":10.0,"minimum":1.0,"title":"Years","description":"Number of recent years to include","default":3},"include_answers":{"type":"boolean","title":"Include Answers","description":"Include answer schedules","default":true},"include_schedules":{"type":"boolean","title":"Include Schedules","description":"Include assessment schedules","default":true},"include_exemplars":{"type":"boolean","title":"Include Exemplars","description":"Include exemplars","default":false}},"type":"object","required":["subject","level"],"title":"ExamPackageRequest","description":"Exam package request model"},"File":{"properties":{"id":{"type":"integer","title":"Id","description":"Unique file identifier"},"file_name":{"type":"string","title":"File Name","description":"Name of the file"},"file_path":{"type":"string","title":"File Path","description":"Path to the file"},"file_type":{"type":"string","title":"File Type","description":"Type of file (PDF, DOC, etc.)"},"file_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size","description":"File size in bytes"},"file_size_human":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Size Human","description":"Human-readable file size (e.g., '2.5 MB')"},"md5_checksum":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md5 Checksum","description":"MD5 checksum of the file for integrity verification"},"sha256_checksum":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha256 Checksum","description":"SHA256 checksum of the file for integrity verification"},"cdn_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cdn Url","description":"CDN download URL"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Subject the file belongs to"},"canonical_subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Canonical Subject","description":"Main subject this folder aliases to (same as subject when this is the main folder)"},"level":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Level","description":"NZQA level (1, 2, or 3)"},"year":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Year","description":"Year the file was created"},"standard_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Standard Number","description":"Associated standard number"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"File description"},"keywords":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Keywords","description":"Extracted keywords"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"File creation timestamp"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"Last update timestamp"}},"type":"object","required":["id","file_name","file_path","file_type"],"title":"File"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"MetadataResponse":{"properties":{"total_files":{"type":"integer","title":"Total Files","description":"Total number of files"},"total_standards":{"type":"integer","title":"Total Standards","description":"Total number of standards"},"total_subjects":{"type":"integer","title":"Total Subjects","description":"Total number of subjects"},"total_keywords":{"type":"integer","title":"Total Keywords","description":"Total number of keywords"},"last_updated":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Updated","description":"Last update timestamp"},"database_size_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Database Size Mb","description":"Database size in MB"},"api_version":{"type":"string","title":"Api Version","description":"API version"},"cache_stats":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Cache Stats","description":"Cache statistics"},"pool_stats":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Pool Stats","description":"Connection pool statistics"}},"type":"object","required":["total_files","total_standards","total_subjects","total_keywords","api_version"],"title":"MetadataResponse","description":"Metadata response for API information"},"PdfTextBody":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"CDN URL under https://cdn.toasting.me/direct/{filename}","example":"https://cdn.toasting.me/direct/91524-exm-2023.pdf"},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path","description":"Local PDF path relative to an allowed root (PDF_ALLOWED_ROOTS)","example":"files/91524-exm-2023.pdf"},"pages":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Pages","description":"Optional 1-based page numbers to extract","example":[1,2]},"max_pages":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Max Pages","description":"Max pages when pages is omitted (1-200)","default":50,"example":50},"as_markdown":{"type":"boolean","title":"As Markdown","description":"Must be false or omitted. Markdown conversion is disabled (HTTP 415).","default":false,"example":false},"use_cache":{"type":"boolean","title":"Use Cache","description":"Use diskcache for repeated extractions","default":true,"example":true}},"type":"object","title":"PdfTextBody","description":"POST body for PDF text extraction."},"ResponseMetadata":{"properties":{"api_version":{"type":"string","title":"Api Version","description":"API version","default":"5.1.0.2"},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id","description":"Unique request identifier"},"cached":{"type":"boolean","title":"Cached","description":"Whether result was served from cache","default":false},"execution_time_ms":{"type":"number","title":"Execution Time Ms","description":"Request execution time in milliseconds","default":0.0},"timestamp":{"type":"string","format":"date-time","title":"Timestamp","description":"Response timestamp (ISO format, UTC)"}},"type":"object","title":"ResponseMetadata","description":"Metadata for StandardResponse - grouped for cleaner JSON output"},"SessionCreate":{"properties":{"device_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Device Name","description":"Device name"},"device_fingerprint":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Device Fingerprint","description":"Device fingerprint"},"tier":{"type":"string","pattern":"^(session|basic|premium|unlimited|free)$","title":"Tier","description":"Session tier (public can only use 'session')","default":"session"},"custom_rate_limit":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Custom Rate Limit","description":"Custom rate limit (requests per minute) - ADMIN ONLY"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"Session expiration date"},"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id","description":"Account ID to link this session to (optional)"}},"type":"object","title":"SessionCreate","description":"Request model for creating sessions"},"SessionRefresh":{"properties":{"extend_expiry":{"type":"boolean","title":"Extend Expiry","description":"Whether to extend session expiry","default":true},"access_token":{"anyOf":[{"type":"string","maxLength":4096},{"type":"null"}],"title":"Access Token","description":"Session JWT when the nzqa_session cookie is not available (cross-origin SPA / Bearer clients). Prefer Authorization: Bearer."}},"type":"object","title":"SessionRefresh","description":"Request model for refreshing sessions"},"Standard":{"properties":{"number":{"type":"string","title":"Number","description":"Standard number (5 digits)"},"title":{"type":"string","title":"Title","description":"Standard title"},"description":{"type":"string","title":"Description","description":"Standard description (usually empty if File also has it)","default":""},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Subject (usually empty if File also has it)"},"primary_subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Subject","description":"Top-level subject grouping"},"level":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Level","description":"NZQA level (usually None if File also has it)"},"credits":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credits","description":"Number of credits"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version","description":"Standard version"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Standard status (version_status)"},"assessment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assessment","description":"Assessment type (e.g. Internal, External)"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"Standard type (e.g. Unit Standard, Achievement Standard)"},"expired":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Expired","description":"Whether the standard is expired"},"literacy":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Literacy","description":"Literacy requirement flag"},"numeracy":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Numeracy","description":"Numeracy requirement flag"},"te_reo_matatini":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Te Reo Matatini","description":"Te Reo Matatini flag"},"subject_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Reference","description":"Subject reference"},"subject_reference_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Reference Number","description":"Subject reference number"},"achievement_criteria_achieved":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Achievement Criteria Achieved","description":"Achievement criteria – Achieved"},"achievement_criteria_merit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Achievement Criteria Merit","description":"Achievement criteria – Merit"},"achievement_criteria_excellence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Achievement Criteria Excellence","description":"Achievement criteria – Excellence"},"total_files":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Files","description":"Total number of files referencing this standard"},"effective_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Effective Date","description":"Effective date"},"expiry_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expiry Date","description":"Expiry date"},"files":{"anyOf":[{"items":{"$ref":"#/components/schemas/File"},"type":"array"},{"type":"null"}],"title":"Files","description":"Associated files"}},"type":"object","required":["number","title"],"title":"Standard","description":"Standard entity"},"StandardResponse_APIKeyInfo_":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"$ref":"#/components/schemas/APIKeyInfo"},{"type":"null"}],"description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[APIKeyInfo]"},"StandardResponse_Account_":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"$ref":"#/components/schemas/Account"},{"type":"null"}],"description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[Account]"},"StandardResponse_File_":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"$ref":"#/components/schemas/File"},{"type":"null"}],"description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[File]"},"StandardResponse_List_APIKeyInfo__":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"items":{"$ref":"#/components/schemas/APIKeyInfo"},"type":"array"},{"type":"null"}],"title":"Data","description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[List[APIKeyInfo]]"},"StandardResponse_MetadataResponse_":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"$ref":"#/components/schemas/MetadataResponse"},{"type":"null"}],"description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[MetadataResponse]"},"StandardResponse_Standard_":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"$ref":"#/components/schemas/Standard"},{"type":"null"}],"description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[Standard]"},"StandardResponse_StatsResponse_":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"$ref":"#/components/schemas/StatsResponse"},{"type":"null"}],"description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[StatsResponse]"},"StandardResponse_Subject_":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"$ref":"#/components/schemas/Subject"},{"type":"null"}],"description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[Subject]"},"StandardResponse_dict_":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data","description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[dict]"},"StandardResponse_list_":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Data","description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[list]"},"StatsResponse":{"properties":{"total_files":{"type":"integer","title":"Total Files","description":"Total number of files"},"total_standards":{"type":"integer","title":"Total Standards","description":"Total number of standards"},"total_subjects":{"type":"integer","title":"Total Subjects","description":"Total number of subjects"},"total_keywords":{"type":"integer","title":"Total Keywords","description":"Total number of keywords"},"last_updated":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Updated","description":"Last database update"},"database_size_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Database Size Mb","description":"Database size in MB"}},"type":"object","required":["total_files","total_standards","total_subjects","total_keywords"],"title":"StatsResponse","description":"Statistics response"},"StudyPackRequestV2":{"properties":{"standard_numbers":{"items":{"type":"string"},"type":"array","maxItems":20,"minItems":1,"title":"Standard Numbers","description":"List of standard numbers"},"file_types":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":20},{"type":"null"}],"title":"File Types","description":"File types to include"},"years":{"type":"integer","maximum":10.0,"minimum":1.0,"title":"Years","description":"Number of recent years to include","default":5},"include_difficulty_insights":{"type":"boolean","title":"Include Difficulty Insights","description":"Include difficulty insights based on attainment","default":true},"preferences":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Preferences","description":"Additional preferences"}},"type":"object","required":["standard_numbers"],"title":"StudyPackRequestV2","description":"Study pack request model (v2 - for POST endpoints)"},"Subject":{"properties":{"name":{"type":"string","title":"Name","description":"Subject name"},"canonical_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Canonical Name","description":"Canonical subject name"},"level":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Level","description":"Subject level"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Subject description"},"file_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Count","description":"Number of files in this subject"},"standard_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Standard Count","description":"Number of standards in this subject"},"keywords":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Keywords","description":"Subject keywords"},"variants":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Variants","description":"Subject name variants"}},"type":"object","required":["name"],"title":"Subject","description":"Subject entity"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"core__response__AutocompleteResponse":{"properties":{"suggestions":{"items":{"type":"string"},"type":"array","title":"Suggestions","description":"Autocomplete suggestions"},"query":{"type":"string","title":"Query","description":"Original query"},"total_suggestions":{"type":"integer","title":"Total Suggestions","description":"Total number of suggestions"}},"type":"object","required":["suggestions","query","total_suggestions"],"title":"AutocompleteResponse","description":"Autocomplete response"},"core__response__StandardResponse_AutocompleteResponse___1":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"$ref":"#/components/schemas/core__response__AutocompleteResponse"},{"type":"null"}],"description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[AutocompleteResponse]"},"core__response__StandardResponse_AutocompleteResponse___2":{"properties":{"status":{"type":"string","title":"Status","description":"Status string (success, error)"},"status_code":{"type":"integer","maximum":599.0,"minimum":200.0,"title":"Status Code","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Machine-readable error code (only present for errors)"},"data":{"anyOf":[{"$ref":"#/components/schemas/models__responses__AutocompleteResponse"},{"type":"null"}],"description":"Response data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}],"description":"Detailed error information with recovery guidance (only present for errors)"},"meta":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata (api_version, request_id, cached, execution_time_ms, timestamp)"}},"type":"object","required":["status","status_code","message"],"title":"StandardResponse[AutocompleteResponse]"},"models__responses__AutocompleteResponse":{"properties":{"suggestions":{"items":{"type":"string"},"type":"array","title":"Suggestions","description":"List of autocomplete suggestions"},"query":{"type":"string","title":"Query","description":"Original query"},"total_suggestions":{"type":"integer","title":"Total Suggestions","description":"Total number of suggestions"}},"type":"object","required":["suggestions","query","total_suggestions"],"title":"AutocompleteResponse","description":"Autocomplete response model"}}},"tags":[{"name":"Info","description":"API information, health checks, metadata, cache statistics, and rate limit info"},{"name":"Search","description":"Advanced search with smart ranking, autocomplete, and click tracking"},{"name":"Typesense","description":"High-performance Typesense retrieval with SmartRanker-compatible weighting"},{"name":"PDF","description":"PDF plain-text extraction from CDN URLs or safe local paths (markdown returns HTTP 415)"},{"name":"Files","description":"File listing with comprehensive filtering, sorting, and detailed file information"},{"name":"Standards","description":"NZQA standards by number, subject, level, with related standards"},{"name":"Subjects","description":"Subject listing and detailed subject information with metadata"},{"name":"Browse","description":"Fast subject/level package: standards + files (+ rates-only attainment) in one call"},{"name":"Analytics","description":"Usage analytics (disabled — returns HTTP 410 Gone)"},{"name":"Attainment","description":"Historical attainment rates (no student headcounts) with filtering and trend analysis"},{"name":"Combinations","description":"Combined resource packages (disabled — returns HTTP 410 Gone)"},{"name":"Bulk","description":"Bulk operations for standards and search"},{"name":"Accounts","description":"User account registration, login, profile management, and retrieval"},{"name":"Auth","description":"Public session authentication with refresh token rotation and session management"},{"name":"Admin - Keys","description":"API key management with creation, rotation, IP whitelisting, and statistics"},{"name":"Admin - Sessions","description":"Admin session management with creation, listing, and revocation"},{"name":"Admin - Analytics","description":"Audit logs, search analytics, popular queries, and data cleanup"},{"name":"Admin - Security","description":"IP blocking, abuse detection statistics, and security monitoring"},{"name":"Admin - Accounts","description":"Account management with CRUD operations, unlocking, email verification, and statistics"}]}