{
  "openapi": "3.1.0",
  "info": {
    "title": "Address to ZIP API",
    "version": "1.0.0",
    "summary": "日本の住所と郵便番号を相互変換するAPI",
    "description": "住所文字列から郵便番号を検索し、郵便番号から住所を逆引きします。データの正本は日本郵便が公開する公式の郵便番号データで、毎月の更新に追従します。認証は `x-api-key` ヘッダーによるAPIキー方式です。",
    "termsOfService": "https://addresstozip.jp/legal/terms",
    "contact": {
      "name": "Address to ZIP サポート",
      "url": "https://addresstozip.jp/contact",
      "email": "support@addresstozip.jp"
    }
  },
  "servers": [
    { "url": "https://addresstozip.jp", "description": "本番" }
  ],
  "externalDocs": {
    "description": "APIドキュメント",
    "url": "https://addresstozip.jp/docs"
  },
  "tags": [
    { "name": "lookup", "description": "住所⇄郵便番号の変換" },
    { "name": "status", "description": "稼働状態" }
  ],
  "paths": {
    "/api/v1/search": {
      "get": {
        "operationId": "searchAddress",
        "summary": "住所→郵便番号 検索",
        "description": "住所文字列(番地・建物名付きも可)から郵便番号候補を検索します。町名まで自動解釈します。ローマ字・英語表記の住所(例: `Marunouchi, Chiyoda-ku, Tokyo`)でも検索できます。",
        "tags": ["lookup"],
        "security": [{ "apiKeyAuth": [] }],
        "parameters": [
          {
            "name": "address",
            "in": "query",
            "required": true,
            "description": "検索する住所(例: 東京都千代田区丸の内)",
            "schema": { "type": "string", "minLength": 1, "maxLength": 256 }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "返却件数の上限",
            "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 }
          },
          { "$ref": "#/components/parameters/Lang" }
        ],
        "responses": {
          "200": {
            "description": "検索結果",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchResult" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidParameter" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/lookup": {
      "get": {
        "operationId": "lookupZipcode",
        "summary": "郵便番号→住所 逆引き",
        "description": "郵便番号から住所を逆引きします。`100-0005` / `1000005` 形式に対応します。",
        "tags": ["lookup"],
        "security": [{ "apiKeyAuth": [] }],
        "parameters": [
          {
            "name": "zipcode",
            "in": "query",
            "required": true,
            "description": "郵便番号(例: 100-0005 または 1000005)",
            "schema": { "type": "string", "pattern": "^[0-9]{3}-?[0-9]{4}$" }
          },
          { "$ref": "#/components/parameters/Lang" }
        ],
        "responses": {
          "200": {
            "description": "逆引き結果",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchResult" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidParameter" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "operationId": "health",
        "summary": "稼働状態",
        "description": "データ件数・更新日・データストア到達性を返します。認証不要。DBに到達できない場合は503 degraded。",
        "tags": ["status"],
        "security": [],
        "responses": {
          "200": {
            "description": "正常",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Health" }
              }
            }
          },
          "503": {
            "description": "縮退(データストア到達不可)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Health" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "Lang": {
        "name": "lang",
        "in": "query",
        "required": false,
        "description": "エラーメッセージの言語。`en` で英語。未指定時は `Accept-Language` ヘッダーを参照します(エラーコードは言語に依らず不変)。",
        "schema": { "type": "string", "enum": ["ja", "en"] }
      }
    },
    "securitySchemes": {
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "契約時に発行されるAPIキー(`azk_live_…`)。"
      }
    },
    "schemas": {
      "PostalRecord": {
        "type": "object",
        "properties": {
          "zipcode": { "type": "string", "example": "100-0005" },
          "prefecture": { "type": "string", "example": "東京都" },
          "city": { "type": "string", "example": "千代田区" },
          "town": { "type": "string", "example": "丸の内" },
          "prefecture_romaji": { "type": "string", "example": "Tokyo", "description": "都道府県のローマ字表記(日本郵便ローマ字データ準拠)" },
          "city_romaji": { "type": "string", "example": "Chiyoda-ku", "description": "市区町村のローマ字表記" },
          "town_romaji": { "type": "string", "example": "Marunochi", "description": "町域のローマ字表記" },
          "romaji": { "type": "string", "example": "Marunochi, Chiyoda-ku, Tokyo", "description": "英語圏の宛名順(町域, 市区町村, 都道府県)で合成したローマ字住所" }
        },
        "required": ["zipcode", "prefecture", "city", "town", "prefecture_romaji", "city_romaji", "town_romaji", "romaji"]
      },
      "SearchResult": {
        "type": "object",
        "properties": {
          "query": { "type": "string", "description": "正規化された検索クエリ" },
          "count": { "type": "integer", "description": "返却件数" },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/PostalRecord" }
          }
        },
        "required": ["query", "count", "results"]
      },
      "Health": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "enum": ["ok", "degraded"] },
          "service": { "type": "string", "example": "address-to-zip" },
          "db": { "type": "string", "enum": ["up", "down"] },
          "billing": { "type": "string", "enum": ["up", "unavailable"] },
          "data": {
            "type": "object",
            "properties": {
              "count": { "type": "integer" },
              "updatedAt": { "type": "string" }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "description": "エラーコード" },
          "message": { "type": "string", "description": "人間向けの説明" }
        },
        "required": ["error"]
      }
    },
    "responses": {
      "InvalidParameter": {
        "description": "パラメータ不正",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "APIキー未指定または無効",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "レート制限超過。`retry-after` ヘッダーを参照。",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ServiceUnavailable": {
        "description": "データストア到達不可または過負荷。`retry-after` ヘッダーを参照。",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
