{"components":{"schemas":{"Error":{"properties":{"detail":{"type":"string"},"status":{"type":"integer"}},"type":"object"}},"securitySchemes":{"bearerAuth":{"bearerFormat":"JWT","scheme":"bearer","type":"http"}}},"info":{"contact":{"email":"gef.land.degradation@gmail.com","name":"Trends.Earth Team"},"description":"API for managing Scripts, Users, and Executions in Trends.Earth","license":{"name":"MIT","url":"https://opensource.org/licenses/MIT"},"title":"Trends.Earth API","version":"1.0.0"},"openapi":"3.0.3","paths":{"/api/v1/admin/client-stats":{"get":{"description":"**Access**: Restricted to users with `role: \"SUPERADMIN\"`\n**Purpose**: Provides visibility into client platform and version usage\n\n**Query Parameters**:\n- `days`: Time period in days (7, 14, 30, 60, 90). Default: 30\n- `client_type`: Optional filter (qgis_plugin, api_ui, cli)\n\n**Response Schema**:\n```json\n{\n  \"period_days\": 30,\n  \"generated_at\": \"2026-03-18T12:00:00Z\",\n  \"platform_summary\": {\n    \"qgis_plugin\": {\"active_users\": 450, \"total_users\": 800},\n    \"api_ui\": {\"active_users\": 50, \"total_users\": 120}\n  },\n  \"plugin_stats\": {\n    \"by_plugin_version\": [\n      {\n        \"version\": \"2.2.4\",\n        \"user_count\": 450,\n        \"by_qgis_version\": [\n          {\"qgis_version\": \"3.34\", \"count\": 300}\n        ],\n        \"by_os\": [\n          {\"os\": \"Windows\", \"count\": 280}\n        ]\n      }\n    ],\n    \"by_qgis_version\": [\n      {\n        \"qgis_version\": \"3.34\",\n        \"user_count\": 500,\n        \"by_plugin_version\": [\n          {\"version\": \"2.2.4\", \"count\": 300}\n        ]\n      }\n    ],\n    \"by_os\": [\n      {\"os\": \"Windows\", \"user_count\": 520}\n    ]\n  },\n  \"api_ui_stats\": {\n    \"by_version\": [{\"version\": \"1.5.2\", \"user_count\": 35}]\n  },\n  \"cli_stats\": {\n    \"by_version\": [{\"version\": \"1.0.0\", \"user_count\": 10}]\n  }\n}\n```\n\n**Use Cases**:\n- Monitor plugin version adoption\n- Plan deprecation of old versions\n- Understand platform and OS distribution\n- Inform release decisions\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Superadmin access required\n- `500 Internal Server Error`: Failed to compute statistics","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Get aggregated client platform usage statistics."}},"/api/v1/admin/news":{"post":{"description":"**Authentication**: JWT token required\n**Access**: ADMIN and SUPERADMIN only\n\n**Request Body**:\n```json\n{\n  \"title\": \"New Feature Released\",\n  \"message\": \"We're excited to announce...\",\n  \"link_url\": \"https://docs.trends.earth/feature\",\n  \"link_text\": \"Read more\",\n  \"publish_at\": \"2025-01-15T10:00:00Z\",\n  \"expires_at\": \"2025-02-15T10:00:00Z\",\n  \"target_platforms\": \"app,webapp\",\n  \"target_roles\": \"USER,ADMIN\",\n  \"min_version\": \"2.0.0\",\n  \"max_version\": null,\n  \"is_active\": true,\n  \"priority\": 10,\n  \"news_type\": \"info\"\n}\n```\n\n**Required Fields**: title, message\n**Optional Fields**: All others (see schema above)\n**target_roles**: Comma-separated list of roles (USER, ADMIN, SUPERADMIN).\n                  Empty or null means visible to all users.\n\n**Response**: Created news item object\n\n**Error Responses**:\n- `400 Bad Request`: Missing required fields\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Not authorized (ADMIN+ required)","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Create a new news item."}},"/api/v1/admin/news/{news_id}":{"delete":{"description":"**Authentication**: JWT token required\n**Access**: ADMIN and SUPERADMIN only\n**Path Parameters**:\n- `news_id`: UUID of the news item to delete\n\n**Response**:\n```json\n{\n  \"status\": \"success\",\n  \"message\": \"News item deleted\"\n}\n```\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Not authorized\n- `404 Not Found`: News item not found","parameters":[{"description":"Path parameter: news_id","in":"path","name":"news_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Delete a news item."}},"/api/v1/admin/news/{news_id}/translations":{"put":{"description":"**Authentication**: JWT token required\n**Access**: ADMIN and SUPERADMIN only\n\n**Path Parameters**:\n- `news_id`: UUID of the news item\n\n**Request Body**:\n```json\n{\n  \"translations\": {\n    \"es\": {\n      \"title\": \"T\u00edtulo traducido\",\n      \"message\": \"Mensaje traducido...\",\n      \"link_text\": \"Leer m\u00e1s\",\n      \"is_machine_translated\": true\n    },\n    \"fr\": {\n      \"title\": \"Titre traduit\",\n      \"message\": \"Message traduit...\",\n      \"link_text\": \"En savoir plus\",\n      \"is_machine_translated\": true\n    }\n  }\n}\n```\n\n**Notes**:\n- Supported languages: ar, es, fa, fr, pt, ru, sw, zh\n- To delete a translation, pass null or omit it from the request\n- `is_machine_translated` defaults to true\n\n**Response**: Updated translations object\n\n**Error Responses**:\n- `400 Bad Request`: Invalid data\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Not authorized\n- `404 Not Found`: News item not found","parameters":[{"description":"Path parameter: news_id","in":"path","name":"news_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Update translations for a news item."}},"/api/v1/admin/news/{news_id}/translations/{lang}":{"delete":{"description":"**Authentication**: JWT token required\n**Access**: ADMIN and SUPERADMIN only\n\n**Path Parameters**:\n- `news_id`: UUID of the news item\n- `lang`: Language code to delete (e.g., 'es', 'fr')\n\n**Response**:\n```json\n{\n  \"status\": \"success\",\n  \"message\": \"Translation deleted\"\n}\n```","parameters":[{"description":"Path parameter: news_id","in":"path","name":"news_id","required":true,"schema":{"type":"string"}},{"description":"Path parameter: lang","in":"path","name":"lang","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Delete a specific translation for a news item."}},"/api/v1/bulk-email":{"post":{"description":"Create a draft bulk email.","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Create a draft bulk email."}},"/api/v1/bulk-email/config":{"get":{"description":"Return bulk email config (max_recipients, from_email) for superadmins.","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Return bulk email config (max_recipients, from_email) for superadmins."}},"/api/v1/bulk-email/recipient-list":{"post":{"description":"Create a named recipient list from filter criteria.","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Create a named recipient list from filter criteria."}},"/api/v1/bulk-email/recipient-list/preview":{"post":{"description":"Preview users matching filter criteria without saving.","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Preview users matching filter criteria without saving."}},"/api/v1/bulk-email/recipient-list/{list_id}":{"patch":{"description":"Update an existing saved recipient list.","parameters":[{"description":"Path parameter: list_id","in":"path","name":"list_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Update an existing saved recipient list."}},"/api/v1/bulk-email/{bulk_email_id}":{"delete":{"description":"Delete a draft bulk email.","parameters":[{"description":"Path parameter: bulk_email_id","in":"path","name":"bulk_email_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Delete a draft bulk email."}},"/api/v1/bulk-email/{bulk_email_id}/restore-draft":{"post":{"description":"Restore a SENT or FAILED bulk email back to DRAFT status.","parameters":[{"description":"Path parameter: bulk_email_id","in":"path","name":"bulk_email_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Restore a SENT or FAILED bulk email back to DRAFT status."}},"/api/v1/bulk-email/{bulk_email_id}/send":{"post":{"description":"Optional body: {\"code\": \"123456\"} when responding to a 428 challenge.\nReturns 428 with {requires_verification, recipient_count} when threshold exceeded.","parameters":[{"description":"Path parameter: bulk_email_id","in":"path","name":"bulk_email_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Send a bulk email."}},"/api/v1/bulk-email/{bulk_email_id}/send-test":{"post":{"description":"Send bulk email as test to superadmins only (no 2FA, status unchanged).","parameters":[{"description":"Path parameter: bulk_email_id","in":"path","name":"bulk_email_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Send bulk email as test to superadmins only (no 2FA, status unchanged)."}},"/api/v1/bulk-email/{bulk_email_id}/send-test-self":{"post":{"description":"Send bulk email as test to the requesting user only (no 2FA, status unchanged).","parameters":[{"description":"Path parameter: bulk_email_id","in":"path","name":"bulk_email_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Send bulk email as test to the requesting user only (no 2FA, status unchanged)."}},"/api/v1/bulk-email/{bulk_email_id}/send-verification":{"post":{"description":"Generate and email a 6-digit OTP to the caller for this bulk email.","parameters":[{"description":"Path parameter: bulk_email_id","in":"path","name":"bulk_email_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Generate and email a 6-digit OTP to the caller for this bulk email."}},"/api/v1/data/boundaries":{"get":{"description":"Returns metadata and download URLs from geoBoundaries API responses.\nClients should use gjDownloadURL to fetch the actual GeoJSON geometry.\n\nQuery Parameters:\n- level: Administrative level (0, 1, or 0,1 for mixed levels, default: 0)\n- release_type: geoBoundaries release type\n  (gbOpen, gbHumanitarian, gbAuthoritative, default: gbOpen)\n- id: Filter by ID (boundaryISO for ADM0, shapeID for ADM1)\n- iso: Filter by ISO country code (applies to both levels)\n- name: Filter by name (partial match, case-insensitive) - ONLY works for level=0\n  (ADM0). Not supported for level=1 as there are no download links for\n  individual admin1 units, only country-level downloads.\n- page: Page number for pagination (default: 1)\n- per_page: Results per page (default: 100, max: 1000)\n- created_at_since: Filter boundaries created since this datetime (ISO format)\n- updated_at_since: Filter boundaries updated since this datetime (ISO format)\n\nRelease Types (as per geoBoundaries API):\n- gbOpen: CC-BY 4.0 compliant, most open license (default)\n- gbHumanitarian: Mirrored from UN OCHA, may have less open licensure\n- gbAuthoritative: Mirrored from UN SALB, verified through in-country\n  processes, no commercial use\n\nReturns:\n- 200: Boundary metadata matching query criteria\n- 400: Invalid query parameters\n- 404: No boundaries found matching criteria\n\nExample Requests:\n- /api/v1/data/boundaries?level=0&iso=USA\n- /api/v1/data/boundaries?level=0&name=united (name filter only for ADM0)\n- /api/v1/data/boundaries?level=1&iso=USA (ADM1 by country)\n- /api/v1/data/boundaries?level=0,1&iso=USA (mixed levels)\n- /api/v1/data/boundaries?release_type=gbHumanitarian&iso=SYR\n- /api/v1/data/boundaries?release_type=gbAuthoritative&level=0\n- /api/v1/data/boundaries?updated_at_since=2023-01-01T00:00:00Z\n\nNote: Name filtering is NOT supported for level=1 (ADM1) because geoBoundaries\nonly provides country-level downloads for admin1 units, not individual polygon\ndownloads. Use the hierarchy endpoint to see admin1 unit names.\n\nResponse includes:\n- boundaryISO: ISO country code\n- releaseType: Release type (gbOpen, gbHumanitarian, or gbAuthoritative)\n- boundaryName: Name of the boundary\n- boundaryType: \"ADM0\" or \"ADM1\"\n- Continent: Continent name\n- buildDate: Date the boundary was built (use for updated_at)\n- gjDownloadURL: GeoJSON download URL (use this to fetch geometry)\n- tjDownloadURL: TopoJSON download URL\n- Other geoBoundaries metadata fields","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Get administrative boundary metadata with download URLs."}},"/api/v1/data/boundaries/last-updated":{"get":{"description":"Returns the latest updated_at datetime from both ADM0 and ADM1 boundary\ntables. Useful for cache invalidation and determining if boundary data\nneeds to be refreshed.\n\nQuery Parameters:\n- release_type: geoBoundaries release type\n  (gbOpen, gbHumanitarian, gbAuthoritative, default: gbOpen)\n\nReturns:\n- 200: Last updated timestamp\n- 400: Invalid query parameters\n- 404: No boundary data found\n- 500: Server error\n\nResponse Structure:\n{\n    \"data\": {\n        \"last_updated\": \"2025-01-26T10:30:00.000000\",\n        \"release_type\": \"gbOpen\"\n    }\n}","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Get the most recent modification timestamp across all boundaries."}},"/api/v1/data/boundaries/list":{"get":{"description":"Returns a complete list without pagination, including GeoJSON and TopoJSON\ndownload URLs for both ADM0 and ADM1 levels. ADM1 boundaries are nested\nunder their parent ADM0 country.\n\nQuery Parameters:\n- release_type: geoBoundaries release type\n  (gbOpen, gbHumanitarian, gbAuthoritative, default: gbOpen)\n\nReturns:\n- 200: Hierarchical boundary list with download links organized by country\n- 400: Invalid query parameters\n- 500: Server error\n\nResponse Structure:\n{\n    \"data\": [\n        {\n            \"boundaryISO\": \"USA\",\n            \"boundaryName\": \"United States\",\n            \"releaseType\": \"gbOpen\",\n            \"adm0_geojson_url\": \"https://...\",\n            \"adm0_topojson_url\": \"https://...\",\n            \"adm1_geojson_url\": \"https://...\",\n            \"adm1_topojson_url\": \"https://...\",\n            \"admin1_units\": [\n                {\n                    \"shapeID\": \"66186276B98555229882405\",\n                    \"shapeName\": \"Washington\"\n                },\n                ...\n            ]\n        },\n        ...\n    ]\n}","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Get hierarchical list of all boundaries with ADM1 nested under ADM0."}},"/api/v1/execution":{"get":{"description":"**Authentication**: JWT token required\n**Access**: Admin users can see all executions, regular users see only their own\n**Admin Features**: ADMIN+ users can filter by user_id to view other users'\n  executions\n\n**Query Parameters**:\n- `user_id`: Filter executions by specific user ID (admin-only feature)\n- `updated_at`: Filter executions started after specific timestamp (ISO 8601)\n- `status`: Filter by execution status (PENDING, RUNNING, SUCCESS, FAILED,\n  CANCELLED)\n- `include`: Comma-separated list of additional fields to include\n- `exclude`: Comma-separated list of fields to exclude\n- `filter`: General search/filter across execution attributes\n- `sort`: Sort field (prefix with '-' for descending, e.g., '-updated_at')\n- `page`: Page number for pagination (triggers pagination when provided)\n- `per_page`: Items per page (1-100, default: 20, max without pagination:\n  varies by permission)\n\n**Response Schema (without pagination)**:\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"exec-123\",\n      \"script_id\": \"script-456\",\n      \"status\": \"SUCCESS\",\n      \"params\": {\n        \"region\": \"africa\",\n        \"analysis_type\": \"vegetation_change\"\n      },\n      \"user_id\": \"user-789\",\n      \"created_at\": \"2025-01-15T10:30:00Z\",\n      \"updated_at\": \"2025-01-15T11:45:00Z\",\n      \"start_time\": \"2025-01-15T10:31:00Z\",\n      \"end_time\": \"2025-01-15T11:45:00Z\",\n      \"results\": {\n        \"analysis_complete\": true,\n        \"output_files\": [\"vegetation_2023.tif\", \"change_summary.json\"]\n      }\n    }\n  ]\n}\n```\n\n**Response Schema (with pagination)**:\n```json\n{\n  \"data\": [...],\n  \"page\": 1,\n  \"per_page\": 20,\n  \"total\": 1250\n}\n```\n\n**Admin Query Examples**:\n- `?user_id=123` - View executions for specific user (admin only)\n- `?status=FAILED` - Find all failed executions across system\n- `?updated_at=2025-01-15T00:00:00Z` - Find executions updated since date\n\n**Regular User Behavior**:\n- Non-admin users: Only see their own executions regardless of user_id parameter\n- Admin users: Can see all executions, can filter by user_id\n\n**Filtering Examples**:\n- `?filter=vegetation` - Find executions with \"vegetation\" in script or params\n- `?filter=2024-12` - Find executions from December 2024\n- `?status=RUNNING&sort=-created_at` - Find currently running executions,\n  newest first\n\n**Sorting Examples**:\n- `?sort=updated_at` - Sort by last update ascending\n- `?sort=-created_at` - Sort by creation time descending (newest first)\n- `?sort=status` - Sort by execution status alphabetically\n\n**Timestamp Filtering**:\n- `updated_at` parameter accepts ISO 8601 format: `2025-01-15T10:30:00Z`\n- Returns executions that started after the specified timestamp\n- Useful for incremental synchronization and monitoring\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `500 Internal Server Error`: Failed to retrieve executions","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve all executions with admin filtering and cross-user visibility."}},"/api/v1/execution/export":{"get":{"description":"**Authentication**: JWT token required\n**Authorization**: ADMIN or SUPERADMIN required\n\n**Query Parameters**:\n- ``date_field``: Column to filter by (``start_date`` or ``end_date``)\n- ``date_from``: ISO 8601 start date (inclusive)\n- ``date_to``:   ISO 8601 end date   (inclusive)\n\n**Response**: ``text/csv`` attachment named ``executions_export_<timestamp>.csv``\n\n**Error Responses**:\n- ``400`` \u2013 invalid ``date_field``\n- ``403`` \u2013 insufficient privileges","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Export executions as a CSV file (admin only)."}},"/api/v1/execution/user":{"get":{"description":"**Authentication**: JWT token required\n**Access**: Returns only executions belonging to the current user\n**Scope**: User-specific endpoint - users can only see their own executions\n\n**Query Parameters**:\n- `include`: Comma-separated list of additional fields to include in response\n- `exclude`: Comma-separated list of fields to exclude from response\n- `filter`: Search/filter executions by script name, status, or other attributes\n- `sort`: Sort field (prefix with '-' for descending, e.g., '-created_at',\n  '-updated_at')\n- `updated_at`: Filter executions started after specific timestamp (ISO 8601)\n- `page`: Page number for pagination (triggers pagination when provided)\n- `per_page`: Items per page (1-100, default: 20)\n\n**Response Schema (without pagination)**:\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"exec-123\",\n      \"script_id\": \"script-456\",\n      \"status\": \"SUCCESS\",\n      \"params\": {\n        \"region\": \"africa\",\n        \"year_start\": 2020,\n        \"year_end\": 2023\n      },\n      \"user_id\": \"user-789\",\n      \"created_at\": \"2025-01-15T10:30:00Z\",\n      \"updated_at\": \"2025-01-15T11:45:00Z\",\n      \"start_time\": \"2025-01-15T10:31:00Z\",\n      \"end_time\": \"2025-01-15T11:45:00Z\",\n      \"results\": {\n        \"output_file\": \"analysis_results.json\",\n        \"summary\": \"Processing completed successfully\"\n      }\n    }\n  ]\n}\n```\n\n**Response Schema (with pagination)**:\n```json\n{\n  \"data\": [...],\n  \"page\": 1,\n  \"per_page\": 20,\n  \"total\": 45\n}\n```\n\n**Execution Status Values**:\n- `PENDING`: Execution queued, waiting to start\n- `RUNNING`: Currently executing\n- `SUCCESS`: Completed successfully with results\n- `FAILED`: Execution failed with error\n- `CANCELLED`: Execution was cancelled before completion\n\n**Filtering Examples**:\n- `?filter=land-analysis` - Find executions related to \"land-analysis\" script\n- `?filter=status:SUCCESS` - Find only successful executions\n- `?filter=2024` - Find executions from 2024 (searches in timestamps)\n\n**Sorting Examples**:\n- `?sort=created_at` - Sort by creation time ascending\n- `?sort=-updated_at` - Sort by last update descending (most recent first)\n- `?sort=status` - Sort by execution status\n\n**Timestamp Filtering**:\n- `?updated_at=2025-01-15T10:30:00Z` - Find executions started after date\n- `updated_at` parameter accepts ISO 8601 format\n- Returns executions that started after the specified timestamp\n- Useful for incremental synchronization and monitoring recent activity\n\n**Pagination Examples**:\n- `?page=1&per_page=50` - Get first 50 executions with pagination\n- `?page=2&per_page=20` - Get second page with 20 executions per page\n- **Performance Note**: Without pagination, results are limited to 1000 executions\n\n**Field Control Examples**:\n- `?include=script,logs` - Include script details and execution logs\n- `?include=script_name,user_name` - Include script and user names\n- `?exclude=params,results` - Exclude verbose parameter and result data\n\n**Combined Query Examples**:\n    - `?updated_at=2025-08-01&include=script&page=1&per_page=20`\n- `?filter=vegetation&sort=-created_at&exclude=params`\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `500 Internal Server Error`: Failed to retrieve executions","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve executions for the current authenticated user."}},"/api/v1/execution/{execution}":{"patch":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin level access required\n**Access**: Only admin users can update execution properties\n\n**Path Parameters**:\n- `execution`: Execution ID (UUID format)\n\n**Request Body**:\n```json\n{\n  \"status\": \"CANCELLED\",\n  \"progress\": 100,\n  \"results\": {\n    \"reason\": \"User requested cancellation\"\n  }\n}\n```\n\n**Updatable Fields**:\n- `status`: Execution status (PENDING, RUNNING, SUCCESS, FAILED, CANCELLED)\n- `progress`: Progress percentage (0-100)\n- `results`: Results data (JSON object)\n- `end_date`: End timestamp (ISO 8601 format)\n\n**Response Schema**:\n```json\n{\n  \"data\": {\n    \"id\": \"abc123-def456\",\n    \"script_id\": \"vegetation-analysis\",\n    \"status\": \"CANCELLED\",\n    \"progress\": 100,\n    \"user_id\": \"user-789\",\n    \"start_date\": \"2025-01-15T10:30:00Z\",\n    \"end_date\": \"2025-01-15T10:35:00Z\",\n    \"results\": {\n      \"reason\": \"User requested cancellation\"\n    }\n  }\n}\n```\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Admin access required\n- `404 Not Found`: Execution does not exist\n- `422 Unprocessable Entity`: Invalid request data\n- `500 Internal Server Error`: Server error","parameters":[{"description":"Path parameter: execution","in":"path","name":"execution","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Update an execution's properties (admin only)."}},"/api/v1/execution/{execution}/batch-logs":{"get":{"description":"**Authentication**: JWT token required\n**Access**: Restricted to ADMIN and SUPERADMIN users only\n**Purpose**: Provides CloudWatch container logs for Batch-based\n  script executions, analogous to the Docker logs endpoint.\n\n**Path Parameters**:\n- `execution`: The ID of the execution to retrieve logs for.\n\n**Success Response Schema**:\n```json\n{\n  \"data\": [\n    {\n      \"id\": 0,\n      \"created_at\": \"2025-08-04T10:30:00.123Z\",\n      \"text\": \"Log message from the container\",\n      \"job_name\": \"extract\"\n    }\n  ]\n}\n```\n\n**Response Fields**:\n- `id`: A sequential identifier for the log line (for ordering).\n- `created_at`: The timestamp of the log entry (ISO 8601 format).\n- `text`: The content of the log line.\n- `job_name`: The Batch job step name that produced this log line.\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required.\n- `403 Forbidden`: Insufficient privileges (ADMIN+ required).\n- `404 Not Found`: The specified execution or its logs do not exist.\n- `500 Internal Server Error`: Failed to retrieve logs due to a server-side\n  issue.","parameters":[{"description":"Path parameter: execution","in":"path","name":"execution","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve AWS Batch (CloudWatch) logs for a specific execution."}},"/api/v1/execution/{execution}/cancel":{"post":{"description":"**Authentication**: JWT token required\n**Access**: Users can cancel their own executions, ADMIN+ users can cancel any\n**Purpose**: Immediately stops execution processing, cleans up resources, and\ncancels any associated Google Earth Engine tasks that may have been started.\n\n**Path Parameters**:\n- `execution`: The ID of the execution to cancel\n\n**Request**: No request body required - this is a POST endpoint that triggers\ncancellation\n\n**Usage Examples**:\n```bash\n# Cancel your own execution\ncurl -X POST \"https://api.trends.earth/api/v1/execution/abc123-def456/cancel\"          -H \"Authorization: Bearer your_jwt_token\"\n\n# Admin canceling any user's execution\ncurl -X POST \"https://api.trends.earth/api/v1/execution/xyz789-uvw012/cancel\"          -H \"Authorization: Bearer admin_jwt_token\"\n```\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"execution\": {\n      \"id\": \"abc123-def456\",\n      \"script_id\": \"vegetation-analysis\",\n      \"status\": \"CANCELLED\",\n      \"params\": {\n        \"region\": \"africa\",\n        \"year_start\": 2020,\n        \"year_end\": 2023\n      },\n      \"user_id\": \"user-789\",\n      \"created_at\": \"2025-01-15T10:30:00Z\",\n      \"updated_at\": \"2025-01-15T10:35:00Z\",\n      \"start_time\": \"2025-01-15T10:31:00Z\",\n      \"end_time\": \"2025-01-15T10:35:00Z\",\n      \"progress\": 100\n    },\n    \"cancellation_details\": {\n      \"execution_id\": \"abc123-def456\",\n      \"previous_status\": \"RUNNING\",\n      \"docker_service_stopped\": true,\n      \"docker_container_stopped\": false,\n      \"batch_jobs_terminated\": [\n        {\n          \"job_id\": \"aws-batch-job-id-123\",\n          \"name\": \"extract\",\n          \"previous_status\": \"RUNNING\",\n          \"success\": true\n        }\n      ],\n      \"gee_tasks_cancelled\": [\n        {\n          \"task_id\": \"6CIGR7EG2J45GJ2DN2J7X3WZ\",\n          \"success\": true,\n          \"error\": null,\n          \"status\": \"CANCELLED\"\n        },\n        {\n          \"task_id\": \"YBKKBHM2V63JYBVIPCCRY7A2\",\n          \"success\": true,\n          \"error\": null,\n          \"status\": \"CANCELLED\"\n        }\n      ],\n      \"errors\": []\n    }\n  }\n}\n```\n\n**Response Fields**:\n- `execution`: The updated execution object with `CANCELLING` status\n- `cancellation_details`: Dispatch metadata for background cancellation:\n  - `execution_id`: ID of the execution being cancelled\n  - `previous_status`: Status before cancellation request\n  - `new_status`: Set to `CANCELLING`\n  - `queued`: `true` when cancellation work was enqueued\n  - `task_id`: Celery task ID handling cancellation\n  - `errors`: Any immediate dispatch errors\n\n **Cancellation Process**:\n 1. Request transitions execution to `CANCELLING`\n 2. API returns `202 Accepted` immediately\n 3. Background worker cancels compute resources and GEE tasks\n 4. Background worker sets final status to `CANCELLED`\n\n**Cancellable States**:\n- `PENDING`: Execution queued, waiting to start\n- `READY`: Execution initialized and starting\n- `RUNNING`: Currently executing\n\n**Non-Cancellable States**:\n- `FINISHED`: Execution completed successfully\n- `FAILED`: Execution already failed\n- `CANCELLED`: Execution already cancelled\n- `CANCELLING`: Cancellation already in progress\n\n**Error Responses**:\n- `400 Bad Request`: Execution is not in a cancellable state\n  ```json\n  {\n    \"status\": 400,\n    \"detail\": \"Cannot cancel execution in FINISHED state\"\n  }\n  ```\n- `409 Conflict`: Cancellation is already in progress\n  ```json\n  {\n    \"status\": 409,\n    \"detail\": \"Cancellation already in progress for this execution\"\n  }\n  ```\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: User can only cancel their own executions (unless admin)\n  ```json\n  {\n    \"status\": 403,\n    \"detail\": \"You can only cancel your own executions\"\n  }\n  ```\n- `404 Not Found`: Execution does not exist\n- `500 Internal Server Error`: Cancellation process failed\n\n**Partial Cancellation**: The endpoint will attempt to cancel all associated\nresources even if some steps fail. Check the `cancellation_details.errors` array\nfor any issues encountered during the process.","parameters":[{"description":"Path parameter: execution","in":"path","name":"execution","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Cancel a running execution and any associated Google Earth Engine tasks."}},"/api/v1/execution/{execution}/docker-logs":{"get":{"description":"**Authentication**: JWT token required\n**Access**: Restricted to ADMIN and SUPERADMIN users only\n**Purpose**: Provides raw Docker service logs for debugging and monitoring\n  individual script executions.\n\n**Path Parameters**:\n- `execution`: The ID of the execution to retrieve logs for.\n\n**Success Response Schema**:\n```json\n{\n  \"data\": [\n    {\n      \"id\": 0,\n      \"created_at\": \"2025-08-04T10:30:00.123456Z\",\n      \"text\": \"Log message from the container\"\n    },\n    {\n      \"id\": 1,\n      \"created_at\": \"2025-08-04T10:30:01.789012Z\",\n      \"text\": \"Another log message\"\n    }\n  ]\n}\n```\n\n**Response Fields**:\n- `id`: A sequential identifier for the log line (for ordering).\n- `created_at`: The timestamp of the log entry (ISO 8601 format).\n- `text`: The content of the log line.\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required.\n- `403 Forbidden`: Insufficient privileges (ADMIN+ required).\n- `404 Not Found`: The specified execution or its logs do not exist.\n- `500 Internal Server Error`: Failed to retrieve logs due to a server-side\n  issue.","parameters":[{"description":"Path parameter: execution","in":"path","name":"execution","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve Docker service logs for a specific execution."}},"/api/v1/execution/{execution}/download-results":{"get":{"description":"**Authentication**: JWT token required\n**Access**: Execution owners, admins, or superadmins\n**Format**: Returns results as downloadable JSON file\n\n**Path Parameters**:\n- `execution`: Execution ID (UUID format)\n\n**Response**: JSON file download\n- **Content-Type**: `text/plain`\n- **Content-Disposition**: `attachment; filename=results.json`\n- **Body**: JSON-formatted execution results\n\n**Use Cases**:\n- Download analysis results for offline processing\n- Archive execution outputs for record keeping\n- Share results with external stakeholders\n- Integrate with external reporting systems\n\n**Error Responses**:\n- `404 Not Found`: Execution does not exist or user lacks access\n- `500 Internal Server Error`: Failed to retrieve results","parameters":[{"description":"Path parameter: execution","in":"path","name":"execution","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Download execution results as a JSON file."}},"/api/v1/execution/{execution}/log":{"post":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin level access required (ADMIN or SUPERADMIN)\n**Purpose**: Manually add log entries for debugging, monitoring, or audit purposes\n\n**Path Parameters**:\n- `execution`: Execution ID (UUID format)\n\n**Request Schema**:\n```json\n{\n  \"level\": \"INFO\",\n  \"message\": \"Custom log message for execution monitoring\",\n  \"details\": {\n    \"component\": \"manual_logging\",\n    \"admin_action\": true,\n    \"timestamp\": \"2025-01-15T10:30:00Z\"\n  }\n}\n```\n\n**Request Fields**:\n- `level`: Log level - \"DEBUG\", \"INFO\", \"WARNING\", \"ERROR\" (required)\n- `message`: Log message content (required, max 1000 characters)\n- `details`: Additional structured data (optional JSON object)\n- `timestamp`: Log timestamp (optional, defaults to current time)\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"id\": \"log-789\",\n    \"execution_id\": \"abc123-def456\",\n    \"timestamp\": \"2025-01-15T10:30:00Z\",\n    \"level\": \"INFO\",\n    \"message\": \"Custom log message for execution monitoring\",\n    \"details\": {\n      \"component\": \"manual_logging\",\n      \"admin_action\": true,\n      \"created_by\": \"admin-user-123\"\n    }\n  }\n}\n```\n\n**Use Cases**:\n- Add administrative notes to execution logs\n- Record manual interventions or troubleshooting steps\n- Supplement automated logs with human observations\n- Document resolution of execution issues\n\n**Log Integration**:\n- Manual logs appear alongside automated execution logs\n- Preserved in execution log history\n- Included in log exports and monitoring dashboards\n- Tagged with creating admin user information\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Admin access required\n- `404 Not Found`: Execution does not exist\n- `422 Unprocessable Entity`: Invalid request data or validation failed\n- `500 Internal Server Error`: Log creation failed","parameters":[{"description":"Path parameter: execution","in":"path","name":"execution","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Create a log entry for a specific execution (admin only)."}},"/api/v1/gee-oauth/callback":{"post":{"description":"plugin) that open the browser but cannot receive the Google redirect directly.\n\nThe user ID is resolved from the ``state`` parameter via the reverse mapping\nwritten by the initiate endpoint, so no authentication header is needed.\n\n**Content-Type**: application/json\n\n**Request Body**:\n- ``code`` (str, required): Authorization code from Google\n- ``state`` (str, required): CSRF state token from the initiate call\n\n**Response**:\n```json\n{\n  \"data\": {\n    \"status\": \"connected\",\n    \"gee_google_email\": \"user@gmail.com\",\n    \"client_type\": \"qgis_plugin\"\n  }\n}\n```\n\n``client_type`` reflects the originating client (``\"qgis_plugin\"`` or ``null``)\nand is used by the callback page to show a context-appropriate success message.\n\n**Error Responses**:\n- ``400``: Missing/invalid code or state\n- ``404``: User not found\n- ``500``: Token exchange or save failure","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Complete GEE OAuth flow without requiring a JWT \u2014 for clients (e.g. the QGIS"}},"/api/v1/google-groups/info":{"get":{"description":"**Authentication**: Not required (public endpoint)\n**Access**: Public endpoint for displaying available groups\n**Purpose**: Provide group information for registration and informational purposes\n\n**Response Schema**:\n```json\n{\n  \"data\": {\n    \"available_groups\": {\n      \"trends_earth_users\": {\n        \"group_name\": \"Trends.Earth Users\",\n        \"description\": \"General Trends.Earth user community for announcements and\n        discussions\"\n      },\n      \"trendsearth\": {\n        \"group_name\": \"TrendsEarth\",\n        \"description\": \"TrendsEarth platform users for technical updates and\n        support\"\n      }\n    }\n  }\n}\n```\n\n**Available Groups Information**:\n- `trends_earth_users`: General user community\n  - Announcements and general discussions\n  - User community support and collaboration\n  - Platform updates and news\n- `trendsearth`: Platform-specific group\n  - Technical updates and platform changes\n  - Feature announcements and improvements\n  - User support and troubleshooting\n\n**Public Information Only**:\n- Group names and descriptions provided\n- No sensitive information exposed\n- No actual group email addresses shown\n- Service status not disclosed\n\n**Use Cases**:\n- Display available groups during user registration\n- Show group options in user preference settings\n- Provide information for marketing and outreach\n- Public documentation and help pages\n\n**Security Considerations**:\n- No authentication required\n- Limited to public information only\n- No user-specific data exposed\n- Rate limiting may apply\n\n**Integration Notes**:\n- Can be cached for performance\n- Updates when new groups are added\n- Consistent with authenticated endpoints\n- Safe for public consumption\n\n**Error Responses**:\n- `500 Internal Server Error`: Failed to retrieve group information","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve public information about available Google Groups."}},"/api/v1/news":{"get":{"description":"**Authentication**: Optional (provides role-based filtering if authenticated)\n**Purpose**: Fetch news and announcements for the QGIS plugin, web app, or API UI\n\n**Query Parameters**:\n- `platform`: Filter by platform (app, webapp, api-ui)\n- `version`: Filter by plugin version compatibility\n- `lang`: Language code for translations (e.g., 'es', 'fr', 'zh')\n- `sort`: Sort field (prefix with '-' for descending)\n- `page`: Page number for pagination (default: 1)\n- `per_page`: Items per page (1-100, default: 20)\n\n**Response Schema**:\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"uuid-string\",\n      \"title\": \"New Feature Available\",\n      \"message\": \"We've released a new analysis tool...\",\n      \"link_url\": \"https://docs.trends.earth/new-feature\",\n      \"link_text\": \"Learn more\",\n      \"created_at\": \"2025-01-15T10:30:00Z\",\n      \"publish_at\": \"2025-01-15T10:30:00Z\",\n      \"expires_at\": null,\n      \"target_platforms\": [\"app\", \"webapp\"],\n      \"target_roles\": [\"USER\", \"ADMIN\"],\n      \"min_version\": \"2.0.0\",\n      \"max_version\": null,\n      \"priority\": 10,\n      \"news_type\": \"info\"\n    }\n  ],\n  \"page\": 1,\n  \"per_page\": 20,\n  \"total\": 5\n}\n```\n\n**News Types**:\n- `info`: General information or announcements\n- `warning`: Important warnings or notices\n- `alert`: Critical alerts requiring attention\n- `update`: Software update notifications\n\n**Platform Filtering**:\n- `app`: QGIS plugin\n- `webapp`: Web application\n- `api-ui`: API admin UI\n\n**Role Filtering**:\n- News items can be targeted to specific roles (USER, ADMIN, SUPERADMIN)\n- Unauthenticated users only see news with no role restrictions\n- Authenticated users see news targeted to their role or with no restrictions\n\n**Version Filtering**:\n- News items can specify min_version and/or max_version\n- Use semantic versioning (e.g., \"2.1.0\")\n\n**Note**: Dismissal of news items is handled client-side (localStorage/QgsSettings).","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve news items for display in clients."}},"/api/v1/news/{news_id}":{"get":{"description":"**Authentication**: Not required\n**Path Parameters**:\n- `news_id`: UUID of the news item\n\n**Response**: Single news item object (see GET /news for schema)\n\n**Error Responses**:\n- `404 Not Found`: News item not found","parameters":[{"description":"Path parameter: news_id","in":"path","name":"news_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve a single news item by ID."}},"/api/v1/oauth/clients":{"get":{"description":"The ``client_secret`` is **never** returned.","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"List the caller's active (non-revoked) service clients."}},"/api/v1/oauth/clients/{client_db_id}":{"delete":{"description":"Only the owner or an admin may revoke.","parameters":[{"description":"Path parameter: client_db_id","in":"path","name":"client_db_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Revoke a service client by its database UUID."}},"/api/v1/oauth/token":{"post":{"description":"Accepts either ``application/x-www-form-urlencoded`` (standard) or\n``application/json`` request bodies.\n\nRequired parameters:\n\n* ``grant_type`` \u2013 must be ``\"client_credentials\"``\n* ``client_id``\n* ``client_secret``","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Exchange client credentials for a short-lived access token."}},"/api/v1/rate-limit/events":{"get":{"description":"Retrieve historical rate limit breach events for auditing.","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve historical rate limit breach events for auditing."}},"/api/v1/rate-limit/reset":{"post":{"description":"**Access**: Restricted to users with `role: \"SUPERADMIN\"`\n**Purpose**: Clears all current rate limit counters - useful for emergency\n  situations or testing\n\n**Request**: No request body required\n\n**Success Response Schema**:\n```json\n{\n  \"message\": \"All rate limits have been reset.\"\n}\n```\n\n**Use Cases**:\n- Emergency situations where legitimate users are being rate limited\n- Testing and development environments\n- After configuration changes to rate limiting policies\n- System maintenance and debugging\n\n**Behavior**:\n- Clears all rate limit counters from storage (Redis/Memory)\n- Affects all endpoints and all users/IP addresses\n- Does not disable rate limiting - new requests will start fresh counters\n- Operation is immediate and irreversible\n\n**Error Responses**:\n- `403 Forbidden`: User does not have SUPERADMIN privileges\n- `401 Unauthorized`: Valid JWT token required\n- `500 Internal Server Error`: Failed to reset rate limits","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Reset all rate limits across the system."}},"/api/v1/rate-limit/reset/{identifier}":{"post":{"description":"**Access**: Restricted to users with `role: \"SUPERADMIN\"`\n**Purpose**: Clears rate limit counters for a specific user or IP address\n\n**Path Parameters**:\n- `identifier`: The rate limit identifier (e.g., \"user:123\", \"ip:192.168.1.1\",\n  \"auth:hash:ip\")\n\n**Success Response Schema**:\n```json\n{\n  \"message\": \"Rate limit reset for identifier: user:123\"\n}\n```\n\n**Use Cases**:\n- Clear rate limit for specific user who was legitimately rate limited\n- Remove rate limit for specific IP address\n- Targeted rate limit management without affecting all users\n\n**Error Responses**:\n- `403 Forbidden`: User does not have SUPERADMIN privileges\n- `401 Unauthorized`: Valid JWT token required\n- `404 Not Found`: No rate limit found for the specified identifier\n- `500 Internal Server Error`: Failed to reset rate limit","parameters":[{"description":"Path parameter: identifier","in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Reset a specific rate limit by its identifier."}},"/api/v1/rate-limit/status":{"get":{"description":"**Access**: Restricted to users with `role: \"SUPERADMIN\"`\n**Purpose**: Provides visibility into current rate limiting state for monitoring\n  and debugging.\n\n**Response Schema**:\n```json\n{\n  \"message\": \"Rate limiting status retrieved successfully\",\n  \"data\": {\n    \"enabled\": true,\n    \"storage_type\": \"database\",\n    \"total_active_limits\": 1,\n    \"active_limits\": [\n      {\n        \"key\": \"user:123\",\n        \"identifier\": \"user:123\",\n        \"type\": \"user\",\n        \"occurred_at\": \"2025-11-02T10:15:30+00:00\",\n        \"expires_at\": \"2025-11-02T10:16:30+00:00\",\n        \"limit_definition\": \"5 per minute\",\n        \"limit\": 5,\n        \"current_count\": null,\n        \"time_window_seconds\": 60,\n        \"retry_after_seconds\": 60,\n        \"user_info\": {\n          \"id\": \"123\",\n          \"email\": \"user@example.com\",\n          \"name\": \"John Doe\",\n          \"role\": \"USER\"\n        }\n      }\n    ]\n  }\n}\n```\n\n**Response Fields**:\n- `enabled`: Whether rate limiting is active.\n    - `storage_type`: Backend storage type reported by the status endpoint.\n    - `total_active_limits`: Count of currently active rate limit entries.\n    - `active_limits`: Array with metadata for each active rate limit, including:\n  - `key`: Identifier used by the limiter storage (e.g., `user:<uuid>`).\n  - `identifier`: User or IP identifier associated with the limit.\n  - `type`: Limit category such as `user`, `ip`, or `auth`.\n  - `occurred_at`: Timestamp when the most recent 429 was recorded.\n  - `expires_at`: Timestamp when the limit will automatically expire.\n  - `limit_definition`: Human-readable description of the breached rule.\n        - `limit`: Parsed numeric request limit when available.\n  - `current_count`: Latest known request count (may be null).\n  - `time_window_seconds`: Time window for the limit in seconds.\n  - `retry_after_seconds`: Retry hint provided by the limiter, if available.\n        - `user_info`: Contextual user information for user-type limits\n            (null otherwise).\n\n**Error Responses**:\n- `403 Forbidden`: User does not have SUPERADMIN privileges\n- `401 Unauthorized`: Valid JWT token required\n- `500 Internal Server Error`: Failed to query rate limiting status","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Query current rate limiting status across the system."}},"/api/v1/script":{"get":{"description":"**Authentication**: JWT token required\n**Access**: Returns scripts visible to the current user based on permissions\n\n**Query Parameters**:\n- `include`: Comma-separated list of additional fields to include in response\n- `exclude`: Comma-separated list of fields to exclude from response\n- `filter`: Search/filter scripts by name, description, or other attributes\n- `sort`: Sort field (prefix with '-' for descending, e.g., '-created_at')\n- `page`: Page number for pagination (triggers pagination when provided)\n- `per_page`: Items per page (1-100, default: 20, max without pagination: 2000)\n\n**Response Schema (without pagination)**:\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"script-123\",\n      \"slug\": \"my-analysis-script\",\n      \"name\": \"Land Use Analysis\",\n      \"description\": \"Analyzes land use changes over time\",\n      \"status\": \"PUBLISHED\",\n      \"created_at\": \"2025-01-15T10:30:00Z\",\n      \"updated_at\": \"2025-01-15T10:30:00Z\",\n      \"user_id\": \"user-456\",\n      \"cpu\": 2,\n      \"memory\": 4096,\n      \"logs\": false\n    }\n  ]\n}\n```\n\n**Response Schema (with pagination)**:\n```json\n{\n  \"data\": [...],\n  \"page\": 1,\n  \"per_page\": 20,\n  \"total\": 150\n}\n```\n\n**Script Status Values**:\n- `UPLOADED`: Script uploaded but not yet published\n- `PUBLISHED`: Script is available for execution\n- `UNPUBLISHED`: Script was published but later unpublished\n- `FAILED`: Script validation or processing failed\n\n**Filtering Examples**:\n- `?filter=land` - Find scripts with \"land\" in name or description\n- `?filter=status:PUBLISHED` - Find only published scripts\n- `?filter=user:john@example.com` - Find scripts by specific user\n\n**Sorting Examples**:\n- `?sort=name` - Sort by name ascending\n- `?sort=-created_at` - Sort by creation date descending\n- `?sort=status` - Sort by status\n\n**Field Control Examples**:\n- `?include=logs` - Include execution logs\n- `?include=user` - Include user information\n- `?exclude=description,logs` - Exclude verbose fields\n\n**Pagination Control**:\n- Without pagination: Returns up to 2000 scripts in single response\n- With pagination: `?page=1&per_page=20` - Returns paginated results\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `500 Internal Server Error`: Failed to retrieve scripts","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve all scripts with flexible filtering, sorting, and pagination."}},"/api/v1/script/export":{"get":{"description":"**Authentication**: JWT token required\n**Authorization**: ADMIN or SUPERADMIN required\n\n**Query Parameters**:\n- ``date_field``: Column to filter by (``created_at`` or ``updated_at``)\n- ``date_from``: ISO 8601 start date (inclusive)\n- ``date_to``:   ISO 8601 end date   (inclusive)\n\n**Response**: ``text/csv`` attachment named ``scripts_export_<timestamp>.csv``\n\n**Error Responses**:\n- ``400`` \u2013 invalid ``date_field``\n- ``403`` \u2013 insufficient privileges","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Export scripts as a CSV file (admin only)."}},"/api/v1/script/{script_id}/access":{"delete":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin level access required (ADMIN or SUPERADMIN)\n**Purpose**: Make script accessible to all authenticated users\n\n**Path Parameters**:\n- `script_id`: Script identifier/slug or numeric ID\n\n**Request**: No request body required\n\n**Response Schema**:\n```json\n{\n  \"data\": {\n    \"script_id\": \"12345\",\n    \"restricted\": false,\n    \"allowed_roles\": [],\n    \"allowed_users\": [],\n    \"access_type\": \"unrestricted\"\n  }\n}\n```\n\n**Clearing Process**:\n- Removes all role-based restrictions\n- Removes all user-specific restrictions\n- Makes script available to all authenticated users\n- Updates script access type to \"unrestricted\"\n\n**Effect**:\n- Any authenticated user can discover and execute the script\n- Script appears in public script listings\n- No special permissions required for access\n- Maintains script visibility in search results\n\n**Use Cases**:\n- Making private scripts public\n- Removing outdated access restrictions\n- Simplifying script access management\n- Opening scripts for general use\n\n**Security Considerations**:\n- Ensure script content is appropriate for public access\n- Review script parameters for sensitive data exposure\n- Consider impact on system resources from increased usage\n- Document decision for audit purposes\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Admin privileges required\n- `404 Not Found`: Script does not exist\n- `500 Internal Server Error`: Failed to clear access restrictions","parameters":[{"description":"Path parameter: script_id","in":"path","name":"script_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Remove all access restrictions from a script."}},"/api/v1/script/{script_id}/access/roles":{"put":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin level access required (ADMIN or SUPERADMIN)\n**Purpose**: Configure which user roles can access and execute a script\n\n**Path Parameters**:\n- `script_id`: Script identifier/slug or numeric ID\n\n**Request Schema**:\n```json\n{\n  \"roles\": [\"ADMIN\", \"SUPERADMIN\"]\n}\n```\n\n**Request Fields**:\n- `roles`: Array of role names that should have access\n  (empty array removes restrictions)\n\n**Valid Roles**:\n- `USER`: Regular users\n- `ADMIN`: Administrator users\n- `SUPERADMIN`: Super administrator users\n\n**Response Schema**:\n```json\n{\n  \"data\": {\n    \"script_id\": \"12345\",\n    \"restricted\": true,\n    \"allowed_roles\": [\"ADMIN\", \"SUPERADMIN\"],\n    \"allowed_users\": [],\n    \"access_type\": \"role_restricted\"\n  }\n}\n```\n\n**Behavior**:\n- Empty roles array removes all role restrictions\n- Non-empty array restricts access to specified roles only\n- Users with specified roles can access script regardless of user-specific\n  restrictions\n- Higher privilege roles (SUPERADMIN) can access scripts restricted to\n  lower roles\n\n**Use Cases**:\n- Restrict sensitive analysis scripts to admin users only\n- Make scripts available to all authenticated users\n- Create role-based script catalogs\n- Implement script security policies\n\n**Error Responses**:\n- `400 Bad Request`: Missing 'roles' field or invalid role names\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Admin privileges required\n- `404 Not Found`: Script does not exist\n- `500 Internal Server Error`: Failed to update access controls","parameters":[{"description":"Path parameter: script_id","in":"path","name":"script_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Set role-based access control for a script."}},"/api/v1/script/{script_id}/access/roles/{role}":{"delete":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin level access required (ADMIN or SUPERADMIN)\n**Purpose**: Revoke script access from all users with a specific role\n\n**Path Parameters**:\n- `script_id`: Script identifier/slug or numeric ID\n- `role`: Role name to remove (USER, ADMIN, or SUPERADMIN)\n\n**Request**: No request body required\n\n**Response Schema**:\n```json\n{\n  \"data\": {\n    \"script_id\": \"12345\",\n    \"restricted\": true,\n    \"allowed_roles\": [\"ADMIN\"],\n    \"allowed_users\": [],\n    \"access_type\": \"role_restricted\"\n  }\n}\n```\n\n**Behavior**:\n- Removes role from script's allowed roles list\n- All users with only this role lose access immediately\n- Does not affect other roles' access\n- Gracefully handles removal of non-existent roles\n\n**Access Effect**:\n- Users with this role can no longer see script in listings\n- Users with this role cannot execute script\n- Existing executions continue to completion\n- Users may retain access through user-specific permissions or other roles\n\n**Cleanup Logic**:\n- If role list becomes empty, role restrictions may be removed\n- Script may become unrestricted if no other access controls exist\n- Access type is recalculated based on remaining restrictions\n\n**Use Cases**:\n- Remove access when role permissions change\n- Tighten security by restricting to higher privilege roles only\n- Clean up outdated role-based permissions\n- Implement policy changes across scripts\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Admin privileges required\n- `404 Not Found`: Script does not exist\n- `500 Internal Server Error`: Failed to remove role access","parameters":[{"description":"Path parameter: script_id","in":"path","name":"script_id","required":true,"schema":{"type":"string"}},{"description":"Path parameter: role","in":"path","name":"role","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Remove a specific role from script access control list."}},"/api/v1/script/{script_id}/access/users":{"put":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin level access required (ADMIN or SUPERADMIN)\n**Purpose**: Configure which specific users can access and execute a script\n\n**Path Parameters**:\n- `script_id`: Script identifier/slug or numeric ID\n\n**Request Schema**:\n```json\n{\n  \"users\": [\"user123\", \"user456\"]\n}\n```\n\n**Request Fields**:\n- `users`: Array of user IDs that should have access\n  (empty array removes restrictions)\n\n**User Validation**:\n- All provided user IDs must exist in the system\n- User IDs can be numeric IDs or email addresses\n- Invalid user IDs will cause the request to fail\n\n**Response Schema**:\n```json\n{\n  \"data\": {\n    \"script_id\": \"12345\",\n    \"restricted\": true,\n    \"allowed_roles\": [],\n    \"allowed_users\": [\"user123\", \"user456\"],\n    \"access_type\": \"user_restricted\"\n  }\n}\n```\n\n**Behavior**:\n- Empty users array removes all user-specific restrictions\n- Non-empty array restricts access to specified users only\n- User restrictions work independently of role restrictions\n- Users in the allowed list can access script regardless of their role\n\n**Access Logic**:\n- If both role and user restrictions exist: user must match either criteria\n- User-specific access overrides role-based restrictions\n- Admins can always access scripts regardless of restrictions\n\n**Use Cases**:\n- Grant script access to specific researchers or partners\n- Create private scripts for limited user groups\n- Implement per-project script access controls\n- Beta testing with selected users\n\n**Error Responses**:\n- `400 Bad Request`: Missing 'users' field, invalid user IDs, or user not found\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Admin privileges required\n- `404 Not Found`: Script does not exist\n- `500 Internal Server Error`: Failed to update access controls","parameters":[{"description":"Path parameter: script_id","in":"path","name":"script_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Set user-specific access control for a script."}},"/api/v1/script/{script_id}/access/users/{user_id}":{"delete":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin level access required (ADMIN or SUPERADMIN)\n**Purpose**: Revoke script access from an individual user\n\n**Path Parameters**:\n- `script_id`: Script identifier/slug or numeric ID\n- `user_id`: User identifier (numeric ID or email address)\n\n**Request**: No request body required\n\n**Response Schema**:\n```json\n{\n  \"data\": {\n    \"script_id\": \"12345\",\n    \"restricted\": true,\n    \"allowed_roles\": [],\n    \"allowed_users\": [\"user123\"],\n    \"access_type\": \"user_restricted\"\n  }\n}\n```\n\n**Behavior**:\n- Removes user from script's allowed users list\n- User immediately loses access to script\n- Does not affect other users' access\n- Gracefully handles removal of non-existent users\n\n**Access Effect**:\n- User can no longer see script in listings\n- User cannot execute script\n- Existing executions continue to completion\n- User may regain access through role-based permissions\n\n**Cleanup Logic**:\n- If user list becomes empty, user restrictions may be removed\n- Script may become unrestricted if no other access controls exist\n- Access type is recalculated based on remaining restrictions\n\n**Use Cases**:\n- Remove access when user leaves project\n- Revoke access due to security concerns\n- Clean up outdated user permissions\n- Manage temporary access grants\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Admin privileges required\n- `404 Not Found`: Script does not exist\n- `500 Internal Server Error`: Failed to remove user access","parameters":[{"description":"Path parameter: script_id","in":"path","name":"script_id","required":true,"schema":{"type":"string"}},{"description":"Path parameter: user_id","in":"path","name":"user_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Remove a specific user from script access control list."}},"/api/v1/script/{script}":{"delete":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin access required (ADMIN or SUPERADMIN)\n**Warning**: This action is irreversible and deletes all associated data\n\n**Path Parameters**:\n- `script`: Script identifier/slug or numeric ID to delete\n\n**Deletion Process**:\n- Cancels any running executions using this script\n- Deletes all execution history and logs for this script\n- Removes script files and dependencies from storage\n- Deletes script metadata and configuration\n- Removes access control settings\n- Cleans up any associated resources\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"id\": \"script-123\",\n    \"slug\": \"vegetation-analysis\",\n    \"name\": \"Vegetation Change Analysis\",\n    \"description\": \"Analyzes vegetation changes using satellite imagery\",\n    \"status\": \"DELETED\",\n    \"created_at\": \"2025-01-15T10:30:00Z\",\n    \"updated_at\": \"2025-01-15T12:00:00Z\",\n    \"user_id\": \"user-456\",\n    \"cpu\": 2,\n    \"memory\": 4096,\n    \"logs\": false\n  }\n}\n```\n\n**Impact**:\n- All executions using this script will be cancelled\n- Historical execution data is permanently lost\n- Script cannot be recovered after deletion\n- Users lose access to download script files\n\n**Security Considerations**:\n- Only admin users can delete scripts\n- Action is logged for audit purposes\n- Confirmation should be required in client applications\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Admin access required\n- `404 Not Found`: Script does not exist\n- `500 Internal Server Error`: Deletion failed","parameters":[{"description":"Path parameter: script","in":"path","name":"script","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Permanently delete a script and all associated data."}},"/api/v1/script/{script}/config":{"patch":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin or script owner access required\n**Content-Type**: application/json\n\n**Path Parameters**:\n- `script`: Script identifier/slug or UUID\n\n**Request Body** (all fields optional):\n```json\n{\n  \"name\": \"New Script Name\",\n  \"description\": \"Updated description\",\n  \"uses_gee\": true\n}\n```\n\n**Updatable Fields**:\n- `name`: Script display name\n- `description`: Script description text\n- `uses_gee`: Whether the script uses Google Earth Engine (boolean)\n\n**Error Responses**:\n- `400 Bad Request`: No valid fields provided\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Insufficient permissions\n- `404 Not Found`: Script does not exist","parameters":[{"description":"Path parameter: script","in":"path","name":"script","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Update script metadata without uploading a new script archive."}},"/api/v1/script/{script}/download":{"get":{"description":"**Authentication**: JWT token required\n**Access**: Script must be accessible to current user based on permissions\n**Format**: Returns compressed tar.gz archive containing script files\n\n**Path Parameters**:\n- `script`: Script identifier/slug or numeric ID\n\n**Response**: Binary file download (tar.gz archive)\n- **Content-Type**: `application/gzip`\n- **Content-Disposition**: `attachment; filename=script-name.tar.gz`\n\n**Archive Contents**:\n- Main script file(s) (Python, R, or other supported languages)\n- Configuration files (script metadata, requirements)\n- Dependencies and libraries (if included)\n- Documentation and README files\n- Supporting data files (if any)\n\n**Usage Examples**:\n```bash\n# Download script archive\ncurl -H \"Authorization: Bearer your_jwt_token\"          -o vegetation-analysis.tar.gz          \"https://api.trends.earth/api/v1/script/vegetation-analysis/download\"\n\n# Extract downloaded archive\ntar -xzf vegetation-analysis.tar.gz\n```\n\n**Access Control**:\n- Users can download scripts they have execution access to\n- Admin users can download any script\n- Private scripts require explicit permission\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: No access to download this script\n- `404 Not Found`: Script does not exist\n- `500 Internal Server Error`: Download failed or file unavailable","parameters":[{"description":"Path parameter: script","in":"path","name":"script","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Download a script's source code and files as a compressed archive."}},"/api/v1/script/{script}/log":{"get":{"description":"**Authentication**: JWT token required\n**Access**: Script must be accessible to current user\n**Purpose**: View script upload, validation, and publishing logs for debugging\n\n**Path Parameters**:\n- `script`: Script identifier/slug or numeric ID\n\n**Query Parameters**:\n- `start`: Start timestamp for log filtering (ISO 8601 format)\n  - Example: `?start=2025-01-15T10:30:00Z`\n- `last-id`: Last log ID for pagination/incremental updates\n  - Example: `?last-id=log-456`\n\n**Response Schema**:\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"log-123\",\n      \"script_id\": \"script-456\",\n      \"timestamp\": \"2025-01-15T10:30:00Z\",\n      \"level\": \"INFO\",\n      \"message\": \"Script validation started\",\n      \"details\": {\n        \"stage\": \"validation\",\n        \"file_count\": 5,\n        \"total_size\": \"2.3MB\"\n      }\n    },\n    {\n      \"id\": \"log-124\",\n      \"script_id\": \"script-456\",\n      \"timestamp\": \"2025-01-15T10:30:15Z\",\n      \"level\": \"SUCCESS\",\n      \"message\": \"Script validation completed successfully\",\n      \"details\": {\n        \"stage\": \"validation\",\n        \"duration\": \"15s\"\n      }\n    }\n  ]\n}\n```\n\n**Log Levels**:\n- `DEBUG`: Detailed diagnostic information\n- `INFO`: General information about script processing\n- `WARNING`: Warning messages about potential issues\n- `ERROR`: Error messages about failures\n- `SUCCESS`: Successful completion of operations\n\n**Log Types**:\n- Upload processing logs\n- Script validation and syntax checking\n- Dependency resolution logs\n- Publishing process logs\n- Configuration validation results\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: No access to view script logs\n- `404 Not Found`: Script does not exist\n- `500 Internal Server Error`: Failed to retrieve logs","parameters":[{"description":"Path parameter: script","in":"path","name":"script","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve processing and validation logs for a specific script."}},"/api/v1/script/{script}/publish":{"post":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin or script owner access required\n**Purpose**: Makes an uploaded script available for execution by authorized users\n\n**Path Parameters**:\n- `script`: Script identifier/slug or numeric ID\n\n**Request**: No request body required\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"id\": \"script-123\",\n    \"slug\": \"vegetation-analysis\",\n    \"name\": \"Vegetation Change Analysis\",\n    \"description\": \"Analyzes vegetation changes using satellite imagery\",\n    \"status\": \"PUBLISHED\",\n    \"created_at\": \"2025-01-15T10:30:00Z\",\n    \"updated_at\": \"2025-01-15T10:35:00Z\",\n    \"user_id\": \"user-456\",\n    \"cpu\": 2,\n    \"memory\": 4096,\n    \"logs\": false\n  }\n}\n```\n\n**Publishing Process**:\n- Validates script configuration and dependencies\n- Updates script status from UPLOADED to PUBLISHED\n- Makes script available in public script listings\n- Enables script execution for authorized users\n\n**Prerequisites**:\n- Script must be in UPLOADED status\n- Script must have valid configuration\n- User must have publishing permissions (admin or script owner)\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Insufficient permissions to publish script\n- `404 Not Found`: Script does not exist\n- `400 Bad Request`: Script not in valid state for publishing\n- `500 Internal Server Error`: Publishing failed","parameters":[{"description":"Path parameter: script","in":"path","name":"script","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Publish a script to make it available for execution."}},"/api/v1/script/{script}/run":{"post":{"description":"**Rate Limited**: Subject to execution limits (configurable per minute/hour)\n**Authentication**: JWT token required\n**Access**: Script must be published and user must have execution permissions\n\n**Request Schema**:\n```json\n{\n  \"param1\": \"value1\",\n  \"param2\": 123,\n  \"param3\": true,\n  \"nested_param\": {\n    \"sub_param\": \"nested_value\"\n  }\n}\n```\n\n**Path Parameters**:\n- `script`: Script identifier/name to execute\n\n**Request Body**: JSON object containing script parameters\n- Parameters vary by script - see individual script documentation\n- Can include nested objects and arrays\n- Boolean, string, and numeric values supported\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"id\": \"exec_123456\",\n    \"script_id\": \"my-script\",\n    \"status\": \"PENDING\",\n    \"params\": {\n      \"param1\": \"value1\",\n      \"param2\": 123\n    },\n    \"user_id\": \"user_789\",\n    \"created_at\": \"2025-01-15T10:30:00Z\",\n    \"updated_at\": \"2025-01-15T10:30:00Z\",\n    \"start_time\": null,\n    \"end_time\": null,\n    \"results\": null\n  }\n}\n```\n\n**Execution States**:\n- `PENDING`: Execution queued, waiting to start\n- `RUNNING`: Currently executing\n- `SUCCESS`: Completed successfully\n- `FAILED`: Execution failed with error\n- `CANCELLED`: Execution was cancelled\n\n**Error Responses**:\n- `400 Bad Request`: Invalid parameters or script not in valid state\n- `401 Unauthorized`: JWT token required\n- `404 Not Found`: Script does not exist\n- `429 Too Many Requests`: Execution rate limit exceeded\n- `500 Internal Server Error`: Execution creation failed","parameters":[{"description":"Path parameter: script","in":"path","name":"script","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Execute a script with provided parameters."}},"/api/v1/script/{script}/unpublish":{"post":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin or script owner access required\n**Purpose**: Removes a published script from public availability while\n  preserving data\n\n**Path Parameters**:\n- `script`: Script identifier/slug or numeric ID\n\n**Request**: No request body required\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"id\": \"script-123\",\n    \"slug\": \"vegetation-analysis\",\n    \"name\": \"Vegetation Change Analysis\",\n    \"description\": \"Analyzes vegetation changes using satellite imagery\",\n    \"status\": \"UNPUBLISHED\",\n    \"created_at\": \"2025-01-15T10:30:00Z\",\n    \"updated_at\": \"2025-01-15T10:35:00Z\",\n    \"user_id\": \"user-456\",\n    \"cpu\": 2,\n    \"memory\": 4096,\n    \"logs\": false\n  }\n}\n```\n\n**Unpublishing Effects**:\n- Updates script status from PUBLISHED to UNPUBLISHED\n- Removes script from public script listings\n- Prevents new executions from being started\n- Existing running executions continue to completion\n- Script data and configuration are preserved\n\n**Use Cases**:\n- Temporarily disable a script for maintenance\n- Remove deprecated or problematic scripts\n- Control script availability during updates\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Insufficient permissions to unpublish script\n- `404 Not Found`: Script does not exist\n- `400 Bad Request`: Script not in valid state for unpublishing\n- `500 Internal Server Error`: Unpublishing failed","parameters":[{"description":"Path parameter: script","in":"path","name":"script","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Unpublish a script to make it unavailable for new executions."}},"/api/v1/stats/cache":{"delete":{"description":"Query Parameters:\n- pattern: Optional pattern to match cache keys (e.g., 'summary', 'trends')\n\nAllowed patterns: summary, trends, geographic, tasks, executions, users, activity\n\nExample Response (success):\n{\n    \"data\": {\n        \"success\": true,\n        \"message\": \"Cache cleared successfully\"\n    }\n}\n\nExample Response (with pattern):\n{\n    \"data\": {\n        \"success\": true,\n        \"message\": \"Cache cleared successfully for pattern: summary\"\n    }\n}\n\nReturns:\n    JSON response indicating success or failure of cache clearing operation","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Clear the stats cache."}},"/api/v1/stats/dashboard":{"get":{"description":"Only SUPERADMIN users can access dashboard statistics.\n\nQuery Parameters:\n- period: Time period filter (last_day, last_week, last_month, last_year, all)\n- include: Comma-separated list of sections (summary, trends, geographic, tasks)\n\nExample Response for include=summary (period=all):\n{\n    \"data\": {\n        \"summary\": {\n            \"total_executions\": 1500,\n            \"total_jobs\": 1500,\n            \"total_users\": 250,\n            \"total_scripts\": 150,\n            \"total_executions_finished\": 1200,\n            \"total_executions_failed\": 200,\n            \"total_executions_cancelled\": 100\n        }\n    }\n}\n\nExample Response for include=summary (period=last_month):\n{\n    \"data\": {\n        \"summary\": {\n            \"total_executions\": 450,\n            \"total_jobs\": 450,\n            \"total_users\": 80,\n            \"total_scripts\": 150,\n            \"total_executions_finished\": 360,\n            \"total_executions_failed\": 60,\n            \"total_executions_cancelled\": 30\n        }\n    }\n}\n\nExample Response for include=trends:\n{\n    \"data\": {\n        \"trends\": {\n            \"hourly_jobs\": [\n                {\"hour\": \"2025-08-08T10:00:00Z\", \"count\": 5},\n                {\"hour\": \"2025-08-08T11:00:00Z\", \"count\": 8}\n            ],\n            \"daily_jobs\": [\n                {\"date\": \"2025-08-07\", \"count\": 45},\n                {\"date\": \"2025-08-08\", \"count\": 52}\n            ],\n            \"monthly_jobs\": [\n                {\"month\": \"2025-07\", \"count\": 1200},\n                {\"month\": \"2025-08\", \"count\": 300}\n            ]\n        }\n    }\n}\n\nExample Response for include=geographic:\n{\n    \"data\": {\n        \"geographic\": {\n            \"countries\": {\n                \"USA\": 100,\n                \"Brazil\": 75,\n                \"Germany\": 50\n            },\n            \"total_users\": 225\n        }\n    }\n}\n\nExample Response for include=tasks:\n{\n    \"data\": {\n        \"tasks\": {\n            \"by_type\": [\n                {\"task\": \"productivity\", \"count\": 500, \"success_rate\": 95.0},\n                {\"task\": \"land-cover\", \"count\": 300, \"success_rate\": 92.0},\n                {\"task\": \"carbon\", \"count\": 200, \"success_rate\": 89.0}\n            ],\n            \"by_version\": [\n                {\"version\": \"2\", \"count\": 600, \"percentage\": 60.0},\n                {\"version\": \"1\", \"count\": 400, \"percentage\": 40.0}\n            ]\n        }\n    }\n}\n\nExample Response for include=summary,trends,geographic,tasks (all sections):\n{\n    \"data\": {\n        \"summary\": { ... },\n        \"trends\": { ... },\n        \"geographic\": { ... },\n        \"tasks\": { ... }\n    }\n}\n\nReturns:\n    JSON response with dashboard statistics containing requested sections","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Get comprehensive dashboard statistics."}},"/api/v1/stats/executions":{"get":{"description":"Only SUPERADMIN users can access execution statistics.\n\nQuery Parameters:\n- period: Time period filter (last_day, last_week, last_month, last_year, all)\n- group_by: Grouping interval (quarter_hour, hour, day, week, month)\n- task_type: Filter by specific task type\n- status: Filter by execution status (PENDING, RUNNING, FINISHED, FAILED, CANCELLED)\n\nExample Response:\n{\n    \"data\": {\n        \"time_series\": [\n            {\n                \"timestamp\": \"2025-08-08T10:00:00Z\",\n                \"total\": 10,\n                \"by_status\": {\"FINISHED\": 8, \"FAILED\": 2},\n                \"by_task\": {\"productivity\": 6, \"land-cover\": 4}\n            }\n        ],\n        \"top_users\": [\n            {\n                \"user_id\": \"123\",\n                \"email\": \"user1@example.com\",\n                \"execution_count\": 45,\n                \"success_rate\": 94.4,\n                \"favorite_tasks\": [\"productivity\", \"land-cover\"]\n            }\n        ],\n        \"task_performance\": [\n            {\n                \"task\": \"productivity\",\n                \"total_executions\": 500,\n                \"success_rate\": 95.0,\n                \"avg_duration_minutes\": 45.2,\n                \"failure_reasons\": []\n            }\n        ]\n    }\n}\n\nReturns:\n    JSON response with execution statistics","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Get execution statistics and trends."}},"/api/v1/stats/health":{"get":{"description":"Returns basic counts to verify the stats service is working.\n\nExample Response:\n{\n    \"data\": {\n        \"status\": \"healthy\",\n        \"basic_counts\": {\n            \"total_jobs\": 1500,\n            \"total_users\": 250,\n            \"jobs_last_month\": 450\n        }\n    }\n}\n\nReturns:\n    JSON response with health status and basic counts","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Get basic health check for stats endpoints."}},"/api/v1/stats/users":{"get":{"description":"Only SUPERADMIN users can access user statistics.\n\nQuery Parameters:\n- period: Time period filter (last_day, last_week, last_month, last_year, all)\n- group_by: Grouping interval (quarter_hour, hour, day, week, month)\n- country: Filter by specific country\n\nExample Response:\n{\n    \"data\": {\n        \"registration_trends\": [\n            {\"date\": \"2025-08-07\", \"new_users\": 12, \"total_users\": 250},\n            {\"date\": \"2025-08-08\", \"new_users\": 8, \"total_users\": 258}\n        ],\n        \"geographic_distribution\": [\n            {\"country\": \"USA\", \"user_count\": 100, \"percentage\": 40.0},\n            {\"country\": \"Brazil\", \"user_count\": 75, \"percentage\": 30.0},\n            {\"country\": \"Germany\", \"user_count\": 50, \"percentage\": 20.0}\n        ],\n        \"activity_stats\": {\n            \"active_users_in_period\": 234,\n            \"active_users_countries\": 45,\n            \"never_executed\": 123\n        }\n    }\n}\n\nReturns:\n    JSON response with user statistics","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Get user statistics and geographical distribution."}},"/api/v1/status":{"get":{"description":"**Authentication**: JWT token required\n**Access**: Restricted to ADMIN and SUPERADMIN users only\n**Purpose**: Monitor system health, track events, and diagnose issues\n\n**Query Parameters**:\n- `start_date`: Filter logs from this date onwards (ISO 8601 format)\n- `end_date`: Filter logs up to this date (ISO 8601 format)\n- `sort`: Sort field (prefix with '-' for descending, e.g., '-timestamp')\n- `page`: Page number for pagination (default: 1)\n- `per_page`: Items per page (1-10000, default: 100)\n\n**Response Schema**:\n```json\n{\n  \"data\": [\n    {\n      \"id\": 123,\n      \"timestamp\": \"2025-01-15T10:30:00Z\",\n      \"executions_pending\": 2,\n      \"executions_ready\": 2,\n      \"executions_running\": 3,\n      \"executions_finished\": 8,\n      \"executions_failed\": 1,\n      \"executions_cancelled\": 0\n    },\n    {\n      \"id\": 124,\n      \"timestamp\": \"2025-01-15T10:35:00Z\",\n      \"executions_pending\": 3,\n      \"executions_ready\": 5,\n      \"executions_running\": 3,\n      \"executions_finished\": 12,\n      \"executions_failed\": 2,\n      \"executions_cancelled\": 1\n    }\n  ],\n  \"page\": 1,\n  \"per_page\": 100,\n  \"total\": 1547\n}\n```\n\n**Status Log Fields**:\n- `id`: Unique identifier for the status log entry\n- `timestamp`: When the status was recorded (ISO 8601 format)\n- `executions_pending`: Number of executions queued to start (PENDING state)\n- `executions_ready`: Number of executions in READY state\n- `executions_running`: Number of currently running executions\n- `executions_finished`: Number of executions that finished\n- `executions_failed`: Number of executions that failed\n- `executions_cancelled`: Number of executions that were cancelled\n\n**Monitoring Metrics**:\n- Track execution queue length and processing status\n- Monitor execution completion and failure rates\n- Identify trends in script execution success/failure rates\n- System health indicators for capacity planning\n- Event-driven status tracking provides real-time execution state\n\n**Date Filtering Examples**:\n- `?start_date=2025-01-15T00:00:00Z` - Logs from January 15th onwards\n- `?end_date=2025-01-15T23:59:59Z` - Logs up to end of January 15th\n- `?start_date=2025-01-10T00:00:00Z&end_date=2025-01-15T23:59:59Z` - Logs\n  within date range\n\n**Sorting Examples**:\n- `?sort=timestamp` - Chronological order (oldest first)\n- `?sort=-timestamp` - Reverse chronological (newest first, default)\n- `?sort=level` - Sort by severity level\n\n**Pagination Examples**:\n- `?page=1&per_page=50` - First 50 entries\n- `?page=2&per_page=100` - Next 100 entries\n- Default pagination: 100 items per page\n\n**Use Cases**:\n- Monitor execution queue length and processing capacity\n- Track system growth (users and scripts over time)\n- Analyze execution success rates and failure patterns\n- Capacity planning based on execution activity trends\n- Performance monitoring and bottleneck identification\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Insufficient privileges (ADMIN+ required)\n- `500 Internal Server Error`: Failed to retrieve status logs","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve system status logs for monitoring and diagnostics."}},"/api/v1/status/cluster":{"get":{"description":"Returns the same data as /status/swarm. This endpoint provides a\nbackend-agnostic name that will work for both Docker Swarm and\nfuture Kubernetes deployments.\n\n**Authentication**: JWT token required\n**Access**: ADMIN and SUPERADMIN only","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Get cluster status (orchestrator-agnostic alias for /status/swarm)."}},"/api/v1/status/swarm":{"get":{"description":"**Authentication**: JWT token required\n**Access**: Restricted to ADMIN and SUPERADMIN users only\n**Purpose**: Monitor Docker Swarm health, node resources, and capacity\n**Performance**: Uses Redis-cached data updated every 2 minutes for fast response\n\n**Response Schema**:\n```json\n{\n  \"data\": {\n    \"swarm_active\": true,\n    \"total_nodes\": 3,\n    \"total_managers\": 1,\n    \"total_workers\": 2,\n    \"error\": null,\n    \"cache_info\": {\n      \"cached_at\": \"2025-01-15T10:30:00Z\",\n      \"cache_ttl\": 300,\n      \"cache_key\": \"docker_swarm_status\",\n      \"source\": \"cached\"\n    },\n    \"nodes\": [\n      {\n        \"id\": \"node-id-123\",\n        \"hostname\": \"manager-01\",\n        \"role\": \"manager\",\n        \"is_manager\": true,\n        \"is_leader\": true,\n        \"availability\": \"active\",\n        \"state\": \"ready\",\n        \"cpu_count\": 4.0,\n        \"memory_gb\": 8.0,\n        \"running_tasks\": 3,\n        \"available_capacity\": 37,\n        \"resource_usage\": {\n          \"used_cpu_nanos\": 300000000,\n          \"used_memory_bytes\": 536870912,\n          \"available_cpu_nanos\": 3700000000,\n          \"available_memory_bytes\": 7548381184,\n          \"used_cpu_percent\": 7.5,\n          \"used_memory_percent\": 6.25\n        },\n        \"labels\": {\"node.role\": \"manager\"},\n        \"created_at\": \"2025-01-15T10:00:00Z\",\n        \"updated_at\": \"2025-01-15T10:30:00Z\"\n      }\n    ]\n  }\n}\n```\n\n**Data Source**:\n- Uses cached Docker Swarm data from Redis (updated every 2 minutes)\n- Resource calculations based on actual Docker Swarm task reservations\n- Node capacity calculated from CPU/memory resources and current task load\n\n**Error Responses**:\n- 403: Access denied (non-admin user)\n- 500: Server error\n\n**Note**: When Docker is not in swarm mode or unavailable, returns:\n```json\n{\n  \"data\": {\n    \"swarm_active\": false,\n    \"error\": \"Not in swarm mode\" | \"Docker unavailable\",\n    \"nodes\": [],\n    \"total_nodes\": 0,\n    \"total_managers\": 0,\n    \"total_workers\": 0,\n    \"cache_info\": {\n      \"cached_at\": \"2025-01-15T10:30:00Z\",\n      \"cache_ttl\": 0,\n      \"cache_key\": \"docker_swarm_status\",\n      \"source\": \"real_time_fallback\" | \"endpoint_error_fallback\"\n    }\n  }\n}\n```","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Get cached Docker Swarm cluster status including comprehensive node information."}},"/api/v1/unsubscribe":{"patch":{"description":"Query params:\n    token: signed JWT unsubscribe token (required)\n\nBody (JSON, all fields optional):\n    {\"news\": bool, \"engagement\": bool, \"system_updates\": bool}","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Update the email subscription preferences for a tokenised user."}},"/api/v1/user":{"get":{"description":"**Authentication**: JWT token required\n**Access**: Restricted to ADMIN and SUPERADMIN users only\n\n**Query Parameters**:\n- `include`: Comma-separated list of additional fields to include\n- `exclude`: Comma-separated list of fields to exclude from response\n- `filter`: Filter users by email, name, role, or other attributes\n- `sort`: Sort field (prefix with '-' for descending, e.g., '-created_at')\n- `page`: Page number for pagination (triggers pagination when provided)\n- `per_page`: Items per page (1-100, default: 20)\n\n**Response Schema (without pagination)**:\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"123\",\n      \"email\": \"user@example.com\",\n      \"name\": \"John Doe\",\n      \"role\": \"USER\",\n      \"country\": \"US\",\n      \"institution\": \"Example Organization\",\n      \"created_at\": \"2025-01-15T10:30:00Z\",\n      \"updated_at\": \"2025-01-15T10:30:00Z\",\n      \"last_login_at\": \"2025-01-15T09:30:00Z\",\n      \"last_activity_at\": \"2025-01-15T11:30:00Z\",\n      \"email_verified\": true,\n      \"email_verified_at\": \"2025-01-15T10:30:00Z\"\n    }\n  ]\n}\n```\n\n**Response Schema (with pagination)**:\n```json\n{\n  \"data\": [...],\n  \"page\": 1,\n  \"per_page\": 20,\n  \"total\": 150\n}\n```\n\n**Filtering Examples**:\n- `?filter=role=ADMIN` - Find users with ADMIN role\n- `?filter=country=US` - Find users from United States\n- `?filter=email_verified=true` - Find users who verified their email\n- `?filter=last_activity_at>2025-01-01` - Find users active after date\n\n**Sorting Examples**:\n- `?sort=name` - Sort by name ascending\n- `?sort=-created_at` - Sort by creation date descending\n- `?sort=last_activity_at desc` - Sort by last activity descending\n- `?sort=-email_verified` - Sort unverified users first\n\n**Field Control Examples**:\n- `?include=password_last_changed` - Include additional fields\n- `?exclude=institution,country` - Exclude specified fields\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Insufficient privileges (ADMIN+ required)\n- `500 Internal Server Error`: Failed to retrieve users","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Retrieve list of users with filtering, sorting, and pagination."}},"/api/v1/user/export":{"get":{"description":"**Authentication**: JWT token required\n**Authorization**: ADMIN or SUPERADMIN required\n\n**Query Parameters**:\n- ``date_field``: Column to filter by (``created_at``, ``updated_at``,\n  ``email_verified_at``, ``last_login_at``, ``last_activity_at``)\n- ``date_from``: ISO 8601 start date (inclusive)\n- ``date_to``:   ISO 8601 end date   (inclusive)\n\n**Response**: ``text/csv`` attachment named ``users_export.csv``\n\n**Error Responses**:\n- ``400`` \u2013 invalid ``date_field``\n- ``403`` \u2013 insufficient privileges","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Export users as a CSV file (admin only)."}},"/api/v1/user/me":{"delete":{"description":"**Authentication**: JWT token required\n**Warning**: This action is irreversible and deletes all user data\n**Scope**: Deletes current authenticated user's account\n\n**Request**: No request body required\n\n**Deletion Process**:\n- Cancels all running executions for this user\n- Deletes all execution history and logs\n- Removes user-created scripts (if user is owner)\n- Deletes user profile and authentication data\n- Revokes all active sessions and tokens\n- Removes user from any Google Groups (if enabled)\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"id\": \"user-123\",\n    \"email\": \"user@example.com\",\n    \"name\": \"John Doe\",\n    \"role\": \"USER\",\n    \"created_at\": \"2025-01-15T10:30:00Z\",\n    \"updated_at\": \"2025-01-15T13:00:00Z\",\n    \"deleted_at\": \"2025-01-15T13:00:00Z\",\n    \"status\": \"DELETED\"\n  }\n}\n```\n\n**Data Cleanup**:\n- User profile information permanently deleted\n- Execution history and logs removed\n- Script ownership transferred or scripts deleted\n- Session tokens invalidated immediately\n- Email address becomes available for re-registration\n\n**Important Notes**:\n- Action cannot be undone - all data is permanently lost\n- User will be immediately logged out from all devices\n- Any shared scripts may become inaccessible to other users\n- Admin users cannot delete their own accounts via this endpoint\n\n**GDPR Compliance**:\n- Implements \"right to erasure\" requirements\n- Removes all personal data from system\n- Maintains minimal audit log for compliance (anonymized)\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Cannot delete admin accounts via self-service\n- `500 Internal Server Error`: Account deletion failed","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Delete current user's account and all associated data."}},"/api/v1/user/me/change-password":{"patch":{"description":"**Authentication**: JWT token required\n**Access**: Secure password change requiring current password verification\n**Security**: Validates old password before allowing change\n\n**Request Schema**:\n```json\n{\n  \"old_password\": \"currentPassword123\",\n  \"new_password\": \"newSecurePassword456\"\n}\n```\n\n**Request Fields**:\n- `old_password`: Current password for verification (required)\n- `new_password`: New password to set (required)\n\n**Response Schema**:\n```json\n{\n  \"data\": {\n    \"id\": \"user-123\",\n    \"email\": \"user@example.com\",\n    \"name\": \"John Doe\",\n    \"role\": \"USER\",\n    \"created_at\": \"2025-01-15T10:30:00Z\",\n    \"updated_at\": \"2025-01-15T12:30:00Z\",\n    \"password_last_changed\": \"2025-01-15T12:30:00Z\"\n  }\n}\n```\n\n**Security Features**:\n- Verifies current password before allowing change\n- Enforces password strength requirements\n- Updates password change timestamp\n- Invalidates existing sessions (optional security measure)\n- Logs password change event for audit\n\n**Password Requirements**:\n- Minimum length (typically 8+ characters)\n- Must include mix of letters, numbers, and special characters\n- Cannot be same as current password\n- Cannot be common or easily guessable passwords\n\n**Use Cases**:\n- Regular password rotation for security\n- Password change after suspected compromise\n- Compliance with security policies\n- User-initiated security enhancement\n\n**Error Responses**:\n- `400 Bad Request`: Missing required fields\n- `401 Unauthorized`: JWT token required or incorrect old password\n- `422 Unprocessable Entity`: New password doesn't meet requirements\n- `500 Internal Server Error`: Password change failed","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Change current user's password with old password verification."}},"/api/v1/user/me/clients":{"get":{"description":"**Authentication**: JWT token required\n**Purpose**: Shows which client platforms the user has accessed the API from\n\n**Response Schema**:\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"uuid\",\n      \"client_type\": \"qgis_plugin\",\n      \"client_version\": \"2.2.4\",\n      \"os\": \"Windows\",\n      \"qgis_version\": \"3.34.0\",\n      \"last_seen_at\": \"2026-03-18T12:00:00Z\",\n      \"created_at\": \"2026-01-15T10:30:00Z\"\n    }\n  ]\n}\n```\n\n**Client Types**:\n- `qgis_plugin`: Trends.Earth QGIS plugin\n- `api_ui`: Web admin interface\n- `cli`: Command-line interface\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `500 Internal Server Error`: Failed to retrieve clients","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Get current user's client platform metadata."}},"/api/v1/user/me/gee-credentials":{"delete":{"description":"**Authentication**: JWT token required\n**Authorization**: Any authenticated user\n\n**Response Schema**:\n```json\n{\n  \"message\": \"GEE credentials deleted successfully\"\n}\n```\n\n**What Gets Deleted**:\n- OAuth access and refresh tokens (if using OAuth)\n- Service account key (if using service account)\n- Credentials type and creation timestamp\n- All encrypted credential data is permanently removed\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required or invalid\n- `404 Not Found`: User not found or no GEE credentials exist\n- `500 Internal Server Error`: Failed to delete credentials","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Delete current user's Google Earth Engine credentials."}},"/api/v1/user/me/gee-credentials/project":{"patch":{"description":"The project ID must correspond to a GCP project that has the Earth Engine\nAPI enabled and that the user has access to.\n\n**Request body** (JSON):\n- ``cloud_project`` (str, required): GCP project ID (e.g. ``my-gee-project``)\n\n**Response** adds ``gcs_write_access`` (bool) to indicate whether the IAM\ngrant succeeded. When ``false`` a ``detail`` field explains why.","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Save the user's selected GEE Cloud Project ID and grant bucket write access."}},"/api/v1/user/me/gee-credentials/test":{"post":{"description":"**Authentication**: JWT token required\n**Authorization**: Any authenticated user\n\n**Prerequisites**:\n- User must have GEE credentials configured (OAuth or service account)\n\n**Response Schema (Success)**:\n```json\n{\n  \"message\": \"GEE credentials are valid and working\"\n}\n```\n\n**What This Tests**:\n- Initializes Google Earth Engine with user's credentials\n- Verifies credentials are not expired\n- Confirms GEE API access is working\n- Validates credential format and permissions\n\n**Typical Workflow**:\n1. Check if credentials exist using GET /user/me/gee-credentials\n2. Test credentials validity using this endpoint\n3. If credentials are valid, proceed with GEE analysis\n4. If credentials are invalid/expired, refresh or update credentials\n\n**Error Responses**:\n- `400 Bad Request`: GEE credentials not configured or invalid/expired\n- `401 Unauthorized`: JWT token required or invalid\n- `404 Not Found`: User not found\n- `500 Internal Server Error`: Failed to test credentials","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Test current user's Google Earth Engine credentials."}},"/api/v1/user/me/gee-oauth/callback":{"post":{"description":"**Authentication**: JWT token required\n**Authorization**: Any authenticated user\n**Content-Type**: application/json\n\n**Request Body Schema**:\n```json\n{\n  \"code\": \"authorization_code_from_google\",\n  \"state\": \"state_token_from_initiate_call\"\n}\n```\n\n**Required Fields**:\n- `code`: Authorization code received from Google OAuth callback\n- `state`: State token from the initiate call for CSRF protection\n\n**Response Schema**:\n```json\n{\n  \"message\": \"GEE OAuth credentials saved successfully\"\n}\n```\n\n**Error Responses**:\n- `400 Bad Request`: Missing code/state, invalid code, or JSON parsing error\n- `401 Unauthorized`: JWT token required or invalid\n- `404 Not Found`: User not found\n- `500 Internal Server Error`: Failed to exchange code or save credentials","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Complete OAuth flow and store Google Earth Engine credentials."}},"/api/v1/user/me/gee-oauth/initiate":{"post":{"description":"**Authentication**: JWT token required\n**Authorization**: Any authenticated user\n\n**Prerequisites**:\n- Server must have Google OAuth client credentials configured\n- Environment variables GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET\n  required\n\n**Response Schema**:\n```json\n{\n  \"data\": {\n    \"auth_url\": \"https://accounts.google.com/o/oauth2/auth?...\",\n    \"state\": \"random-state-string-for-csrf-protection\"\n  }\n}\n```\n\n**Response Fields**:\n- `auth_url`: URL to redirect user to for Google OAuth authorization\n- `state`: CSRF protection token to include in callback\n\n**OAuth Flow Steps**:\n1. Call this endpoint to get authorization URL\n2. Redirect user to the auth_url\n3. User authorizes your application in Google\n4. User is redirected back with authorization code\n5. Call `/user/me/gee-oauth/callback` with the code and state\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required or invalid\n- `500 Internal Server Error`: OAuth not configured or server error","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Initiate OAuth flow for Google Earth Engine authentication."}},"/api/v1/user/me/gee-service-account":{"post":{"description":"**Authentication**: JWT token required\n**Authorization**: Any authenticated user\n**Content-Type**: application/json\n\n**Request Body Schema**:\n```json\n{\n  \"service_account_key\": {\n    \"type\": \"service_account\",\n    \"project_id\": \"your-gee-project\",\n    \"private_key_id\": \"key-id\",\n    \"private_key\": \"-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----\\n\",\n    \"client_email\": \"service-account@your-gee-project.iam.gserviceaccount.com\",\n    \"client_id\": \"client-id\",\n    \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n    \"token_uri\": \"https://oauth2.googleapis.com/token\",\n    \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n    \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/...\"\n  }\n}\n```\n\n**Required Fields**:\n- `service_account_key`: Google service account JSON key object or JSON string\n\n**Service Account Key Requirements**:\n- Must be a valid Google Cloud service account key\n- Must have Google Earth Engine API access enabled\n- Should have appropriate permissions for your GEE project\n- Can be provided as JSON object or JSON string\n\n**Response Schema**:\n```json\n{\n  \"message\": \"GEE service account credentials saved successfully\"\n}\n```\n\n**Security Notes**:\n- Service account keys are encrypted before storage\n- Keys should be generated specifically for Trends.Earth use\n- Rotate keys regularly following Google Cloud security best practices\n\n**Error Responses**:\n- `400 Bad Request`: Missing/invalid service account key, or validation failed\n- `401 Unauthorized`: JWT token required or invalid\n- `404 Not Found`: User not found\n- `500 Internal Server Error`: Failed to save credentials","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Upload Google Earth Engine service account credentials."}},"/api/v1/user/me/google-groups":{"put":{"description":"**Authentication**: JWT token required\n**Access**: Users can update their own Google Groups preferences\n**Purpose**: Change opt-in preferences and sync with Google Groups service\n\n**Request Schema**:\n```json\n{\n  \"preferences\": {\n    \"trends_earth_users\": true,\n    \"trendsearth\": false\n  }\n}\n```\n\n**Request Fields**:\n- `preferences`: Object containing group opt-in preferences\n  - `trends_earth_users`: Boolean for general user community\n  - `trendsearth`: Boolean for platform-specific group\n  - Only valid group keys accepted\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"message\": \"Google Groups preferences updated successfully\",\n    \"updated_groups\": [\"trends_earth_users\"],\n    \"preferences\": {\n      \"trends_earth_users\": true,\n      \"trendsearth\": false\n    },\n    \"sync_results\": {\n      \"user_email\": \"user@example.com\",\n      \"trends_earth_users\": {\n        \"action\": \"added\",\n        \"success\": true,\n        \"current_member\": true\n      },\n      \"trendsearth\": {\n        \"action\": \"no_change\",\n        \"success\": true,\n        \"current_member\": false\n      }\n    }\n  }\n}\n```\n\n**Update Process**:\n1. Validates request data and group keys\n2. Updates user preferences in database\n3. Triggers synchronization with Google Groups service\n4. Returns updated preferences and sync results\n5. Handles partial sync failures gracefully\n\n**Sync Actions**:\n- `added`: User was added to the Google Group\n- `removed`: User was removed from the Google Group\n- `no_change`: User membership unchanged\n- `verified`: Membership status confirmed\n\n**Preference Validation**:\n- Only accepts valid group keys\n- Boolean values required for all preferences\n- Partial updates not supported - must specify all groups\n\n**Error Handling**:\n- Preferences saved even if sync fails\n- Sync errors reported in response\n- User can retry sync separately\n- Service availability checked before sync\n\n**Error Responses**:\n- `400 Bad Request`: Invalid preferences data or group keys\n- `401 Unauthorized`: JWT token required\n- `500 Internal Server Error`: Failed to update preferences or sync","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Update current user's Google Groups preferences and trigger synchronization."}},"/api/v1/user/me/google-groups/sync":{"post":{"description":"**Authentication**: JWT token required\n**Access**: Users can trigger sync for their own Google Groups\n**Purpose**: Force synchronization between user preferences and actual Google\nGroups memberships\n\n**Request**: No request body required - this is a POST endpoint\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"message\": \"Google Groups sync completed\",\n    \"sync_results\": {\n      \"user_email\": \"user@example.com\",\n      \"trends_earth_users\": {\n        \"action\": \"verified\",\n        \"success\": true,\n        \"current_member\": true,\n        \"details\": \"Membership confirmed in Google Groups\"\n      },\n      \"trendsearth\": {\n        \"action\": \"removed\",\n        \"success\": true,\n        \"current_member\": false,\n        \"details\": \"User removed from group as requested\"\n      }\n    }\n  }\n}\n```\n\n**Manual Sync Process**:\n1. Checks Google Groups service availability\n2. Reads current user preferences from database\n3. Queries actual Google Groups memberships\n4. Synchronizes memberships to match preferences\n5. Returns detailed sync results\n\n**Sync Actions Performed**:\n- `added`: User added to Google Group\n- `removed`: User removed from Google Group\n- `verified`: Membership status confirmed as correct\n- `skipped`: No action needed\n- `failed`: Action attempted but failed\n\n**Use Cases**:\n- Verify current group memberships\n- Recover from failed automatic sync\n- Troubleshoot synchronization issues\n- Force immediate sync after preference changes\n\n**Service Dependency**:\n- Requires Google Groups service to be available\n- Returns service unavailable error if Google Groups down\n- Handles temporary service outages gracefully\n\n**Error Recovery**:\n- Provides detailed error information\n- Distinguishes between service and permission errors\n- Suggests next steps for error resolution\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `503 Service Unavailable`: Google Groups service not available\n- `500 Internal Server Error`: Sync process failed","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Manually trigger synchronization of user's Google Groups memberships."}},"/api/v1/user/me/openeo-credentials":{"delete":{"description":"**Authentication**: JWT token required.\n\n**Response**: ``200 OK`` with ``{\"data\": {\"status\": \"credentials removed\"}}``\n\n**Error Responses**:\n- ``401 Unauthorized``\n- ``404 Not Found``: user not found\n- ``500 Internal Server Error``","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Remove the current user's stored openEO credentials."}},"/api/v1/user/me/openeo-credentials/check":{"get":{"description":"Makes a live request to the configured openEO backend.\n\n**Authentication**: JWT token required.\n\n**Response Schema**::\n\n    {\n      \"data\": {\n        \"valid\": true,\n        \"message\": \"Credentials are valid\"\n      }\n    }\n\n**Error Responses**:\n- ``401 Unauthorized``\n- ``404 Not Found``: user not found\n- ``500 Internal Server Error``","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Validate the current user's stored openEO credentials against the backend."}},"/api/v1/user/me/sessions":{"delete":{"description":"**Authentication**: JWT token required\n**Access**: Revokes all sessions for current authenticated user\n**Purpose**: Complete logout from all devices and applications\n\n**Request**: No request body required\n\n**Success Response Schema**:\n```json\n{\n  \"message\": \"Successfully revoked 3 sessions\"\n}\n```\n\n**Revocation Process**:\n- Identifies all active sessions for current user\n- Invalidates all session tokens immediately\n- Removes all sessions from active sessions list\n- Logs bulk revocation event for security audit\n- Includes current session (user will need to re-authenticate)\n\n**Use Cases**:\n- Emergency security response (suspected account compromise)\n- Complete logout when changing passwords\n- Privacy protection when using shared/public computers\n- Account cleanup and security hygiene\n\n**Security Features**:\n- Immediate invalidation of all tokens\n- Forces re-authentication on all devices\n- Comprehensive security reset\n- Audit logging with session count\n\n**Post-Revocation Effect**:\n- User is logged out from all devices/applications\n- All API requests with old tokens will fail\n- Fresh login required on all devices\n- New sessions will have new tokens and IDs\n\n**Important Notes**:\n- This action affects the current session making the request\n- User will need to re-authenticate immediately after this call\n- All mobile apps and browser sessions will require re-login\n- Consider using this for security incidents or password changes\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `500 Internal Server Error`: Failed to revoke sessions","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Revoke all authentication sessions for current user (logout everywhere)."}},"/api/v1/user/me/sessions/{session_id}":{"delete":{"description":"**Authentication**: JWT token required\n**Access**: Users can only revoke their own sessions\n**Purpose**: Selective session termination for security\n\n**Path Parameters**:\n- `session_id`: Session identifier to revoke\n\n**Request**: No request body required\n\n**Success Response Schema**:\n```json\n{\n  \"message\": \"Session revoked successfully\"\n}\n```\n\n**Revocation Process**:\n- Validates session belongs to current user\n- Invalidates the specified session token\n- Removes session from active sessions list\n- Logs revocation event for security audit\n\n**Use Cases**:\n- Log out from specific device while staying logged in on others\n- Security response to suspicious session activity\n- Remote device management (e.g., lost phone)\n- Granular session control\n\n**Security Features**:\n- Users can only revoke their own sessions (privacy protection)\n- Immediate token invalidation\n- Audit logging for security monitoring\n- No impact on other active sessions\n\n**Session Identification**:\n- Use `GET /user/me/sessions` to list sessions and get session IDs\n- Sessions are identified by unique session identifiers\n- Current session can be revoked (will require re-authentication)\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `404 Not Found`: Session not found or doesn't belong to user\n- `500 Internal Server Error`: Failed to revoke session","parameters":[{"description":"Path parameter: session_id","in":"path","name":"session_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Revoke a specific authentication session."}},"/api/v1/user/reset-password":{"post":{"description":"**Rate Limited**: Subject to password recovery rate limits (configurable)\n**Access**: Public endpoint - no authentication required\n**Security**: Token-based authentication, tokens expire after 1 hour\n\n**Request Body Schema**:\n```json\n{\n  \"token\": \"secure-reset-token-from-email\",\n  \"password\": \"new-secure-password\"\n}\n```\n\n**Password Requirements**:\n- Minimum 8 characters\n- Must contain at least one uppercase letter\n- Must contain at least one lowercase letter\n- Must contain at least one digit\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"message\": \"Password reset successful\"\n  }\n}\n```\n\n**Security Features**:\n- Tokens are single-use (marked as used after successful reset)\n- Tokens expire after 1 hour\n- Rate limiting prevents brute force attacks\n- Password strength validation enforced\n\n**Error Responses**:\n- `400 Bad Request`: Missing token or password\n- `404 Not Found`: Invalid or expired token\n- `422 Unprocessable Entity`: Password doesn't meet requirements\n- `429 Too Many Requests`: Rate limit exceeded\n- `500 Internal Server Error`: System error","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Reset password using a secure token from password recovery email."}},"/api/v1/user/{user_id}/gee-credentials":{"delete":{"description":"**Authentication**: JWT token required\n**Authorization**: ADMIN or SUPERADMIN role required\n\n**Path Parameters**:\n- `user_id`: Target user's ID (string or integer)\n\n**Response Schema**:\n```json\n{\n  \"message\": \"GEE credentials deleted for user user@example.com\"\n}\n```\n\n**What Gets Deleted**:\n- All OAuth tokens (access and refresh tokens)\n- Service account credentials\n- Credentials type and metadata\n- All encrypted credential data is permanently removed\n\n**Admin Use Cases**:\n- Revoke access for users leaving the organization\n- Clean up expired or compromised credentials\n- Force credential refresh by removing and re-adding\n- Audit and compliance requirements\n\n**Security & Audit**:\n- Admin action is logged with both admin and target user details\n- Irreversible operation - credentials cannot be recovered\n- User will need to reconfigure GEE credentials to regain access\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required or invalid\n- `403 Forbidden`: Admin access required\n- `404 Not Found`: Target user not found or user has no GEE credentials\n- `500 Internal Server Error`: Failed to delete credentials","parameters":[{"description":"Path parameter: user_id","in":"path","name":"user_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Delete another user's Google Earth Engine credentials (Admin only)."}},"/api/v1/user/{user_id}/gee-credentials/test":{"post":{"description":"**Authentication**: JWT token required\n**Authorization**: ADMIN or SUPERADMIN role required\n\n**Path Parameters**:\n- `user_id`: Target user's ID (string or integer)\n\n**Prerequisites**:\n- Target user must have GEE credentials configured\n\n**Response Schema (Success)**:\n```json\n{\n  \"message\": \"GEE credentials for user user@example.com are valid and working\"\n}\n```\n\n**What This Tests**:\n- Initializes Google Earth Engine with the user's credentials\n- Verifies credentials are not expired\n- Confirms GEE API access is working\n- Validates credential format and permissions\n\n**Admin Use Cases**:\n- Validate credentials after setup/update\n- Troubleshoot user access issues\n- Periodic credential health checks\n- Pre-execution validation for GEE scripts\n\n**Error Responses**:\n- `400 Bad Request`: No GEE credentials or credentials are invalid/expired\n- `401 Unauthorized`: JWT token required or invalid\n- `403 Forbidden`: Admin access required\n- `404 Not Found`: Target user not found\n- `500 Internal Server Error`: Failed to test credentials","parameters":[{"description":"Path parameter: user_id","in":"path","name":"user_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Test another user's Google Earth Engine credentials (Admin only)."}},"/api/v1/user/{user_id}/gee-service-account":{"post":{"description":"**Authentication**: JWT token required\n**Authorization**: ADMIN or SUPERADMIN role required\n**Content-Type**: application/json\n\n**Path Parameters**:\n- `user_id`: Target user's ID (string or integer)\n\n**Request Body Schema**:\n```json\n{\n  \"service_account_key\": {\n    \"type\": \"service_account\",\n    \"project_id\": \"your-gee-project\",\n    \"private_key_id\": \"key-id\",\n    \"private_key\": \"-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----\\n\",\n    \"client_email\": \"service-account@your-gee-project.iam.gserviceaccount.com\",\n    \"client_id\": \"client-id\",\n    \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n    \"token_uri\": \"https://oauth2.googleapis.com/token\",\n    \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n    \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/...\"\n  }\n}\n```\n\n**Required Fields**:\n- `service_account_key`: Complete service account JSON key object or JSON string\n\n**Response Schema**:\n```json\n{\n  \"message\": \"GEE service account credentials saved for user user@example.com\"\n}\n```\n\n**Admin Use Cases**:\n- Provide organizational GEE access to users\n- Set up shared service account for team projects\n- Replace expired or compromised credentials\n- Migrate users from individual to shared credentials\n\n**Security & Audit**:\n- Admin action is logged with both admin and target user details\n- Service account keys are encrypted before storage\n- Replaces any existing credentials for the user\n\n**Error Responses**:\n- `400 Bad Request`: Missing service account, invalid JSON, or validation failed\n- `401 Unauthorized`: JWT token required or invalid\n- `403 Forbidden`: Admin access required\n- `404 Not Found`: Target user not found\n- `500 Internal Server Error`: Failed to save credentials","parameters":[{"description":"Path parameter: user_id","in":"path","name":"user_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Upload Google Earth Engine service account for another user (Admin only)."}},"/api/v1/user/{user}":{"delete":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin level access required (ADMIN or SUPERADMIN)\n**Warning**: This action is irreversible and deletes all user data\n\n**Path Parameters**:\n- `user`: User identifier (email address or numeric ID) to delete\n\n**Protection**: Cannot delete protected admin accounts configured via\nAPI_ENVIRONMENT_USER\n\n**Deletion Process**:\n- Cancels all running executions for the target user\n- Deletes all execution history and logs\n- Removes user-created scripts (if user is owner)\n- Deletes user profile and authentication data\n- Revokes all active sessions and tokens\n- Removes user from any Google Groups (if enabled)\n- Transfers or deletes shared resources\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"id\": \"user-456\",\n    \"email\": \"user@example.com\",\n    \"name\": \"John Doe\",\n    \"role\": \"USER\",\n    \"created_at\": \"2025-01-15T10:30:00Z\",\n    \"updated_at\": \"2025-01-15T14:30:00Z\",\n    \"deleted_at\": \"2025-01-15T14:30:00Z\",\n    \"deleted_by\": \"admin-123\",\n    \"status\": \"DELETED\"\n  }\n}\n```\n\n**Administrative Features**:\n- Action is logged with admin user who performed deletion\n- All user data is permanently removed\n- User's scripts and executions are cleaned up\n- Email address becomes available for re-registration\n\n**Data Cleanup**:\n- User profile information permanently deleted\n- Execution history and logs removed\n- Script ownership transferred or scripts deleted\n- Session tokens invalidated immediately\n- Audit logs maintain record of deletion action\n\n**Use Cases**:\n- Account cleanup and user management\n- Compliance with data retention policies\n- Removing inactive or problematic accounts\n- GDPR \"right to erasure\" compliance\n\n**Error Responses**:\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Admin access required or cannot delete system admin\n- `404 Not Found`: User does not exist\n- `500 Internal Server Error`: Account deletion failed","parameters":[{"description":"Path parameter: user","in":"path","name":"user","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Delete another user's account and all associated data (admin only)."}},"/api/v1/user/{user}/change-password":{"patch":{"description":"**Authentication**: JWT token required\n**Authorization**: Admin level access required (ADMIN or SUPERADMIN)\n**Security**: Admin password reset without requiring old password\n\n**Path Parameters**:\n- `user`: User identifier (email address or numeric ID) for password change\n\n**Request Schema**:\n```json\n{\n  \"new_password\": \"newSecurePassword123\"\n}\n```\n\n**Request Fields**:\n- `new_password`: New password to set for the user (required)\n\n**Response Schema**:\n```json\n{\n  \"data\": {\n    \"id\": \"user-456\",\n    \"email\": \"user@example.com\",\n    \"name\": \"John Doe\",\n    \"role\": \"USER\",\n    \"created_at\": \"2025-01-15T10:30:00Z\",\n    \"updated_at\": \"2025-01-15T15:00:00Z\",\n    \"password_last_changed\": \"2025-01-15T15:00:00Z\",\n    \"password_changed_by\": \"admin-123\"\n  }\n}\n```\n\n**Administrative Features**:\n- Does not require user's current password\n- Updates password change timestamp\n- Records which admin performed the change\n- Enforces same password strength requirements\n- Invalidates user's existing sessions for security\n\n**Security Considerations**:\n- Action is logged for audit purposes\n- User receives notification of password change\n- All user's active sessions are terminated\n- Password must meet system security requirements\n\n**Use Cases**:\n- Emergency account recovery for locked users\n- Password reset for users who forgot credentials\n- Security incident response\n- Administrative account maintenance\n\n**Audit Logging**:\n- Records admin user who changed password\n- Timestamps the password change event\n- Maintains audit trail for compliance\n- May trigger security notifications\n\n**Error Responses**:\n- `400 Bad Request`: Missing new_password field\n- `401 Unauthorized`: JWT token required\n- `403 Forbidden`: Admin access required, or ADMIN trying to change\n  SUPERADMIN password\n- `404 Not Found`: User does not exist\n- `422 Unprocessable Entity`: Password doesn't meet requirements\n- `500 Internal Server Error`: Password change failed","parameters":[{"description":"Path parameter: user","in":"path","name":"user","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"security":[{"bearerAuth":[]}],"summary":"Change another user's password (admin only)."}},"/api/v1/user/{user}/recover-password":{"post":{"description":"**Rate Limited**: Subject to password recovery rate limits (configurable)\n**Access**: Public endpoint - no authentication required\n**Security**: Rate limited to prevent abuse and email flooding\n\n**Path Parameters**:\n- `user`: User identifier (email address or numeric ID)\n\n**Request**: No request body required\n\n    **Recovery Process**:\n1. Validates user exists and account is active\n2. Generates secure password reset token with 1-hour expiration\n3. Sends password recovery email with reset link\n4. User clicks link and sets new password via /user/reset-password endpoint\n\n**Success Response Schema**:\n```json\n{\n  \"data\": {\n    \"message\": \"If an account with that email exists,\n      a password recovery email has been sent.\"\n  }\n}\n```\n\n**Security Notes**:\n- Returns the same response regardless of whether the user exists,\n  preventing user enumeration attacks (CWE-204).\n    - Rate limiting prevents email flooding attacks.\n\n**Error Responses**:\n- `429 Too Many Requests`: Rate limit exceeded\n- `500 Internal Server Error`: System error (email failures are masked)","parameters":[{"description":"Path parameter: user","in":"path","name":"user","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Initiate password recovery process for a user account."}},"/auth":{"post":{"description":"Endpoint: create_token","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Endpoint: create_token"}},"/auth/logout":{"post":{"description":"Endpoint: logout","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Endpoint: logout"}},"/auth/logout-all":{"post":{"description":"Endpoint: logout_all","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Endpoint: logout_all"}},"/auth/refresh":{"post":{"description":"Endpoint: refresh_token","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Endpoint: refresh_token"}},"/debug/routes":{"get":{"description":"Security: This endpoint is only available in development/test environments.\nIt is completely disabled in production and staging unless explicitly enabled.","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Debug endpoint to show all registered routes."}},"/ping":{"get":{"description":"Simple ping endpoint without database dependency","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Success"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal Server Error"}},"summary":"Simple ping endpoint without database dependency"}}},"servers":[{"description":"API v1","url":"/api/v1"}]}
