{
  "openapi": "3.1.0",
  "info": {
    "title": "全台酒駕、毒駕累犯公告查詢 — 公開資料 API",
    "version": "1.0.0",
    "summary": "各地方政府與公路局主動公開之酒駕、毒駕累犯公告的唯讀 JSON 介面。",
    "description": "唯讀公開資料 API，不需金鑰。所有日期欄位為台灣當地日期，fetched_at 為 ISO 8601 並明帶 +08:00 位移。name 欄位照政府原始公告輸出，未做遮罩。路徑前綴固定為 /api/v1，回應同時帶 api_version 欄位；不提供無版本別名。相容性變更（新增欄位、新增選用參數）在 v1 內進行，請以忽略未知欄位的方式解析；破壞性變更會另開 /api/v2，v1 保留。以 /api/ 為前綴的路徑套用每來源 IP 每 60 秒 600 次請求的滑動視窗限制，超出回 429。人類可讀說明見 /api-docs。",
    "license": {
      "name": "CC BY-NC-SA 4.0",
      "url": "https://creativecommons.org/licenses/by-nc-sa/4.0/"
    }
  },
  "servers": [
    { "url": "https://traffic.i23iv.cc" }
  ],
  "tags": [
    { "name": "records", "description": "公告記錄" },
    { "name": "counties", "description": "縣市清單" },
    { "name": "geo", "description": "熱點地圖座標（彙總用，不含個別記錄資訊）" }
  ],
  "paths": {
    "/api/v1/records": {
      "get": {
        "tags": ["records"],
        "operationId": "listRecords",
        "summary": "分頁列出公告記錄",
        "description": "排序 publish_date DESC, id ASC。數值參數超出範圍一律夾到邊界值，不報錯。",
        "parameters": [
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/PerPage" },
          { "$ref": "#/components/parameters/County" },
          { "$ref": "#/components/parameters/Since" },
          { "$ref": "#/components/parameters/Until" },
          { "$ref": "#/components/parameters/Q" }
        ],
        "responses": {
          "200": {
            "description": "分頁結果。快取指示為 public, max-age=300。",
            "headers": {
              "ETag": { "$ref": "#/components/headers/ETag" },
              "Cache-Control": { "$ref": "#/components/headers/CacheControl" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RecordsPage" }
              }
            }
          },
          "304": { "$ref": "#/components/responses/NotModified" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/records/{id}": {
      "get": {
        "tags": ["records"],
        "operationId": "getRecord",
        "summary": "取得單筆公告記錄",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "16 碼小寫十六進位的記錄識別碼；格式不符回 400。",
            "schema": { "type": "string", "pattern": "^[0-9a-f]{16}$" }
          }
        ],
        "responses": {
          "200": {
            "description": "單筆記錄。快取指示為 public, max-age=3600。",
            "headers": {
              "ETag": { "$ref": "#/components/headers/ETag" },
              "Cache-Control": { "$ref": "#/components/headers/CacheControl" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RecordEnvelope" }
              }
            }
          },
          "304": { "$ref": "#/components/responses/NotModified" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/counties": {
      "get": {
        "tags": ["counties"],
        "operationId": "listCounties",
        "summary": "列出已收錄縣市與各縣筆數",
        "responses": {
          "200": {
            "description": "縣市清單。快取指示為 public, max-age=3600。",
            "headers": {
              "ETag": { "$ref": "#/components/headers/ETag" },
              "Cache-Control": { "$ref": "#/components/headers/CacheControl" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CountiesResponse" }
              }
            }
          },
          "304": { "$ref": "#/components/responses/NotModified" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/latest": {
      "get": {
        "tags": ["records"],
        "operationId": "listLatest",
        "summary": "取得最新 N 筆公告記錄",
        "description": "/api/v1/records 的輕量版，資料列形狀相同。",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/County" }
        ],
        "responses": {
          "200": {
            "description": "最新 N 筆。快取指示為 public, max-age=300。",
            "headers": {
              "ETag": { "$ref": "#/components/headers/ETag" },
              "Cache-Control": { "$ref": "#/components/headers/CacheControl" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LatestResponse" }
              }
            }
          },
          "304": { "$ref": "#/components/responses/NotModified" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/geo/points.geojson": {
      "get": {
        "tags": ["geo"],
        "operationId": "getGeoPoints",
        "summary": "熱點地圖座標點（GeoJSON FeatureCollection）",
        "description": "非版本化端點，供本站 /map 頁使用，不受 /api/v1 相容性承諾約束。座標四捨五入到 3 位小數（約 100 公尺）；properties 僅 c（縣市代碼）、d（鄉鎮市區，無則 null）、ym（公告年月 YYYY-MM，無則 null）、p（座標精度），不含姓名、id、照片或個別記錄連結。無座標的記錄不輸出；無資料時回空 FeatureCollection。伺服器端快取 300 秒。",
        "parameters": [
          {
            "name": "county",
            "in": "query",
            "required": false,
            "description": "縣市代碼（如 TPE），大小寫不拘；非法值回 400",
            "schema": { "type": "string" }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "公告年月下界（含），YYYY-MM",
            "schema": { "type": "string", "pattern": "^\\d{4}-(0[1-9]|1[0-2])$" }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "公告年月上界（含），YYYY-MM；不得早於 from",
            "schema": { "type": "string", "pattern": "^\\d{4}-(0[1-9]|1[0-2])$" }
          },
          {
            "name": "precision",
            "in": "query",
            "required": false,
            "description": "逗號分隔的座標精度，只接受 point,intersection,road,district,county,none；預設 point,intersection,road",
            "schema": { "type": "string", "default": "point,intersection,road" }
          }
        ],
        "responses": {
          "200": {
            "description": "GeoJSON FeatureCollection。Content-Type 為 application/geo+json，快取指示為 public, max-age=300。",
            "headers": {
              "Cache-Control": { "$ref": "#/components/headers/CacheControl" }
            },
            "content": {
              "application/geo+json": {
                "schema": { "$ref": "#/components/schemas/GeoFeatureCollection" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/geo/districts.json": {
      "get": {
        "tags": ["geo"],
        "operationId": "getGeoDistricts",
        "summary": "熱點地圖行政區計數（依縣市 + 鄉鎮市區聚合）",
        "description": "非版本化端點，供本站 /map 頁的行政區計數圖層使用，不受 /api/v1 相容性承諾約束。只統計座標精度為 district（鄉鎮市區中心點暫補）的記錄，依縣市 + 鄉鎮市區聚合；每列僅 c（縣市代碼）、d（鄉鎮市區，無則 null）、n（筆數）、lat / lng（中心點，四捨五入到 3 位小數），不含姓名、id、照片或個別記錄連結。無資料時回 rows 空陣列。伺服器端快取 300 秒。",
        "parameters": [
          {
            "name": "county",
            "in": "query",
            "required": false,
            "description": "縣市代碼（如 TPE），大小寫不拘；非法值回 400",
            "schema": { "type": "string" }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "公告年月下界（含），YYYY-MM",
            "schema": { "type": "string", "pattern": "^\\d{4}-(0[1-9]|1[0-2])$" }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "公告年月上界（含），YYYY-MM；不得早於 from",
            "schema": { "type": "string", "pattern": "^\\d{4}-(0[1-9]|1[0-2])$" }
          }
        ],
        "responses": {
          "200": {
            "description": "行政區計數列。Content-Type 為 application/json，快取指示為 public, max-age=300。",
            "headers": {
              "Cache-Control": { "$ref": "#/components/headers/CacheControl" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/GeoDistrictsResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/geo/records": {
      "get": {
        "tags": ["geo"],
        "operationId": "getGeoRecords",
        "summary": "熱點地圖側欄記錄清單（依行政區或座標篩選）",
        "description": "非版本化端點，供本站 /map 頁點擊計數圓圈後的側欄卡片使用，不受 /api/v1 相容性承諾約束。只回有座標的記錄（geo_precision 非 none 且座標非 NULL），依公告日新到舊排序；每列僅 id、name、publish_date、location、county、district、precision，另附符合條件的總筆數 total。name 照政府原始公告輸出，未做遮罩。個別記錄頁為 /record?id=<id>。伺服器端快取 300 秒。",
        "parameters": [
          {
            "name": "county",
            "in": "query",
            "required": false,
            "description": "縣市代碼（如 TYC），大小寫不拘；非法值回 400",
            "schema": { "type": "string" }
          },
          {
            "name": "district",
            "in": "query",
            "required": false,
            "description": "鄉鎮市區名稱（如 龜山區），完全相等比對；超過 32 字回 400",
            "schema": { "type": "string", "maxLength": 32 }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "公告年月下界（含），YYYY-MM",
            "schema": { "type": "string", "pattern": "^\\d{4}-(0[1-9]|1[0-2])$" }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "公告年月上界（含），YYYY-MM；不得早於 from",
            "schema": { "type": "string", "pattern": "^\\d{4}-(0[1-9]|1[0-2])$" }
          },
          {
            "name": "lat",
            "in": "query",
            "required": false,
            "description": "緯度；須與 lng 同時提供。四捨五入到 3 位小數後與記錄座標（同樣取 3 位小數）相等比對；非法值回 400",
            "schema": { "type": "number", "minimum": -90, "maximum": 90 }
          },
          {
            "name": "lng",
            "in": "query",
            "required": false,
            "description": "經度；須與 lat 同時提供。比對方式同 lat",
            "schema": { "type": "number", "minimum": -180, "maximum": 180 }
          },
          {
            "name": "precision",
            "in": "query",
            "required": false,
            "description": "逗號分隔的座標精度，只接受 point,intersection,road,district,county（不收 none）；預設 point,intersection,road,district,county。含非白名單值或清單為空回 400",
            "schema": { "type": "string", "default": "point,intersection,road,district,county" }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "回傳筆數，夾在 1–100，預設 50；非整數視為預設值",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "略過筆數，非負整數；非法值視為 0",
            "schema": { "type": "integer", "minimum": 0, "default": 0 }
          }
        ],
        "responses": {
          "200": {
            "description": "記錄清單。Content-Type 為 application/json，快取指示為 public, max-age=300。",
            "headers": {
              "Cache-Control": { "$ref": "#/components/headers/CacheControl" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/GeoRecordsResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "Page": {
        "name": "page",
        "in": "query",
        "required": false,
        "description": "頁碼，最小 1，預設 1。",
        "schema": { "type": "integer", "minimum": 1, "default": 1 }
      },
      "PerPage": {
        "name": "per_page",
        "in": "query",
        "required": false,
        "description": "每頁筆數，夾在 10–200，預設 50。",
        "schema": { "type": "integer", "minimum": 10, "maximum": 200, "default": 50 }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "回傳筆數，夾在 1–100，預設 20。",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
      },
      "County": {
        "name": "county",
        "in": "query",
        "required": false,
        "description": "縣市代碼（大寫）；不是有效代碼時回 400。有效值見 /api/v1/counties。",
        "schema": { "type": "string", "pattern": "^[A-Z]{3}$" }
      },
      "Since": {
        "name": "since",
        "in": "query",
        "required": false,
        "description": "公告日下界（含），YYYY-MM-DD。",
        "schema": { "type": "string", "format": "date" }
      },
      "Until": {
        "name": "until",
        "in": "query",
        "required": false,
        "description": "公告日上界（含），YYYY-MM-DD。",
        "schema": { "type": "string", "format": "date" }
      },
      "Q": {
        "name": "q",
        "in": "query",
        "required": false,
        "description": "關鍵字。",
        "schema": { "type": "string" }
      }
    },
    "headers": {
      "ETag": {
        "description": "弱驗證器（形如 W/\"<hash>\"），由回應內容算出。帶回 If-None-Match 且內容未變更時回 304。",
        "schema": { "type": "string" }
      },
      "CacheControl": {
        "description": "快取指示。/api/v1/records 與 /api/v1/latest 為 public, max-age=300；/api/v1/records/{id} 與 /api/v1/counties 為 public, max-age=3600。錯誤回應為 no-store。",
        "schema": { "type": "string" }
      }
    },
    "responses": {
      "NotModified": {
        "description": "If-None-Match 與目前 ETag 相符，內容未變更；不帶 body。",
        "headers": {
          "ETag": { "$ref": "#/components/headers/ETag" },
          "Cache-Control": { "$ref": "#/components/headers/CacheControl" }
        }
      },
      "BadRequest": {
        "description": "參數語法錯誤",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "查無資料或端點不存在",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "TooManyRequests": {
        "description": "超出流量限制（/api/ 前綴：每來源 IP 每 60 秒 600 次）。body 形狀與其他錯誤不同：無 message，改帶 retry_after_sec。",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RateLimitError" } } }
      },
      "ServiceUnavailable": {
        "description": "資料庫暫時無法存取",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Record": {
        "type": "object",
        "description": "單筆公告記錄。欄位名沿用資料庫欄位名。",
        "required": ["id", "county", "county_zh", "name", "record_url"],
        "properties": {
          "id": { "type": "string", "pattern": "^[0-9a-f]{16}$", "description": "穩定識別碼" },
          "county": { "type": "string", "description": "縣市代碼（大寫）" },
          "county_zh": { "type": "string", "description": "縣市中文名" },
          "name": { "type": "string", "description": "公告所載姓名；照政府原始公告輸出，未遮罩" },
          "publish_date": { "type": ["string", "null"], "format": "date", "description": "公告日（台灣當地日期）" },
          "violation_date": { "type": ["string", "null"], "format": "date", "description": "公告揭示的違規日期（台灣當地日期）" },
          "incident_date": { "type": ["string", "null"], "format": "date", "description": "事件發生日（台灣當地日期）" },
          "location": { "type": ["string", "null"], "description": "公告所載地點原文" },
          "district": { "type": ["string", "null"], "description": "由地點推得的鄉鎮市區" },
          "announcement": { "type": ["string", "null"], "description": "公告事由原文，含換行" },
          "photo_path": { "type": ["string", "null"], "description": "本站保存的公告截圖相對路徑" },
          "source_url": { "type": ["string", "null"], "description": "政府原始公告頁網址" },
          "source_file": { "type": ["string", "null"], "description": "本站抓取批次的來源檔名" },
          "fetched_at": { "type": ["string", "null"], "description": "本站抓取時間，ISO 8601 帶 +08:00" },
          "geo_lat": { "type": ["number", "null"], "description": "緯度" },
          "geo_lng": { "type": ["number", "null"], "description": "經度" },
          "geo_precision": { "type": ["string", "null"], "description": "座標精度層級" },
          "record_url": { "type": "string", "description": "本站對應的單筆頁面網址" }
        }
      },
      "RecordsFilters": {
        "type": "object",
        "properties": {
          "county": { "type": ["string", "null"] },
          "since": { "type": ["string", "null"], "format": "date" },
          "until": { "type": ["string", "null"], "format": "date" },
          "q": { "type": ["string", "null"] }
        }
      },
      "LatestFilters": {
        "type": "object",
        "properties": {
          "county": { "type": ["string", "null"] }
        }
      },
      "RecordsPage": {
        "type": "object",
        "required": ["api_version", "page", "per_page", "total", "total_pages", "filters", "rows"],
        "properties": {
          "api_version": { "type": "string", "const": "v1" },
          "page": { "type": "integer" },
          "per_page": { "type": "integer" },
          "total": { "type": "integer" },
          "total_pages": { "type": "integer" },
          "filters": { "$ref": "#/components/schemas/RecordsFilters" },
          "rows": { "type": "array", "items": { "$ref": "#/components/schemas/Record" } }
        }
      },
      "LatestResponse": {
        "type": "object",
        "required": ["api_version", "limit", "count", "filters", "rows"],
        "properties": {
          "api_version": { "type": "string", "const": "v1" },
          "limit": { "type": "integer" },
          "count": { "type": "integer" },
          "filters": { "$ref": "#/components/schemas/LatestFilters" },
          "rows": { "type": "array", "items": { "$ref": "#/components/schemas/Record" } }
        }
      },
      "RecordEnvelope": {
        "type": "object",
        "required": ["api_version", "record"],
        "properties": {
          "api_version": { "type": "string", "const": "v1" },
          "record": { "$ref": "#/components/schemas/Record" }
        }
      },
      "County": {
        "type": "object",
        "required": ["code", "zh", "region", "total_records"],
        "properties": {
          "code": { "type": "string", "description": "縣市代碼（大寫）" },
          "zh": { "type": "string", "description": "縣市中文名" },
          "region": { "type": "string", "description": "區域分組（north / central / south / east / islands）" },
          "total_records": { "type": "integer", "description": "該縣收錄筆數" },
          "last_publish_date": { "type": ["string", "null"], "format": "date", "description": "該縣最新公告日" },
          "rss_url": { "type": "string", "description": "該縣 RSS feed 網址" }
        }
      },
      "CountiesResponse": {
        "type": "object",
        "required": ["api_version", "counties"],
        "properties": {
          "api_version": { "type": "string", "const": "v1" },
          "counties": { "type": "array", "items": { "$ref": "#/components/schemas/County" } }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error", "message"],
        "properties": {
          "error": { "type": "string", "description": "錯誤代碼，例 invalid_parameter / not_found / database_unavailable" },
          "message": { "type": "string", "description": "人類可讀的錯誤說明" }
        }
      },
      "RateLimitError": {
        "type": "object",
        "description": "429 專用的錯誤形狀；與 Error 不同，不帶 message。",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "const": "rate_limit_exceeded", "description": "錯誤代碼，固定為 rate_limit_exceeded" },
          "retry_after_sec": { "type": "integer", "description": "建議等待秒數，與 Retry-After 標頭同值" }
        }
      },
      "GeoFeatureCollection": {
        "type": "object",
        "required": ["type", "features"],
        "additionalProperties": false,
        "properties": {
          "type": { "const": "FeatureCollection" },
          "features": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["type", "geometry", "properties"],
              "additionalProperties": false,
              "properties": {
                "type": { "const": "Feature" },
                "geometry": {
                  "type": "object",
                  "required": ["type", "coordinates"],
                  "additionalProperties": false,
                  "properties": {
                    "type": { "const": "Point" },
                    "coordinates": {
                      "type": "array",
                      "description": "[經度, 緯度]，WGS84，3 位小數",
                      "items": { "type": "number" },
                      "minItems": 2,
                      "maxItems": 2
                    }
                  }
                },
                "properties": {
                  "type": "object",
                  "required": ["c", "d", "ym", "p"],
                  "additionalProperties": false,
                  "properties": {
                    "c": { "type": "string", "description": "縣市代碼" },
                    "d": { "type": ["string", "null"], "description": "鄉鎮市區" },
                    "ym": { "type": ["string", "null"], "description": "公告年月 YYYY-MM" },
                    "p": { "type": "string", "enum": ["point", "intersection", "road", "district", "county", "none"], "description": "座標精度" }
                  }
                }
              }
            }
          }
        }
      },
      "GeoDistrictsResponse": {
        "type": "object",
        "required": ["rows"],
        "additionalProperties": false,
        "properties": {
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["c", "d", "n", "lat", "lng"],
              "additionalProperties": false,
              "properties": {
                "c": { "type": "string", "description": "縣市代碼" },
                "d": { "type": ["string", "null"], "description": "鄉鎮市區" },
                "n": { "type": "integer", "minimum": 1, "description": "筆數" },
                "lat": { "type": "number", "description": "緯度，WGS84，3 位小數" },
                "lng": { "type": "number", "description": "經度，WGS84，3 位小數" }
              }
            }
          }
        }
      },
      "GeoRecordsResponse": {
        "type": "object",
        "required": ["rows", "total"],
        "additionalProperties": false,
        "properties": {
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["id", "name", "publish_date", "location", "county", "district", "precision"],
              "additionalProperties": false,
              "properties": {
                "id": { "type": "string", "description": "記錄 id；記錄頁為 /record?id=<id>" },
                "name": { "type": ["string", "null"], "description": "姓名，照政府原始公告輸出，未做遮罩" },
                "publish_date": { "type": ["string", "null"], "format": "date", "description": "公告日期（YYYY-MM-DD，台灣當地日期）" },
                "location": { "type": ["string", "null"], "description": "違規地點（公告原文）" },
                "county": { "type": "string", "description": "縣市代碼" },
                "district": { "type": ["string", "null"], "description": "鄉鎮市區" },
                "precision": {
                  "type": "string",
                  "enum": ["point", "intersection", "road", "district", "county"],
                  "description": "座標精度等級"
                }
              }
            }
          },
          "total": { "type": "integer", "minimum": 0, "description": "符合條件的總筆數（不受 limit / offset 影響）" }
        }
      }
    }
  }
}
