{
  "openapi": "3.1.0",
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "info": {
    "title": "키햐 상품 API",
    "version": "1.0.0",
    "summary": "승인된 파트너 프로젝트를 위한 상품·가격·링크 조회 API",
    "description": "키햐 상품 정보, 공개 판매가, 판매 상태와 구매 링크를 조회하는 API입니다. 상품 목록·단건 조회, CSV 내보내기, 변경 내역과 제공 정책 조회를 지원합니다. 프로젝트와 상품 범위 승인 후 API 키를 발급해 이용합니다. 예시의 상품번호와 가격은 가상 데이터입니다.\n\n서버 간 HTTPS 요청에 Authorization: Bearer YOUR_API_KEY 헤더를 사용합니다. 키는 서버의 비밀 설정에 보관하고 공개 코드·앱·URL·브라우저 저장소·로그에 넣지 마세요. 기본 한도는 프로젝트 합산 분당 60회, 하루 10,000회이며 일일 한도는 UTC 00:00에 초기화됩니다. CSV에는 시간당 60회 한도도 적용합니다. 실제 프로젝트 한도는 정책 응답에서 확인하세요.\n\n상품은 1시간마다 수집을 권장하며 최소 하루 1회 전체 목록을 갱신합니다. 제공 정책은 최대 5분 간격으로 확인하고, 15분 동안 확인하지 못하면 표시를 중단합니다. 시각은 ISO 8601 형식입니다. 상품의 valid_until을 따르고 키햐 출처·링크·용량·판매 조건과 가격·재고 변동 안내를 함께 표시하세요.\n\nv1은 기존 필드 의미를 유지합니다. 필드 제거·타입·가격 기준 등 호환되지 않는 변경은 새 버전으로 구분하고, 폐지는 원칙적으로 90일 전에 공지합니다. 이용 조건은 /terms에서 확인할 수 있습니다."
  },
  "servers": [
    {
      "url": "/",
      "description": "현재 키햐 Developers 원본 주소. 운영에서는 HTTPS를 사용합니다."
    }
  ],
  "tags": [
    {
      "name": "Catalog",
      "description": "승인된 상품·판매 조건과 동기화"
    },
    {
      "name": "Policy",
      "description": "프로젝트 데이터 제공·표시 정책"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "paths": {
    "/api/v1/products": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "승인된 상품과 판매 조건 목록",
        "description": "catalog:read 권한이 필요합니다. 전체 수집은 ids 없이 시작하고 같은 snapshot_id의 모든 페이지를 meta.complete=true까지 받습니다. 페이지 크기는 data와 removed를 합산합니다. 모든 페이지가 성공한 전체(full) 목록에서만 기존 카탈로그를 교체합니다. 부분(selected) 조회의 누락 항목으로 전체 상품을 삭제하지 마세요. 품절은 display_allowed=false이며 공개 승인 철회는 이전 제공 식별자만 removed로 제공합니다. 같은 커서라도 유효 기간이 지나면 반환 시 표시 여부가 false로 바뀔 수 있습니다.",
        "operationId": "listProducts",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "responses": {
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "409": {
            "$ref": "#/components/responses/SyncRestartRequired"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableInput"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "200": {
            "description": "정상 응답. 공유 캐시에 보관하지 마세요.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-DailyLimit-Remaining": {
                "$ref": "#/components/headers/DailyLimitRemaining"
              },
              "X-DailyLimit-Reset": {
                "$ref": "#/components/headers/DailyLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogResponse"
                },
                "example": {
                  "data": [
                    {
                      "product_id": "9999990001",
                      "offer_id": "9999990001:base",
                      "name": "예시 준마이 사케 720ml",
                      "volume_ml": 720,
                      "pack_quantity": 1,
                      "price": {
                        "amount": 32000,
                        "currency": "KRW",
                        "basis": "public_sale"
                      },
                      "canonical_url": "https://m.kihya.com/goods/goods_view.php?goodsNo=9999990001",
                      "referral_url": "https://m.kihya.com/goods/goods_view.php?goodsNo=9999990001&utm_source=example&utm_medium=referral&utm_campaign=catalog&utm_content=9999990001",
                      "availability": "in_stock",
                      "display_allowed": true,
                      "fulfillment": {
                        "mode": "store_pickup",
                        "label": "온라인 주문 · 매장 픽업",
                        "region_label": "상품 페이지에서 가능 매장 확인"
                      },
                      "adult_required": true,
                      "source_updated_at": null,
                      "verified_at": "2026-09-08T10:00:00+09:00",
                      "valid_until": "2026-09-10T10:00:00+09:00"
                    }
                  ],
                  "removed": [],
                  "meta": {
                    "snapshot_id": "00000000000000000000000000000001",
                    "snapshot_created_at": "2026-09-08T10:00:00+09:00",
                    "total_records": 1,
                    "next_cursor": null,
                    "complete": true,
                    "sync_scope": "full",
                    "policy_version": 1,
                    "recommended_sync_interval_seconds": 3600,
                    "notice": "가격과 재고는 변동될 수 있으며, 최종 판매 조건은 키햐 상품 페이지에서 확인해 주세요."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "ids",
            "in": "query",
            "required": false,
            "description": "상품번호 최대 100개를 쉼표로 구분합니다. 서버가 중복을 제거하며 승인 범위만 조회합니다. 커서 요청에서도 첫 요청과 같은 ids를 유지합니다.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/ProductId"
              },
              "minItems": 1,
              "maxItems": 100
            },
            "example": [
              "9999990001"
            ]
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "한 페이지의 최대 레코드 수. 목록은 data와 removed를 합산합니다. 목록 커서를 사용할 때 첫 요청과 같은 값을 유지합니다.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "이전 응답의 next_cursor. 목록 스냅샷은 30분, 변경 커서는 7일간 유효합니다. 각 종류의 커서는 서로 바꾸어 사용할 수 없습니다.",
            "schema": {
              "$ref": "#/components/schemas/Cursor"
            }
          }
        ]
      }
    },
    "/api/v1/products/{product_id}": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "단일 상품의 판매 조건",
        "description": "catalog:read 권한이 필요합니다. data 배열과 removed를 포함한 목록과 동일한 응답 형식입니다. 승인된 해당 상품의 판매 조건 또는 이전 제공 식별자의 제거 정보를 반환합니다. 조회 가능한 항목이 전혀 없으면 404입니다. meta.next_cursor가 있는 경우 /products에 ids=product_id, limit=100과 해당 커서를 전달해 나머지를 수집합니다. 이 경로에 query cursor를 전달해 단건 페이지를 넘기지 마세요.",
        "operationId": "getProduct",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "responses": {
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "409": {
            "$ref": "#/components/responses/SyncRestartRequired"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableInput"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "200": {
            "description": "정상 응답. 공유 캐시에 보관하지 마세요.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-DailyLimit-Remaining": {
                "$ref": "#/components/headers/DailyLimitRemaining"
              },
              "X-DailyLimit-Reset": {
                "$ref": "#/components/headers/DailyLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogResponse"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "product_id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ProductId"
            }
          }
        ]
      }
    },
    "/api/v1/catalog.csv": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "전체 카탈로그 CSV",
        "description": "catalog:export 권한이 필요합니다. offer_id마다 한 행이며 열 순서는 고정입니다. action=upsert는 상품 필드가 있는 갱신 행, action=remove는 product_id·offer_id·action만 있는 삭제 행입니다. UTF-8 CSV의 쉼표·따옴표·줄바꿈을 정규 파서로 해석하고 상품번호는 문자열로 보존합니다. 수식 시작 문자(=,+,-,@,탭,CR)로 시작하는 텍스트에는 작은따옴표를 앞에 붙입니다. 다운로드 성공·X-Sync-Complete:true를 확인한 뒤 전체 교체합니다. 프로젝트 공통 한도 외 시간당 60회 CSV 한도가 있습니다. 파일 생성 실패나 연결 중단을 빈 상품 목록으로 처리하지 마세요.",
        "operationId": "exportCatalog",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-required-scopes": [
          "catalog:export"
        ],
        "responses": {
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "409": {
            "$ref": "#/components/responses/SyncRestartRequired"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableInput"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "200": {
            "description": "UTF-8 CSV 전체 파일. JSON API와 동일한 승인 범위·판매 조건·기준 시각을 사용합니다. 실패하면 JSON 오류이며 정상 CSV를 반환하지 않습니다.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-DailyLimit-Remaining": {
                "$ref": "#/components/headers/DailyLimitRemaining"
              },
              "X-DailyLimit-Reset": {
                "$ref": "#/components/headers/DailyLimitReset"
              },
              "Content-Disposition": {
                "$ref": "#/components/headers/ContentDisposition"
              },
              "X-Snapshot-Id": {
                "$ref": "#/components/headers/SnapshotId"
              },
              "X-Total-Records": {
                "$ref": "#/components/headers/TotalRecords"
              },
              "X-Sync-Complete": {
                "$ref": "#/components/headers/SyncComplete"
              }
            },
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "product_id,offer_id,name,volume_ml,pack_quantity,price_krw,currency,canonical_url,referral_url,availability,display_allowed,fulfillment_mode,fulfillment_label,region_label,verified_at,valid_until,action\n9999990001,9999990001:base,예시 준마이 사케 720ml,720,1,32000,KRW,https://m.kihya.com/goods/goods_view.php?goodsNo=9999990001,https://m.kihya.com/goods/goods_view.php?goodsNo=9999990001&utm_source=example&utm_medium=referral&utm_campaign=catalog&utm_content=9999990001,in_stock,true,store_pickup,온라인 주문 · 매장 픽업,상품 페이지에서 가능 매장 확인,2026-09-08T10:00:00+09:00,2026-09-10T10:00:00+09:00,upsert\n"
              }
            }
          }
        }
      }
    },
    "/api/v1/policy": {
      "get": {
        "tags": [
          "Policy"
        ],
        "summary": "프로젝트 제공·표시 정책",
        "description": "유효한 해당 프로젝트 키가 있으면 read/export 조합과 무관하게 조회합니다. 상품 수집과 별개로 최대 5분 간격으로 확인하고, 15분 동안 유효한 정책을 확인하지 못하면 전체 표시를 보류합니다. 미승인 프로젝트는 403 PROJECT_NOT_APPROVED, 중단 프로젝트는 403 PARTNER_SUSPENDED입니다. 키 폐기로 파트너가 저장한 데이터가 원격 삭제되지는 않습니다.",
        "operationId": "getProjectPolicy",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-required-scopes": [],
        "responses": {
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "409": {
            "$ref": "#/components/responses/SyncRestartRequired"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableInput"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "200": {
            "description": "정상 응답. 공유 캐시에 보관하지 마세요.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-DailyLimit-Remaining": {
                "$ref": "#/components/headers/DailyLimitRemaining"
              },
              "X-DailyLimit-Reset": {
                "$ref": "#/components/headers/DailyLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PolicyResponse"
                },
                "example": {
                  "data": {
                    "status": "active",
                    "display_allowed": true,
                    "recommended_sync_interval_seconds": 3600,
                    "max_sync_interval_seconds": 86400,
                    "policy_refresh_interval_seconds": 300,
                    "policy_max_unconfirmed_seconds": 900,
                    "max_display_age_seconds": 172800,
                    "price_basis": "public_sale",
                    "currency": "KRW",
                    "notice": "가격과 재고는 변동될 수 있으며, 최종 판매 조건은 키햐 상품 페이지에서 확인해 주세요.",
                    "rate_limits": {
                      "minute": 60,
                      "daily": 10000,
                      "daily_reset_timezone": "UTC",
                      "csv_per_hour": 60
                    }
                  },
                  "meta": {
                    "policy_version": 1,
                    "server_time": "2026-09-08T10:00:00+09:00"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/changes": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "상품 변경·삭제 내역",
        "description": "catalog:read 권한이 필요합니다. 초기 cursor 없는 요청에서 시작 커서를 먼저 저장하고, 전체 상품 동기화를 완주한 다음 저장한 변경 커서를 적용합니다. initialization_required=true의 빈 data는 정상 초기화 안내이며 상품 삭제가 아닙니다. has_more=true면 다음 페이지를 계속 받고, false여도 next_cursor를 다음 동기화용으로 보관합니다. 변경 이벤트는 현재 공개 가능 상태에 맞춰 최신 upsert 또는 remove로 반환됩니다. 과거 가격이나 철회된 상품의 payload는 제공하지 않습니다. verified_at·valid_until 갱신만으로도 upsert가 생길 수 있습니다. 커서는 매 응답에서 7일 유효 기간으로 갱신됩니다. 정책 변경·만료 시 409를 받고 초기 커서와 전체 수집을 다시 시작합니다. 증분을 쓰더라도 최소 하루 1회 전체 수집을 유지합니다.",
        "operationId": "listChanges",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "responses": {
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "409": {
            "$ref": "#/components/responses/SyncRestartRequired"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableInput"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "200": {
            "description": "정상 응답. 공유 캐시에 보관하지 마세요.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-DailyLimit-Remaining": {
                "$ref": "#/components/headers/DailyLimitRemaining"
              },
              "X-DailyLimit-Reset": {
                "$ref": "#/components/headers/DailyLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesResponse"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "한 페이지의 최대 레코드 수. 목록은 data와 removed를 합산합니다. 목록 커서를 사용할 때 첫 요청과 같은 값을 유지합니다.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "이전 응답의 next_cursor. 목록 스냅샷은 30분, 변경 커서는 7일간 유효합니다. 각 종류의 커서는 서로 바꾸어 사용할 수 없습니다.",
            "schema": {
              "$ref": "#/components/schemas/Cursor"
            }
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "KIHYA personal API key",
        "description": "Authorization: Bearer YOUR_API_KEY. 계정 세션과 별개인 프로젝트 API 키. scope는 HTTP bearer가 아닌 키 권한으로 서버가 확인하며 각 operation의 x-required-scopes를 참고합니다."
      }
    },
    "schemas": {
      "ProductId": {
        "type": "string",
        "description": "키햐 상품번호. 앞자리 0 없는 1~10자리 문자열로 보관합니다.",
        "pattern": "^[1-9][0-9]{0,9}$",
        "maxLength": 10,
        "examples": [
          "9999990001"
        ]
      },
      "OfferId": {
        "type": "string",
        "description": "상품번호와 판매 조건을 구분하는 안정적인 식별자. 기본 상품은 {product_id}:base, 옵션은 {product_id}:option:{option_id}입니다.",
        "pattern": "^[1-9][0-9]{0,9}:(base|option:[1-9][0-9]{0,9})$",
        "examples": [
          "9999990001:base"
        ]
      },
      "Cursor": {
        "type": "string",
        "description": "서버가 발급한 불투명한 커서. 내용을 해석·수정하지 말고 URL 인코딩해 전달합니다.",
        "maxLength": 8000
      },
      "Price": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "integer",
            "description": "개인 쿠폰·등급·포인트 사용과 별도 배송비를 제외한 해당 판매 단위의 일반 공개 판매가입니다. 공개 즉시 할인과 필수 옵션가를 포함합니다.",
            "minimum": 1,
            "maximum": 1000000000,
            "examples": [
              32000
            ]
          },
          "currency": {
            "type": "string",
            "enum": [
              "KRW"
            ]
          },
          "basis": {
            "type": "string",
            "enum": [
              "public_sale"
            ]
          }
        },
        "required": [
          "amount",
          "currency",
          "basis"
        ]
      },
      "Fulfillment": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "description": "수령 방식 코드. store_pickup 등 기존 코드 외 새 코드가 추가될 수 있습니다.",
            "maxLength": 40,
            "examples": [
              "store_pickup"
            ]
          },
          "label": {
            "type": "string",
            "description": "실제 수령 방식의 공개 설명입니다.",
            "maxLength": 100,
            "examples": [
              "온라인 주문 · 매장 픽업"
            ]
          },
          "region_label": {
            "type": "string",
            "description": "공개 가능한 수령 지역 설명입니다. 내부 도매사 식별자는 제공하지 않습니다.",
            "maxLength": 100,
            "examples": [
              "상품 페이지에서 가능 매장 확인"
            ]
          }
        },
        "required": [
          "mode",
          "label",
          "region_label"
        ]
      },
      "Offer": {
        "type": "object",
        "properties": {
          "product_id": {
            "$ref": "#/components/schemas/ProductId"
          },
          "offer_id": {
            "$ref": "#/components/schemas/OfferId"
          },
          "name": {
            "type": "string",
            "description": "용량·세트·지역 의미를 유지한 공개 상품명.",
            "minLength": 1,
            "maxLength": 250,
            "examples": [
              "예시 준마이 사케 720ml"
            ]
          },
          "volume_ml": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "description": "검증된 한 병의 용량(ml). 단위·값이 미확인되면 null입니다.",
            "examples": [
              720
            ]
          },
          "pack_quantity": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "description": "해당 판매 단위의 병 수. 확인되지 않으면 null입니다.",
            "examples": [
              1
            ]
          },
          "price": {
            "$ref": "#/components/schemas/Price"
          },
          "canonical_url": {
            "type": "string",
            "description": "UTM을 포함하지 않는 HTTPS 키햐 기본 상품 주소.",
            "format": "uri",
            "pattern": "^https://m\\.kihya\\.com/goods/goods_view\\.php\\?goodsNo=[1-9][0-9]{0,9}$",
            "examples": [
              "https://m.kihya.com/goods/goods_view.php?goodsNo=9999990001"
            ]
          },
          "referral_url": {
            "type": "string",
            "description": "승인된 프로젝트의 utm_source, utm_medium, utm_campaign, 상품번호 utm_content를 적용한 링크. 외부 표시의 연결 버튼에 사용합니다.",
            "format": "uri",
            "examples": [
              "https://m.kihya.com/goods/goods_view.php?goodsNo=9999990001&utm_source=example&utm_medium=referral&utm_campaign=catalog&utm_content=9999990001"
            ]
          },
          "availability": {
            "type": "string",
            "description": "실제 매장별 재고 수량을 뜻하지 않습니다. 유효 기간이 지난 응답은 unknown으로 내려갈 수 있습니다.",
            "enum": [
              "in_stock",
              "out_of_stock",
              "discontinued",
              "unknown"
            ]
          },
          "display_allowed": {
            "type": "boolean",
            "description": "외부 가격·판매처 영역 표시 허용 여부. false이거나 valid_until이 지났으면 비표시합니다."
          },
          "fulfillment": {
            "$ref": "#/components/schemas/Fulfillment"
          },
          "adult_required": {
            "type": "boolean",
            "description": "성인 관련 안내 필요 여부. API 권한은 상품 구매·성인 인증 권한을 부여하지 않습니다."
          },
          "source_updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "확인 가능한 원천 변경 시각. 없으면 null이며 증분 동기화 기준으로 단독 사용하지 않습니다."
          },
          "verified_at": {
            "type": "string",
            "format": "date-time",
            "description": "가격·상태를 마지막으로 실제 검증한 시각. 캐시 재응답으로 새로 찍지 않습니다."
          },
          "valid_until": {
            "type": "string",
            "format": "date-time",
            "description": "외부 표시 유효 시각. 마지막 실제 검증 이후 최대 48시간이며 원천 조건에 따라 더 짧을 수 있습니다."
          }
        },
        "required": [
          "product_id",
          "offer_id",
          "name",
          "volume_ml",
          "pack_quantity",
          "price",
          "canonical_url",
          "referral_url",
          "availability",
          "display_allowed",
          "fulfillment",
          "adult_required",
          "source_updated_at",
          "verified_at",
          "valid_until"
        ]
      },
      "RemovedOffer": {
        "type": "object",
        "properties": {
          "offer_id": {
            "$ref": "#/components/schemas/OfferId"
          },
          "product_id": {
            "$ref": "#/components/schemas/ProductId"
          },
          "removed_at": {
            "type": "string",
            "format": "date-time"
          },
          "reason": {
            "type": "string",
            "description": "삭제·범위 철회 등 제공 중지 사유. 해석하지 않아도 해당 식별자는 제거해야 합니다.",
            "examples": [
              "unavailable"
            ]
          }
        },
        "required": [
          "offer_id",
          "product_id",
          "removed_at",
          "reason"
        ]
      },
      "CatalogMeta": {
        "type": "object",
        "properties": {
          "snapshot_id": {
            "type": "string",
            "description": "동일 수집의 스냅샷 식별자.",
            "pattern": "^[a-f0-9]{32}$"
          },
          "snapshot_created_at": {
            "type": "string",
            "format": "date-time"
          },
          "total_records": {
            "type": "integer",
            "description": "data와 removed를 합친 전체 스냅샷 레코드 수입니다.",
            "minimum": 0
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 8000,
            "description": "다음 페이지 커서. null이면 수집이 완료되었습니다."
          },
          "complete": {
            "type": "boolean",
            "description": "현재 응답을 포함해 스냅샷 전체를 수집했는지 나타냅니다."
          },
          "sync_scope": {
            "type": "string",
            "description": "full인 정상 완주 결과만 전체 카탈로그 교체에 사용합니다. selected는 ids로 제한한 부분 조회입니다.",
            "enum": [
              "full",
              "selected"
            ]
          },
          "policy_version": {
            "type": "integer",
            "minimum": 1
          },
          "recommended_sync_interval_seconds": {
            "type": "integer",
            "minimum": 1,
            "examples": [
              3600
            ]
          },
          "notice": {
            "type": "string",
            "examples": [
              "가격과 재고는 변동될 수 있으며, 최종 판매 조건은 키햐 상품 페이지에서 확인해 주세요."
            ]
          }
        },
        "required": [
          "snapshot_id",
          "snapshot_created_at",
          "total_records",
          "next_cursor",
          "complete",
          "sync_scope",
          "policy_version",
          "recommended_sync_interval_seconds",
          "notice"
        ]
      },
      "CatalogResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Offer"
            }
          },
          "removed": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RemovedOffer"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/CatalogMeta"
          }
        },
        "required": [
          "data",
          "removed",
          "meta"
        ]
      },
      "Policy": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "description": "정상 제공 중이면 active. 중단·미승인 프로젝트는 정상 정책 대신 403 JSON 오류를 반환합니다.",
            "enum": [
              "active"
            ]
          },
          "display_allowed": {
            "type": "boolean",
            "const": true
          },
          "recommended_sync_interval_seconds": {
            "type": "integer",
            "description": "권장 상품 갱신 주기.",
            "const": 3600
          },
          "max_sync_interval_seconds": {
            "type": "integer",
            "description": "전체 동기화 간격의 상한. 최소 하루 1회 전체 동기화해 주세요.",
            "const": 86400
          },
          "policy_refresh_interval_seconds": {
            "type": "integer",
            "description": "가격 수집과 별개인 제공 정책 확인 간격의 상한.",
            "const": 300
          },
          "policy_max_unconfirmed_seconds": {
            "type": "integer",
            "description": "이 시간 동안 유효 정책을 확인하지 못하면 전체 표시를 보류합니다.",
            "const": 900
          },
          "max_display_age_seconds": {
            "type": "integer",
            "description": "마지막 실제 검증 후 최대 표시 가능 기간. 상품 valid_until이 더 빠르면 그 시각을 따릅니다.",
            "minimum": 1,
            "examples": [
              172800
            ]
          },
          "price_basis": {
            "type": "string",
            "const": "public_sale"
          },
          "currency": {
            "type": "string",
            "const": "KRW"
          },
          "notice": {
            "type": "string",
            "examples": [
              "가격과 재고는 변동될 수 있으며, 최종 판매 조건은 키햐 상품 페이지에서 확인해 주세요."
            ]
          },
          "rate_limits": {
            "type": "object",
            "required": [
              "minute",
              "daily",
              "daily_reset_timezone",
              "csv_per_hour"
            ],
            "properties": {
              "minute": {
                "type": "integer",
                "minimum": 1,
                "maximum": 1000,
                "description": "이 프로젝트의 모든 키가 공유하는 분당 요청 한도.",
                "examples": [
                  60
                ]
              },
              "daily": {
                "type": "integer",
                "minimum": 1,
                "maximum": 1000000,
                "description": "UTC 날짜 기준 프로젝트 합산 일일 요청 한도.",
                "examples": [
                  10000
                ]
              },
              "daily_reset_timezone": {
                "type": "string",
                "const": "UTC",
                "description": "매일 UTC 00:00, 한국 시간 09:00에 일일 한도를 초기화합니다."
              },
              "csv_per_hour": {
                "type": "integer",
                "const": 60,
                "description": "CSV 별도 시간당 요청 한도. 일반 분·일 한도에도 포함됩니다."
              }
            }
          }
        },
        "required": [
          "status",
          "display_allowed",
          "recommended_sync_interval_seconds",
          "max_sync_interval_seconds",
          "policy_refresh_interval_seconds",
          "policy_max_unconfirmed_seconds",
          "max_display_age_seconds",
          "price_basis",
          "currency",
          "notice",
          "rate_limits"
        ]
      },
      "PolicyResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Policy"
          },
          "meta": {
            "type": "object",
            "properties": {
              "policy_version": {
                "type": "integer",
                "minimum": 1
              },
              "server_time": {
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "policy_version",
              "server_time"
            ]
          }
        },
        "required": [
          "data",
          "meta"
        ]
      },
      "UpsertChange": {
        "type": "object",
        "properties": {
          "sequence": {
            "type": "integer",
            "description": "프로젝트의 변경 순번. 유일한 순차 정수이며 상품 ID가 아닙니다.",
            "minimum": 1
          },
          "offer_id": {
            "$ref": "#/components/schemas/OfferId"
          },
          "product_id": {
            "$ref": "#/components/schemas/ProductId"
          },
          "changed_at": {
            "type": "string",
            "format": "date-time"
          },
          "action": {
            "type": "string",
            "const": "upsert"
          },
          "data": {
            "$ref": "#/components/schemas/Offer"
          }
        },
        "required": [
          "sequence",
          "offer_id",
          "product_id",
          "changed_at",
          "action",
          "data"
        ],
        "description": "현재 공개 가능한 최신 판매 조건을 반영합니다. 가격뿐 아니라 검증·유효 시각 갱신도 upsert를 생성할 수 있습니다."
      },
      "RemoveChange": {
        "type": "object",
        "properties": {
          "sequence": {
            "type": "integer",
            "description": "프로젝트의 변경 순번. 유일한 순차 정수이며 상품 ID가 아닙니다.",
            "minimum": 1
          },
          "offer_id": {
            "$ref": "#/components/schemas/OfferId"
          },
          "product_id": {
            "$ref": "#/components/schemas/ProductId"
          },
          "changed_at": {
            "type": "string",
            "format": "date-time"
          },
          "action": {
            "type": "string",
            "const": "remove"
          },
          "data": {
            "type": "null"
          }
        },
        "required": [
          "sequence",
          "offer_id",
          "product_id",
          "changed_at",
          "action",
          "data"
        ],
        "description": "현재 제공할 수 없는 판매 조건을 제거합니다. 과거 비공개 상품명·가격 payload를 반환하지 않습니다."
      },
      "Change": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/UpsertChange"
          },
          {
            "$ref": "#/components/schemas/RemoveChange"
          }
        ],
        "discriminator": {
          "propertyName": "action",
          "mapping": {
            "upsert": "#/components/schemas/UpsertChange",
            "remove": "#/components/schemas/RemoveChange"
          }
        }
      },
      "ChangesPage": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Change"
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "next_cursor": {
                "$ref": "#/components/schemas/Cursor"
              },
              "has_more": {
                "type": "boolean",
                "description": "true면 바로 다음 커서로 계속 수집합니다. false여도 next_cursor를 보관해 다음 주기에 사용합니다."
              },
              "notice": {
                "type": "string",
                "examples": [
                  "가격과 재고는 변동될 수 있으며, 최종 판매 조건은 키햐 상품 페이지에서 확인해 주세요."
                ]
              }
            },
            "required": [
              "next_cursor",
              "has_more",
              "notice"
            ]
          }
        },
        "required": [
          "data",
          "meta"
        ]
      },
      "ChangesInitialization": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Change"
            },
            "maxItems": 0
          },
          "meta": {
            "type": "object",
            "properties": {
              "initialization_required": {
                "type": "boolean",
                "const": true
              },
              "message": {
                "type": "string"
              },
              "next_cursor": {
                "$ref": "#/components/schemas/Cursor"
              },
              "has_more": {
                "type": "boolean",
                "const": false
              }
            },
            "required": [
              "initialization_required",
              "message",
              "next_cursor",
              "has_more"
            ]
          }
        },
        "required": [
          "data",
          "meta"
        ]
      },
      "ChangesResponse": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/ChangesInitialization"
          },
          {
            "$ref": "#/components/schemas/ChangesPage"
          }
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "프로그래밍에 사용할 고정 오류 코드.",
                "examples": [
                  "INVALID_API_KEY"
                ]
              },
              "message": {
                "type": "string",
                "description": "사용자 안내용 설명. 파싱에 의존하지 마세요.",
                "examples": [
                  "유효한 API 키가 필요합니다."
                ]
              },
              "request_id": {
                "type": "string",
                "description": "문의·진단에 사용하는 비민감 요청 식별자.",
                "examples": [
                  "example-request-id"
                ]
              }
            },
            "required": [
              "code",
              "message",
              "request_id"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    },
    "headers": {
      "RequestId": {
        "description": "비민감 요청 식별자. 문의 시 이 값만 전달하고 키·인증 헤더는 전달하지 않습니다.",
        "schema": {
          "type": "string"
        }
      },
      "CacheControl": {
        "description": "인증 API 응답은 공유·브라우저 캐시에 보관하지 않습니다.",
        "schema": {
          "type": "string"
        },
        "example": "private, no-store"
      },
      "RateLimitLimit": {
        "description": "이 프로젝트 모든 키가 공유하는 현재 분 단위 요청 한도. 인증·권한 검증 후 한도 검사에 도달한 응답에 포함됩니다.",
        "schema": {
          "type": "integer",
          "minimum": 1
        },
        "example": 60
      },
      "RateLimitRemaining": {
        "description": "현재 분 단위 버킷에서 남은 요청 수. 현재 요청의 사용량을 반영합니다.",
        "schema": {
          "type": "integer",
          "minimum": 0
        },
        "example": 59
      },
      "RateLimitReset": {
        "description": "현재 분 단위 버킷 초기화 시각의 Unix epoch 초입니다. 상대 초 수가 아닙니다.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "DailyLimitRemaining": {
        "description": "프로젝트 합산 일일 한도의 잔여 요청 수입니다. UTC 날짜 기준이며 매일 UTC 00:00(한국 시간 09:00)에 초기화합니다.",
        "schema": {
          "type": "integer",
          "minimum": 0
        },
        "example": 9999
      },
      "DailyLimitReset": {
        "description": "다음 UTC 00:00 초기화 시각의 Unix epoch 초입니다. 포털 일별 사용량은 한국 시간으로 집계하므로 기간이 다를 수 있습니다.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "RetryAfter": {
        "description": "다음 시도까지 기다릴 초 수. 429 및 일부 503(SYNC_BUSY 등)에 포함됩니다.",
        "schema": {
          "type": "integer",
          "minimum": 1
        },
        "example": 60
      },
      "SnapshotId": {
        "description": "CSV를 생성한 전체 스냅샷 식별자.",
        "schema": {
          "type": "string",
          "pattern": "^[a-f0-9]{32}$"
        }
      },
      "TotalRecords": {
        "description": "헤더를 제외한 CSV 행 수(upsert + remove). 정상 빈 CSV는 0입니다.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "SyncComplete": {
        "description": "CSV는 스냅샷 전체 수집에 성공한 뒤에만 응답합니다.",
        "schema": {
          "type": "string",
          "const": "true"
        }
      },
      "ContentDisposition": {
        "description": "CSV 다운로드 파일명.",
        "schema": {
          "type": "string"
        },
        "example": "attachment; filename=\"kihya-catalog.csv\""
      },
      "WWWAuthenticate": {
        "description": "401 응답의 Bearer 인증 안내.",
        "schema": {
          "type": "string"
        },
        "example": "Bearer realm=\"Kihya Developers\""
      },
      "Allow": {
        "description": "지원 메서드. HEAD/POST/OPTIONS 등은 상품 API에서 지원하지 않습니다.",
        "schema": {
          "type": "string",
          "const": "GET"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "커서 형식·범위·페이지 크기가 일치하지 않거나 query에 자격증명이 포함되었습니다.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "INVALID_CURSOR",
                "message": "커서 형식·범위·페이지 크기가 일치하지 않거나 query에 자격증명이 포함되었습니다.",
                "request_id": "example-request-id"
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "키 누락·잘못된 키·만료·폐기 또는 계정 비활성 상태입니다. 재시도를 반복하지 말고 키 설정을 확인합니다.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "WWW-Authenticate": {
            "$ref": "#/components/headers/WWWAuthenticate"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "INVALID_API_KEY",
                "message": "키 누락·잘못된 키·만료·폐기 또는 계정 비활성 상태입니다. 재시도를 반복하지 말고 키 설정을 확인합니다.",
                "request_id": "example-request-id"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "프로젝트 미승인·중단 또는 scope 권한 부족입니다. PARTNER_SUSPENDED이면 프로젝트 전체 표시를 즉시 중단합니다.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "PARTNER_SUSPENDED",
                "message": "프로젝트 미승인·중단 또는 scope 권한 부족입니다. PARTNER_SUSPENDED이면 프로젝트 전체 표시를 즉시 중단합니다.",
                "request_id": "example-request-id"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "조회 가능한 단건 상품 또는 API 경로가 없습니다. 비공개 상품 존재 여부를 알려주지 않습니다.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "PRODUCT_NOT_FOUND",
                "message": "조회 가능한 단건 상품 또는 API 경로가 없습니다. 비공개 상품 존재 여부를 알려주지 않습니다.",
                "request_id": "example-request-id"
              }
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "GET 외 메서드는 지원하지 않습니다.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "Allow": {
            "$ref": "#/components/headers/Allow"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "METHOD_NOT_ALLOWED",
                "message": "GET 외 메서드는 지원하지 않습니다.",
                "request_id": "example-request-id"
              }
            }
          }
        }
      },
      "SyncRestartRequired": {
        "description": "스냅샷·커서가 만료되거나 카탈로그·승인 정책이 바뀌었습니다. 첫 페이지 또는 변경 시작 커서를 다시 받아 전체 동기화합니다.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "SYNC_RESTART_REQUIRED",
                "message": "스냅샷·커서가 만료되거나 카탈로그·승인 정책이 바뀌었습니다. 첫 페이지 또는 변경 시작 커서를 다시 받아 전체 동기화합니다.",
                "request_id": "example-request-id"
              }
            }
          }
        }
      },
      "UnprocessableInput": {
        "description": "파라미터 형식·상품번호·limit 또는 ids 개수를 확인해 주세요.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "INVALID_INPUT",
                "message": "파라미터 형식·상품번호·limit 또는 ids 개수를 확인해 주세요.",
                "request_id": "example-request-id"
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "프로젝트 분·일 합산 한도 또는 CSV 별도 한도를 초과했습니다. Retry-After를 준수합니다.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "RATE_LIMITED",
                "message": "프로젝트 분·일 합산 한도 또는 CSV 별도 한도를 초과했습니다. Retry-After를 준수합니다.",
                "request_id": "example-request-id"
              }
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "원천 연결·가격 검증·동기화 또는 내부 저장소를 확인할 수 없습니다. 정상 빈 카탈로그로 해석하지 않습니다.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "SERVICE_UNAVAILABLE",
                "message": "원천 연결·가격 검증·동기화 또는 내부 저장소를 확인할 수 없습니다. 정상 빈 카탈로그로 해석하지 않습니다.",
                "request_id": "example-request-id"
              }
            }
          }
        }
      }
    }
  }
}
