{
  "openapi": "3.1.0",
  "info": {
    "title": "ThaiSafe Public API",
    "version": "1.0.0",
    "summary": "Real-time Thailand travel safety data for humans and AI agents.",
    "description": "Public, read-only REST API for Thailand tourist safety data: per-region safety scores, active alerts, hospitals, beach conditions, road risk, verified scams, fair transport pricing, and AI-classified safety news.\n\nNo authentication is required for any endpoint in this spec. All responses are JSON in the envelope `{ \"success\": boolean, \"data\": ... }`; errors return `{ \"success\": false, \"error\": { \"code\", \"message\" } }`.\n\nRate limits (per IP): 100 requests/minute globally; `/regions/*` and `/news/*` are limited to 60 requests/minute. Exceeding the limit returns HTTP 429.\n\nAI agents can also connect via the Model Context Protocol at https://api.thaisafe.app/mcp. For commercial/high-volume access see https://thaisafe.app/data.",
    "contact": {
      "name": "ThaiSafe API",
      "email": "api@thaisafe.app",
      "url": "https://thaisafe.app/agents"
    },
    "termsOfService": "https://thaisafe.app/terms"
  },
  "servers": [
    { "url": "https://api.thaisafe.app", "description": "Production" }
  ],
  "tags": [
    { "name": "Regions", "description": "Aggregated per-region safety summaries with live scores." },
    { "name": "Alerts", "description": "Active safety alerts (weather, road, health, security, police checkpoints)." },
    { "name": "Safety", "description": "Hospitals, beaches, roads, scams, and transport pricing." },
    { "name": "News", "description": "Safety-relevant news from Thai outlets, AI-classified by region and category." }
  ],
  "paths": {
    "/regions/{slug}/summary": {
      "get": {
        "tags": ["Regions"],
        "operationId": "getRegionSummary",
        "summary": "Live safety summary for a region",
        "description": "Returns the current safety picture for one region: composite safety score (overall, alert, report, road sub-scores with a green/yellow/red color band, recomputed every 15 minutes), counts of active alerts and recent community reports, hospital and beach counts, and up to 5 curated verified community quotes. Cached for 5 minutes. If no score has been computed yet, a neutral default score of 5.0 is returned.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Region slug, e.g. `bangkok`, `phuket`, `chiang-mai`, `pattaya`, `krabi`, `koh-samui`. Max 50 characters.",
            "schema": { "type": "string", "maxLength": 50, "examples": ["phuket"] }
          }
        ],
        "responses": {
          "200": {
            "description": "Region summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["success", "data"],
                  "properties": {
                    "success": { "const": true },
                    "data": { "$ref": "#/components/schemas/RegionSummary" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/alerts": {
      "get": {
        "tags": ["Alerts"],
        "operationId": "listAlerts",
        "summary": "List active safety alerts",
        "description": "Paginated list of currently active alerts, newest first. Alerts include weather warnings, road hazards, health advisories, security incidents, and community-reported police checkpoints. Filter by region with `?region=`.",
        "parameters": [
          { "name": "region", "in": "query", "required": false, "description": "Region slug filter, e.g. `phuket`.", "schema": { "type": "string" } },
          { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "default": 1 } },
          { "name": "pageSize", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } }
        ],
        "responses": {
          "200": {
            "description": "Active alerts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["success", "data"],
                  "properties": {
                    "success": { "const": true },
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Alert" } },
                    "page": { "type": "integer" },
                    "pageSize": { "type": "integer" }
                  }
                }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/alerts/{id}": {
      "get": {
        "tags": ["Alerts"],
        "operationId": "getAlert",
        "summary": "Get a single alert",
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": {
          "200": { "description": "Alert", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AlertEnvelope" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/safety/hospitals": {
      "get": {
        "tags": ["Safety"],
        "operationId": "listHospitals",
        "summary": "Hospital directory",
        "description": "Hospitals with coordinates, phone and emergency phone numbers, 24-hour status, and ratings. Filter by `?region=`.",
        "parameters": [{ "$ref": "#/components/parameters/Region" }],
        "responses": {
          "200": { "description": "Hospitals", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HospitalListEnvelope" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/safety/hospitals/{id}": {
      "get": {
        "tags": ["Safety"],
        "operationId": "getHospital",
        "summary": "Get a single hospital",
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": {
          "200": { "description": "Hospital", "content": { "application/json": { "schema": { "type": "object", "required": ["success", "data"], "properties": { "success": { "const": true }, "data": { "$ref": "#/components/schemas/Hospital" } } } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/safety/beaches": {
      "get": {
        "tags": ["Safety"],
        "operationId": "listBeaches",
        "summary": "Beach conditions",
        "description": "Beaches with flag status (green/yellow/red), current strength, water quality, and hazard lists. Filter by `?province=`. Pass `?locale=` (e.g. `th`, `fr`, `ja`) to receive machine-translated name/location/province fields.",
        "parameters": [
          { "name": "province", "in": "query", "required": false, "description": "Province slug filter, e.g. `phuket`.", "schema": { "type": "string" } },
          { "$ref": "#/components/parameters/Locale" }
        ],
        "responses": {
          "200": { "description": "Beaches", "content": { "application/json": { "schema": { "type": "object", "required": ["success", "data"], "properties": { "success": { "const": true }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/Beach" } } } } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/safety/beaches/{id}": {
      "get": {
        "tags": ["Safety"],
        "operationId": "getBeach",
        "summary": "Get a single beach",
        "parameters": [{ "$ref": "#/components/parameters/Id" }, { "$ref": "#/components/parameters/Locale" }],
        "responses": {
          "200": { "description": "Beach", "content": { "application/json": { "schema": { "type": "object", "required": ["success", "data"], "properties": { "success": { "const": true }, "data": { "$ref": "#/components/schemas/Beach" } } } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/safety/roads": {
      "get": {
        "tags": ["Safety"],
        "operationId": "listRoads",
        "summary": "Road safety data",
        "description": "Roads with traffic level, scooter safety rating, hazards, accident rate (decayed 1%/day since last update), fatality counts, and tourist risk score. Returns at most 200 rows. Filter by `?region=`.",
        "parameters": [{ "$ref": "#/components/parameters/Region" }, { "$ref": "#/components/parameters/Locale" }],
        "responses": {
          "200": { "description": "Roads", "content": { "application/json": { "schema": { "type": "object", "required": ["success", "data"], "properties": { "success": { "const": true }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/Road" } } } } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/safety/roads/{id}": {
      "get": {
        "tags": ["Safety"],
        "operationId": "getRoad",
        "summary": "Get a single road",
        "parameters": [{ "$ref": "#/components/parameters/Id" }, { "$ref": "#/components/parameters/Locale" }],
        "responses": {
          "200": { "description": "Road", "content": { "application/json": { "schema": { "type": "object", "required": ["success", "data"], "properties": { "success": { "const": true }, "data": { "$ref": "#/components/schemas/Road" } } } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/safety/scams": {
      "get": {
        "tags": ["Safety"],
        "operationId": "listScams",
        "summary": "Verified tourist scam database",
        "description": "Known tourist scams ordered by report count (most reported first), with location, severity, and verification status.",
        "responses": {
          "200": { "description": "Scams", "content": { "application/json": { "schema": { "type": "object", "required": ["success", "data"], "properties": { "success": { "const": true }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/Scam" } } } } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/safety/scams/{id}": {
      "get": {
        "tags": ["Safety"],
        "operationId": "getScam",
        "summary": "Get a single scam report",
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": {
          "200": { "description": "Scam", "content": { "application/json": { "schema": { "type": "object", "required": ["success", "data"], "properties": { "success": { "const": true }, "data": { "$ref": "#/components/schemas/Scam" } } } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/safety/transport": {
      "get": {
        "tags": ["Safety"],
        "operationId": "listTransportRoutes",
        "summary": "Fair transport pricing guide",
        "description": "Fair vs. typical tourist prices (THB) for common taxi, tuk-tuk, boat, and train routes — useful for detecting overcharging.",
        "responses": {
          "200": { "description": "Transport routes", "content": { "application/json": { "schema": { "type": "object", "required": ["success", "data"], "properties": { "success": { "const": true }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/TransportRoute" } } } } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/news": {
      "get": {
        "tags": ["News"],
        "operationId": "listNews",
        "summary": "List safety news articles",
        "description": "Paginated safety-relevant news, newest first. `category`, `region`, and `source` accept comma-separated lists. `minRelevance` (0-1) filters by AI relevance score. Responses are cached for up to 5 minutes.",
        "parameters": [
          { "name": "category", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Comma-separated categories, e.g. `crime,weather`." },
          { "name": "region", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Comma-separated region slugs." },
          { "name": "source", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Comma-separated source slugs." },
          { "name": "minRelevance", "in": "query", "required": false, "schema": { "type": "number", "minimum": 0, "maximum": 1 } },
          { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "default": 1 } },
          { "name": "pageSize", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } }
        ],
        "responses": {
          "200": {
            "description": "News articles",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["success", "data"],
                  "properties": {
                    "success": { "const": true },
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/NewsArticle" } },
                    "pagination": {
                      "type": "object",
                      "properties": { "page": { "type": "integer" }, "pageSize": { "type": "integer" }, "total": { "type": "integer" } }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/news/feed": {
      "get": {
        "tags": ["News"],
        "operationId": "getNewsFeed",
        "summary": "Curated safety news feed",
        "description": "High-relevance (score >= 0.5) safety news, newest first. Pass `?locale=` for machine-translated title/description.",
        "parameters": [
          { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 } },
          { "$ref": "#/components/parameters/Locale" }
        ],
        "responses": {
          "200": { "description": "Curated feed", "content": { "application/json": { "schema": { "type": "object", "required": ["success", "data"], "properties": { "success": { "const": true }, "data": { "type": "array", "items": { "$ref": "#/components/schemas/NewsArticle" } } } } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/news/categories": {
      "get": {
        "tags": ["News"],
        "operationId": "listNewsCategories",
        "summary": "News categories with counts",
        "responses": {
          "200": {
            "description": "Categories",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["success", "data"],
                  "properties": {
                    "success": { "const": true },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "category": { "type": ["string", "null"] },
                          "count": { "type": "integer" }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/news/{id}": {
      "get": {
        "tags": ["News"],
        "operationId": "getNewsArticle",
        "summary": "Get a single news article",
        "parameters": [{ "$ref": "#/components/parameters/Id" }, { "$ref": "#/components/parameters/Locale" }],
        "responses": {
          "200": { "description": "Article", "content": { "application/json": { "schema": { "type": "object", "required": ["success", "data"], "properties": { "success": { "const": true }, "data": { "$ref": "#/components/schemas/NewsArticle" } } } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "Id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string" }
      },
      "Region": {
        "name": "region",
        "in": "query",
        "required": false,
        "description": "Region slug filter, e.g. `phuket`, `bangkok`.",
        "schema": { "type": "string" }
      },
      "Locale": {
        "name": "locale",
        "in": "query",
        "required": false,
        "description": "BCP-47-style locale code (e.g. `th`, `zh`, `ja`, `fr`). When set and not `en`, translatable text fields are machine-translated.",
        "schema": { "type": "string" }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid parameters",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } }
      },
      "RateLimited": {
        "description": "Rate limit exceeded (60-100 requests/minute per IP depending on route)",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } }
      }
    },
    "schemas": {
      "ErrorEnvelope": {
        "type": "object",
        "required": ["success", "error"],
        "properties": {
          "success": { "const": false },
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "string", "examples": ["NOT_FOUND", "INVALID_PARAM", "RATE_LIMITED"] },
              "message": { "type": "string" }
            }
          }
        }
      },
      "RegionSummary": {
        "type": "object",
        "required": ["region", "score", "counts", "quotes"],
        "properties": {
          "region": { "type": "string", "description": "Region slug." },
          "score": {
            "type": "object",
            "required": ["overall", "alert", "report", "road", "colorBand", "computedAt"],
            "properties": {
              "overall": { "type": "number", "minimum": 0, "maximum": 10, "description": "Composite safety score, 0 (unsafe) to 10 (safest)." },
              "alert": { "type": "number", "minimum": 0, "maximum": 10 },
              "report": { "type": "number", "minimum": 0, "maximum": 10 },
              "road": { "type": "number", "minimum": 0, "maximum": 10 },
              "colorBand": { "type": "string", "enum": ["green", "yellow", "red"] },
              "computedAt": { "type": "string", "format": "date-time" }
            }
          },
          "counts": {
            "type": "object",
            "required": ["activeAlerts", "recentReports", "verifiedReports", "hospitals", "beaches"],
            "properties": {
              "activeAlerts": { "type": "integer" },
              "recentReports": { "type": "integer", "description": "Approved community reports in the last 30 days." },
              "verifiedReports": { "type": "integer" },
              "hospitals": { "type": "integer" },
              "beaches": { "type": "integer" }
            }
          },
          "quotes": {
            "type": "array",
            "maxItems": 5,
            "description": "Curated verified community reports (PII scrubbed), highest-upvoted first.",
            "items": {
              "type": "object",
              "required": ["id", "description", "type", "upvotes", "createdAt"],
              "properties": {
                "id": { "type": "string" },
                "description": { "type": "string" },
                "type": { "type": "string" },
                "upvotes": { "type": "integer" },
                "createdAt": { "type": "string", "format": "date-time" }
              }
            }
          }
        }
      },
      "Alert": {
        "type": "object",
        "required": ["id", "type", "severity", "title", "description", "region", "latitude", "longitude", "isActive", "startTime", "source", "createdAt"],
        "properties": {
          "id": { "type": "string" },
          "type": { "type": "string", "description": "Alert type, e.g. `weather`, `road`, `health`, `security`, `police`." },
          "severity": { "type": "string", "enum": ["info", "warning", "danger"] },
          "title": { "type": "string" },
          "description": { "type": "string" },
          "region": { "type": "string" },
          "latitude": { "type": "number" },
          "longitude": { "type": "number" },
          "isActive": { "type": "boolean" },
          "startTime": { "type": "string", "format": "date-time" },
          "endTime": { "type": ["string", "null"], "format": "date-time" },
          "source": { "type": "string", "description": "Origin of the alert, e.g. `community`, `news`." },
          "metadata": {
            "type": ["object", "null"],
            "description": "Public incident metadata (aggregate confirm/deny counts; voter identities removed).",
            "properties": {
              "confirms": { "type": "integer" },
              "denies": { "type": "integer" },
              "direction": { "type": "string" },
              "note": { "type": "string" }
            }
          },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "AlertEnvelope": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": { "const": true },
          "data": { "$ref": "#/components/schemas/Alert" }
        }
      },
      "Hospital": {
        "type": "object",
        "required": ["id", "name", "nameLocal", "latitude", "longitude", "phone", "emergencyPhone", "rating", "isOpen24h", "address", "region"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "nameLocal": { "type": "string", "description": "Thai-script name." },
          "latitude": { "type": "number" },
          "longitude": { "type": "number" },
          "phone": { "type": "string" },
          "emergencyPhone": { "type": "string" },
          "rating": { "type": "number", "minimum": 0, "maximum": 5 },
          "isOpen24h": { "type": "boolean" },
          "address": { "type": "string" },
          "imageUrl": { "type": ["string", "null"] },
          "region": { "type": "string" }
        }
      },
      "HospitalListEnvelope": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": { "const": true },
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Hospital" } }
        }
      },
      "Beach": {
        "type": "object",
        "required": ["id", "name", "location", "province", "description", "latitude", "longitude", "flagStatus", "currentStrength", "waterQuality", "hazards", "rating", "isPopular", "lastUpdated"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "location": { "type": "string" },
          "province": { "type": "string" },
          "description": { "type": "string" },
          "latitude": { "type": "number" },
          "longitude": { "type": "number" },
          "flagStatus": { "type": "string", "enum": ["green", "yellow", "red"], "description": "Swimming flag: green = safe, yellow = caution, red = do not swim." },
          "currentStrength": { "type": "string", "description": "e.g. `calm`, `moderate`, `strong`." },
          "waterQuality": { "type": "string", "description": "e.g. `good`, `fair`, `poor`." },
          "hazards": { "type": "array", "items": { "type": "string" } },
          "imageUrl": { "type": ["string", "null"] },
          "rating": { "type": "number", "minimum": 0, "maximum": 5 },
          "isPopular": { "type": "boolean" },
          "lastUpdated": { "type": "string", "format": "date-time" }
        }
      },
      "Road": {
        "type": "object",
        "required": ["id", "name", "location", "province", "region", "description", "latitude", "longitude", "roadType", "trafficLevel", "scooterSafety", "hazards", "connectedBeaches", "topCauses", "lastUpdated"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "location": { "type": "string" },
          "province": { "type": "string" },
          "region": { "type": "string" },
          "description": { "type": "string" },
          "latitude": { "type": "number" },
          "longitude": { "type": "number" },
          "roadType": { "type": "string", "description": "e.g. `highway`, `arterial`, `mountain`." },
          "trafficLevel": { "type": "string", "description": "e.g. `light`, `moderate`, `heavy`." },
          "scooterSafety": { "type": "string", "description": "e.g. `safe`, `caution`, `dangerous`." },
          "hazards": { "type": "array", "items": { "type": "string" } },
          "speedLimit": { "type": ["integer", "null"], "description": "km/h." },
          "connectedBeaches": { "type": "array", "items": { "type": "string" } },
          "accidentRate": { "type": ["number", "null"], "description": "Accidents per period, decayed 1%/day since lastUpdated." },
          "fatalityCount": { "type": ["integer", "null"] },
          "topCauses": { "type": "array", "items": { "type": "string" } },
          "dataSource": { "type": ["string", "null"] },
          "touristRiskScore": { "type": ["number", "null"], "minimum": 0, "maximum": 10 },
          "lastUpdated": { "type": "string", "format": "date-time" }
        }
      },
      "Scam": {
        "type": "object",
        "required": ["id", "type", "title", "description", "location", "latitude", "longitude", "reportCount", "severity", "verified", "dateReported"],
        "properties": {
          "id": { "type": "string" },
          "type": { "type": "string", "description": "Scam category, e.g. `taxi`, `gem`, `rental`." },
          "title": { "type": "string" },
          "description": { "type": "string" },
          "location": { "type": "string" },
          "latitude": { "type": "number" },
          "longitude": { "type": "number" },
          "reportCount": { "type": "integer" },
          "severity": { "type": "string", "enum": ["low", "medium", "high"] },
          "verified": { "type": "boolean" },
          "dateReported": { "type": "string", "format": "date-time" }
        }
      },
      "TransportRoute": {
        "type": "object",
        "required": ["id", "type", "from", "to", "fairPrice", "touristPrice", "currency", "duration", "verified"],
        "properties": {
          "id": { "type": "string" },
          "type": { "type": "string", "description": "Transport mode, e.g. `taxi`, `tuktuk`, `boat`, `train`." },
          "from": { "type": "string" },
          "to": { "type": "string" },
          "fairPrice": { "type": "integer", "description": "Fair local price in THB." },
          "touristPrice": { "type": "integer", "description": "Typical inflated tourist price in THB." },
          "currency": { "type": "string", "default": "THB" },
          "duration": { "type": "string" },
          "notes": { "type": ["string", "null"] },
          "verified": { "type": "boolean" }
        }
      },
      "NewsArticle": {
        "type": "object",
        "required": ["id", "source", "feed", "title", "description", "sourceUrl", "publishedAt", "relevanceScore", "isAlertWorthy", "createdAt"],
        "properties": {
          "id": { "type": "string" },
          "source": { "type": "string", "description": "Source slug, e.g. `bangkokpost`." },
          "feed": { "type": "string" },
          "title": { "type": "string" },
          "description": { "type": "string" },
          "aiSummary": { "type": ["string", "null"], "description": "AI-generated safety-focused summary." },
          "sourceUrl": { "type": "string", "format": "uri" },
          "urlSection": { "type": ["string", "null"] },
          "publishedAt": { "type": "string", "format": "date-time" },
          "category": { "type": ["string", "null"], "description": "AI-classified category, e.g. `crime`, `weather`, `traffic`." },
          "region": { "type": ["string", "null"], "description": "AI-classified region slug." },
          "relevanceScore": { "type": "number", "minimum": 0, "maximum": 1 },
          "isAlertWorthy": { "type": "boolean" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      }
    }
  }
}
