{
  "openapi": "3.0.3",
  "info": {
    "title": "SEcMS API",
    "description": "Shipboard Electrical Cable Management System — Public REST API\n\nAuthentication: Firebase ID Token (Bearer) + X-CSRF-Token (Double Submit Cookie).\n\nRate Limits: 100 req/min per user (free tier), 1000 req/min (pro tier).\n\n에러 코드 전체: https://secms.tech/dev/errors",
    "version": "0.1.0",
    "contact": {
      "email": "admin@secms.tech",
      "url": "https://secms.tech"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://secms.tech/terms"
    }
  },
  "servers": [
    {
      "url": "https://secms.tech/api",
      "description": "Production"
    },
    {
      "url": "https://sandbox.secms.tech/api",
      "description": "Sandbox (test data)"
    }
  ],
  "tags": [
    {
      "name": "projects",
      "description": "호선 프로젝트 관리"
    },
    {
      "name": "cables",
      "description": "케이블 데이터"
    },
    {
      "name": "nodes",
      "description": "노드 (장비/패널)"
    },
    {
      "name": "admin",
      "description": "관리자 전용 (is_admin=1)"
    },
    {
      "name": "auth",
      "description": "인증"
    },
    {
      "name": "ai",
      "description": "Groq AI 분석"
    },
    {
      "name": "webhooks",
      "description": "이벤트 통합"
    }
  ],
  "paths": {
    "/projects": {
      "get": {
        "tags": [
          "projects"
        ],
        "summary": "프로젝트 목록 조회",
        "description": "인증 사용자의 모든 호선 (소유 + 그룹 공유 + 멤버 초대)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "summary",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "경량 모드 (cables/nodes 제외, count만)"
          }
        ],
        "responses": {
          "200": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Project"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "projects"
        ],
        "summary": "신규 호선 등록 (atomic)",
        "description": "cables/nodes 를 본문에 포함하면 즉시 저장 (R2 hybrid 자동 적용 800KB+)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "생성됨",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "description": "플랜 한도 초과",
            "content": {
              "application/json": {
                "example": {
                  "error": "프로젝트 한도(1개)에 도달했습니다. 플랜을 업그레이드하세요."
                }
              }
            }
          }
        }
      }
    },
    "/projects/{id}": {
      "get": {
        "tags": [
          "projects"
        ],
        "summary": "호선 단건 조회 (R2 dereference 자동)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "tags": [
          "projects"
        ],
        "summary": "호선 업데이트 (CAS optimistic locking)",
        "description": "expectedUpdatedAt 필수 — 미일치 시 409 Conflict",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "저장됨",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "updatedAt": "2026-05-05T08:21:43Z"
                }
              }
            }
          },
          "409": {
            "description": "CAS 충돌",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "projects"
        ],
        "summary": "호선 soft-delete",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "삭제됨"
          }
        }
      }
    },
    "/admin/users": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "전체 사용자 목록 (admin only)",
        "description": "subscriptions JOIN — tier 정보 포함",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminUser"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/admin/inquiries": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "문의 목록 (admin only)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "문의 배열 (테이블 없으면 빈 배열)"
          }
        }
      },
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "신규 문의 접수 (인증 사용자)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string"
                  },
                  "subject": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "생성됨"
          }
        }
      }
    },
    "/admin/settings": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "시스템 설정 조회",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "설정 객체"
          }
        }
      },
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "시스템 설정 저장",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "저장됨"
          }
        }
      }
    },
    "/admin/all-projects": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "전체 프로젝트 메타+settings (Super Admin 전용)",
        "description": "cables/nodes BLOB 제외. owner=<uid> 쿼리로 필터.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "owner",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "성공",
            "content": {
              "application/json": {
                "example": {
                  "projects": [],
                  "meta": {
                    "total": 0,
                    "owners": 0,
                    "by_owner": {}
                  }
                }
              }
            }
          },
          "403": {
            "description": "Super Admin only",
            "content": {
              "application/json": {
                "example": {
                  "error": "Super Admin only",
                  "code": "SUPER_ADMIN_REQUIRED"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "admin"
        ],
        "summary": "프로젝트 settings 일괄 패치 (Super Admin only)",
        "description": "허용 키만: displayName, classSociety, voltageStandard, cableTrayType, defaultDrumLength, inventoryEnabled, autoRouteOnImport, lockedByAdmin, maintenanceMode, notes",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id",
                  "settings"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "settings": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "저장됨"
          },
          "403": {
            "description": "Super Admin only"
          }
        }
      }
    },
    "/auth/verify-invite": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Invite code 검증 + tier 세션 발급",
        "responses": {
          "200": {
            "description": "세션 토큰"
          }
        }
      }
    },
    "/groups": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "그룹 목록 (5단계 RBAC)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "그룹 배열"
          }
        }
      }
    },
    "/users": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "사용자 목록",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "사용자 배열"
          }
        }
      }
    },
    "/subscription": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "본인 구독 / tier 조회",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "tier 정보"
          }
        }
      }
    },
    "/inquiry": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "문의하기 (인증 불필요, rate limit 적용)",
        "responses": {
          "200": {
            "description": "접수"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/feedback": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "피드백 위젯 (익명)",
        "responses": {
          "200": {
            "description": "접수"
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "ai"
        ],
        "summary": "Liveness check (no-auth)",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/status": {
      "get": {
        "tags": [
          "ai"
        ],
        "summary": "Public status page (D1 + R2 health, uptime)",
        "responses": {
          "200": {
            "description": "operational",
            "content": {
              "application/json": {
                "example": {
                  "status": "operational",
                  "version": "0.1.0",
                  "uptime_pct_30d": 99.97,
                  "checks": {
                    "d1": {
                      "ok": true,
                      "latency_ms": 5
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "degraded"
          }
        }
      }
    },
    "/metrics": {
      "get": {
        "tags": [
          "ai"
        ],
        "summary": "Real telemetry (total_projects, active_users_30d 등, 60s cache)",
        "responses": {
          "200": {
            "description": "실 metrics"
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "tags": [
          "ai"
        ],
        "summary": "OpenAPI 3.0 spec (이 문서)",
        "responses": {
          "200": {
            "description": "OpenAPI JSON"
          }
        }
      }
    },
    "/sandbox": {
      "get": {
        "tags": [
          "ai"
        ],
        "summary": "Sandbox 정보 (no-auth)",
        "responses": {
          "200": {
            "description": "sandbox info"
          }
        }
      }
    },
    "/sandbox/projects": {
      "get": {
        "tags": [
          "ai"
        ],
        "summary": "3 mock projects (no-auth)",
        "responses": {
          "200": {
            "description": "mock 배열"
          }
        }
      },
      "post": {
        "tags": [
          "ai"
        ],
        "summary": "Echo + 가짜 id",
        "responses": {
          "201": {
            "description": "에코"
          }
        }
      }
    },
    "/sandbox/route": {
      "post": {
        "tags": [
          "ai"
        ],
        "summary": "Mock 라우팅 결과 (no-auth)",
        "responses": {
          "200": {
            "description": "mock 라우팅"
          }
        }
      }
    },
    "/sandbox/status": {
      "get": {
        "tags": [
          "ai"
        ],
        "summary": "Sandbox status (always operational)",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/telemetry": {
      "post": {
        "tags": [
          "ai"
        ],
        "summary": "클라이언트 에러/이벤트 전송 (PII 자동 마스킹, 8KB max, IP rate limit 60/min)",
        "responses": {
          "201": {
            "description": "저장됨"
          },
          "413": {
            "description": "Body too large"
          }
        }
      },
      "get": {
        "tags": [
          "ai"
        ],
        "summary": "최근 100건 telemetry 이벤트 (관리자 디버깅)",
        "responses": {
          "200": {
            "description": "events 배열"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "tags": [
          "webhooks"
        ],
        "summary": "본인 webhook 목록 + 8개 supported_events",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "webhooks + supported_events"
          }
        }
      },
      "post": {
        "tags": [
          "webhooks"
        ],
        "summary": "Webhook 등록 (HMAC secret 자동 생성)",
        "description": "events: cable.created/updated/deleted, route.calculated/failed, project.created/updated, inquiry.created",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https://"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "secret": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "등록됨 (HMAC secret 포함)"
          },
          "400": {
            "description": "잘못된 URL or events"
          }
        }
      }
    },
    "/webhooks/deliveries": {
      "get": {
        "tags": [
          "webhooks"
        ],
        "summary": "Webhook delivery 목록 (50개) + 통계",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "wh",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "deliveries + stats(success_rate)"
          }
        }
      },
      "post": {
        "tags": [
          "webhooks"
        ],
        "summary": "실패한 delivery 재시도",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "retry queued"
          }
        }
      }
    },
    "/gdpr/export": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "GDPR Article 20 — Data Portability (본인 데이터 JSON download)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "JSON download (Content-Disposition attachment)"
          }
        }
      }
    },
    "/gdpr/delete": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "GDPR Article 17 — Right to be Forgotten (30일 grace + 즉시 PII 익명화)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "confirm"
                ],
                "properties": {
                  "confirm": {
                    "type": "string",
                    "enum": [
                      "DELETE-MY-DATA"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "scheduled_at + 30일 grace + cancel_endpoint"
          },
          "400": {
            "description": "confirm required"
          }
        }
      }
    },
    "/installation": {
      "get": {
        "tags": [
          "cables"
        ],
        "summary": "포설 진행 데이터",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "포설 배열"
          }
        }
      }
    },
    "/inventory": {
      "get": {
        "tags": [
          "cables"
        ],
        "summary": "Inventory (재고)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "재고 배열"
          }
        }
      }
    },
    "/procurement": {
      "get": {
        "tags": [
          "cables"
        ],
        "summary": "발주서 (POS) 데이터",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "발주 배열"
          }
        }
      }
    },
    "/tray-fill": {
      "post": {
        "tags": [
          "cables"
        ],
        "summary": "Tray Fill 물리 시뮬레이션 (IEC 61537)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "fill summary"
          }
        }
      }
    },
    "/bom-margins": {
      "get": {
        "tags": [
          "cables"
        ],
        "summary": "BOM 여유율 설정",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "margins"
          }
        }
      }
    },
    "/class-rule": {
      "post": {
        "tags": [
          "cables"
        ],
        "summary": "8 선급 동시 룰 검증 (DNV/LR/BV/ABS/NK/KR/RINA/CCS)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "위반 리스트"
          }
        }
      }
    },
    "/ai/groq": {
      "post": {
        "tags": [
          "ai"
        ],
        "summary": "Groq AI (4-key round-robin) — 노드/케이블 분석",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "AI 결과"
          }
        }
      }
    },
    "/convert": {
      "post": {
        "tags": [
          "ai"
        ],
        "summary": "Excel/PDF → 케이블 리스트 자동 변환",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "cables 배열"
          }
        }
      }
    },
    "/vessel-history": {
      "get": {
        "tags": [
          "projects"
        ],
        "summary": "호선별 작업 이력 (복원 가능)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "history 배열"
          }
        }
      }
    },
    "/user/backup": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Manual backup trigger (본인 데이터 R2 export)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "backup URL"
          }
        }
      }
    },
    "/super-admin/list": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Super Admin 전용 — 전체 사용자/테넌트",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "전체 리스트"
          },
          "403": {
            "description": "Super Admin only"
          }
        }
      }
    },
    "/projects/{id}/cables": {
      "get": {
        "tags": [
          "cables"
        ],
        "summary": "호선의 케이블 리스트만 (R2 dereference 자동)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cable[] 배열"
          }
        }
      }
    },
    "/projects/{id}/nodes": {
      "get": {
        "tags": [
          "nodes"
        ],
        "summary": "호선의 노드 리스트만",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Node[] 배열"
          }
        }
      }
    },
    "/projects/{id}/route": {
      "post": {
        "tags": [
          "cables"
        ],
        "summary": "호선 전체 라우팅 트리거 (Heap-Dijkstra 4-worker)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "라우팅 결과"
          }
        }
      }
    },
    "/admin/users/{id}": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "단건 사용자 조회",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "AdminUser"
          }
        }
      },
      "put": {
        "tags": [
          "admin"
        ],
        "summary": "사용자 권한 + tier 업데이트",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "permissions": {
                  "_group": "standard"
                },
                "status": "active",
                "tier": "standard"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "저장됨"
          }
        }
      }
    },
    "/admin/inquiries/{id}": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "문의 단건 조회",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "문의 객체"
          }
        }
      },
      "put": {
        "tags": [
          "admin"
        ],
        "summary": "관리자 답변 + 상태 변경",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "저장됨"
          }
        }
      }
    },
    "/admin/approvals": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "승인 대기 신청 목록",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "ApprovalRequest[]"
          }
        }
      },
      "put": {
        "tags": [
          "admin"
        ],
        "summary": "승인/거부 처리",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "id": "...",
                "status": "approved",
                "group": "standard"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "처리됨"
          }
        }
      }
    },
    "/portone/payment": {
      "post": {
        "tags": [
          "ai"
        ],
        "summary": "PortOne 결제 (한국 카드/계좌)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "결제 완료"
          }
        }
      }
    },
    "/portone/verify": {
      "post": {
        "tags": [
          "ai"
        ],
        "summary": "PortOne 결제 검증",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "검증 완료"
          }
        }
      }
    },
    "/stripe/checkout": {
      "post": {
        "tags": [
          "ai"
        ],
        "summary": "Stripe Checkout 세션 생성",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "sessionId + url"
          }
        }
      }
    },
    "/stripe/webhook": {
      "post": {
        "tags": [
          "webhooks"
        ],
        "summary": "Stripe Webhook 수신 (HMAC 검증)",
        "responses": {
          "200": {
            "description": "ack"
          },
          "401": {
            "description": "서명 불일치"
          }
        }
      }
    },
    "/samples": {
      "get": {
        "tags": [
          "ai"
        ],
        "summary": "데모 샘플 데이터 (cables/nodes JSON)",
        "responses": {
          "200": {
            "description": "샘플 객체"
          }
        }
      }
    },
    "/groups-permissions": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "5단계 권한 그룹 정의 조회",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "그룹 정의"
          }
        }
      },
      "put": {
        "tags": [
          "admin"
        ],
        "summary": "그룹 권한 정의 일괄 업데이트",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "저장됨"
          }
        }
      }
    },
    "/activity": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "사용자 활동 로그 조회",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "audit_log 배열"
          }
        }
      },
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "클라이언트 이벤트 기록",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "기록됨"
          }
        }
      }
    },
    "/auth/naver-user": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Naver OAuth 사용자 등록 (한국 시장)",
        "responses": {
          "200": {
            "description": "등록 완료"
          }
        }
      }
    },
    "/grok-advisor": {
      "post": {
        "tags": [
          "ai"
        ],
        "summary": "Grok AI 어드바이저 (선급 룰 추천)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "AI 답변"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Firebase JWT (RS256)"
      }
    },
    "schemas": {
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "a1b2c3d4-..."
          },
          "userId": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "example": "V-001"
          },
          "vesselNo": {
            "type": "string",
            "example": "V001"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "cables": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Cable"
            }
          },
          "nodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Node"
            }
          },
          "cableListTemplate": {
            "type": "string",
            "enum": [
              "standard",
              "extended-17",
              "custom"
            ]
          }
        }
      },
      "ProjectCreate": {
        "type": "object",
        "required": [
          "name",
          "vesselNo"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "vesselNo": {
            "type": "string",
            "maxLength": 50
          },
          "cables": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Cable"
            }
          },
          "nodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Node"
            }
          }
        }
      },
      "ProjectUpdate": {
        "type": "object",
        "required": [
          "expectedUpdatedAt"
        ],
        "properties": {
          "expectedUpdatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "cables": {
            "type": "array"
          },
          "nodes": {
            "type": "array"
          },
          "history": {
            "type": "array"
          }
        }
      },
      "Cable": {
        "type": "object",
        "required": [
          "id",
          "name",
          "type",
          "od"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "system": {
            "type": "string"
          },
          "od": {
            "type": "number",
            "description": "외경 mm"
          },
          "fromNode": {
            "type": "string"
          },
          "toNode": {
            "type": "string"
          },
          "length": {
            "type": "number"
          },
          "calculatedLength": {
            "type": "number"
          },
          "installDate": {
            "type": "string",
            "format": "date"
          },
          "conFromDate": {
            "type": "string",
            "format": "date"
          },
          "conToDate": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "Node": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "deck": {
            "type": "string"
          },
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          },
          "z": {
            "type": "number"
          }
        }
      },
      "AdminUser": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "tier": {
            "type": "string",
            "enum": [
              "free",
              "trial",
              "basic",
              "team",
              "pro"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "pending"
            ]
          },
          "is_admin": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "permissions": {
            "type": "object"
          }
        }
      },
      "ConflictError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "CONFLICT"
          },
          "serverUpdatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "message": {
            "type": "string",
            "example": "다른 사용자가 먼저 수정했습니다."
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "cable.created",
                "cable.updated",
                "cable.deleted",
                "route.calculated",
                "route.failed",
                "project.created",
                "project.updated",
                "inquiry.created"
              ]
            }
          },
          "secret": {
            "type": "string",
            "description": "HMAC-SHA256 서명 시크릿 (생성 시 1회 노출)"
          },
          "active": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "webhook_id": {
            "type": "string"
          },
          "event": {
            "type": "string"
          },
          "attempt": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "success",
              "failed"
            ]
          },
          "http_code": {
            "type": "integer"
          },
          "latency_ms": {
            "type": "integer"
          },
          "error": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "StatusResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "operational",
              "degraded"
            ]
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "version": {
            "type": "string",
            "example": "0.1.0"
          },
          "uptime_pct_30d": {
            "type": "number",
            "example": 99.97
          },
          "maintenance_window": {
            "type": "string",
            "nullable": true
          },
          "checks": {
            "type": "object",
            "properties": {
              "d1": {
                "type": "object",
                "properties": {
                  "ok": {
                    "type": "boolean"
                  },
                  "latency_ms": {
                    "type": "integer"
                  }
                }
              },
              "r2": {
                "type": "object",
                "properties": {
                  "ok": {
                    "type": "boolean"
                  },
                  "latency_ms": {
                    "type": "integer"
                  }
                }
              },
              "api": {
                "type": "object",
                "properties": {
                  "ok": {
                    "type": "boolean"
                  },
                  "latency_ms": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      },
      "MetricsResponse": {
        "type": "object",
        "properties": {
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string",
            "example": "SEcMS Production"
          },
          "total_projects": {
            "type": "integer"
          },
          "active_users_30d": {
            "type": "integer",
            "nullable": true
          },
          "api_requests_24h": {
            "type": "integer",
            "nullable": true
          },
          "r2_sample_objects": {
            "type": "integer"
          },
          "r2_truncated": {
            "type": "boolean"
          }
        }
      },
      "TelemetryEvent": {
        "type": "object",
        "required": [
          "type",
          "level",
          "message"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "error",
              "unhandledrejection",
              "manual"
            ]
          },
          "level": {
            "type": "string",
            "enum": [
              "debug",
              "info",
              "warn",
              "error"
            ]
          },
          "message": {
            "type": "string",
            "maxLength": 1000
          },
          "stack": {
            "type": "string",
            "maxLength": 4000
          },
          "url": {
            "type": "string",
            "maxLength": 500
          },
          "ua": {
            "type": "string",
            "maxLength": 500
          },
          "request_id": {
            "type": "string"
          },
          "meta": {
            "type": "object"
          }
        }
      },
      "RateLimitError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "Too Many Requests"
          },
          "code": {
            "type": "string",
            "example": "RATE_LIMIT_EXCEEDED"
          },
          "retry_after_sec": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "window_sec": {
            "type": "integer"
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "projectId contains invalid characters"
          },
          "code": {
            "type": "string",
            "example": "PROJECT_ID_INVALID_CHARS"
          },
          "field": {
            "type": "string",
            "example": "projectId"
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "401 Unauthorized — Bearer 토큰 누락 또는 만료"
      },
      "BadRequest": {
        "description": "400 Bad Request — 입력 검증 실패"
      },
      "NotFound": {
        "description": "404 Not Found"
      },
      "RateLimited": {
        "description": "429 Too Many Requests",
        "headers": {
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            },
            "description": "시간창 내 최대 요청 수"
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            },
            "description": "남은 요청 수"
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "integer"
            },
            "description": "Unix 타임 (리셋 시각)"
          }
        }
      }
    }
  }
}