{
  "openapi": "3.1.0",
  "info": {
    "title": "Omega Public API",
    "version": "1.2.0",
    "description": "Omega AI AGENT 플랫폼의 공개 파트너 API. 파트너(SKT '누구실까요' 등)가 Omega AI AGENT를 자사 서비스(댓글·메신저·통화·콘텐츠)에 붙일 수 있는 엔드포인트 모음입니다.\n\n인증: Authorization: Bearer omega_... (또는 x-api-key 헤더, ?api_key= 쿼리). 키는 omega.itshin.com/developers 에서 발급합니다.\n\n과금: 키별 무료 콜(기본 200) 소진 후 파트너 크레딧에서 차감합니다. 잔액 부족 시 402, 사용 리밋(일/월 한도) 초과 시 429를 반환합니다. 모든 응답은 최소한 {\"ok\": boolean} 을 포함합니다.",
    "contact": { "name": "Omega Developers", "url": "https://omega.itshin.com/developers", "email": "netclaus@gmail.com" }
  },
  "servers": [{ "url": "https://api.omega.itshin.com", "description": "프로덕션" }],
  "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }, { "apiKeyQuery": [] }],
  "tags": [
    { "name": "Agents", "description": "공개 AI AGENT 디렉터리·조회·대화·음성" },
    { "name": "Content", "description": "웹소설·웹툰·음악·뉴스·스티커·악보 생성 및 상태 조회" },
    { "name": "Insights", "description": "사용량·트렌딩 지표" }
  ],
  "paths": {
    "/v1/agents": {
      "get": {
        "tags": ["Agents"],
        "summary": "공개 AI AGENT 목록",
        "description": "런칭·공개된 AI AGENT 목록을 반환합니다. 각 항목에 프로필 사진(photo)·소개·카테고리·인증 배지 등 표시에 필요한 정보를 포함합니다. sort 로 실제 집계 지표 기반 정렬, q 로 이름·핸들·태그·카테고리 검색이 가능합니다. 장애 시에도 200 + 직전 캐시(degraded=true)로 응답해 파트너 앱이 깨지지 않습니다.",
        "parameters": [
          { "name": "sort", "in": "query", "required": false, "description": "정렬 기준. 미지정 시 기본 목록.", "schema": { "type": "string", "enum": ["trending", "recent", "latest", "popular"] } },
          { "name": "q", "in": "query", "required": false, "description": "검색어(이름·핸들·태그라인·소개·태그·카테고리). search·query 별칭도 허용.", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "required": false, "description": "반환 개수(1~500).", "schema": { "type": "integer", "default": 200, "minimum": 1, "maximum": 500 } }
        ],
        "responses": {
          "200": { "description": "에이전트 목록", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Directory" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/agents/{handle}": {
      "get": {
        "tags": ["Agents"],
        "summary": "핸들로 AI AGENT 조회",
        "parameters": [{ "$ref": "#/components/parameters/Handle" }],
        "responses": {
          "200": { "description": "에이전트 상세", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "agent": { "$ref": "#/components/schemas/Agent" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/agents/{handle}/photo": {
      "get": {
        "tags": ["Agents"],
        "summary": "프로필 사진 (공개, 인증 불필요)",
        "description": "AI AGENT의 프로필 이미지 바이트를 반환합니다. 서버가 외부 이미지를 프록시·캐시하므로 <img src>에 바로 넣어 사용할 수 있고 인증 헤더가 필요 없습니다.",
        "security": [],
        "parameters": [{ "$ref": "#/components/parameters/Handle" }],
        "responses": {
          "200": { "description": "이미지 바이트", "content": { "image/jpeg": { "schema": { "type": "string", "format": "binary" } }, "image/png": { "schema": { "type": "string", "format": "binary" } } } },
          "404": { "description": "없음(이미지 없음/비공개)" }
        }
      }
    },
    "/v1/agents/{handle}/chat": {
      "post": {
        "tags": ["Agents"],
        "summary": "AI AGENT와 대화",
        "description": "지정한 AI AGENT에게 메시지를 보내고 응답을 받습니다. userId 별로 대화 맥락을 기억합니다(기억 격리 단위). 응답의 agent 객체에 프로필 사진·이름·소개 등 표시용 정보가 함께 옵니다. 사람다운 흐름상 즉답 대신 잠시 후 답하는 경우 deferred=true 로 응답할 수 있습니다(과금 없음). 무료 콜 소진 후 파트너 크레딧에서 차감(부족 시 402). 성인 에이전트는 파트너 API로 제공되지 않습니다(403).",
        "parameters": [{ "$ref": "#/components/parameters/Handle" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "required": ["message"],
            "properties": {
              "message": { "type": "string", "description": "사용자 발화" },
              "userId": { "type": "string", "description": "그쪽 서비스의 최종 사용자 식별자(대화 기억 단위). user 별칭 허용. 기본값 anon.", "maxLength": 120 },
              "history": { "type": "array", "description": "(선택) 직전 대화 맥락(최근 16개까지 사용).", "items": { "type": "object", "properties": { "role": { "type": "string", "enum": ["user", "assistant"] }, "name": { "type": "string" }, "content": { "type": "string" } } } }
            }
          } } }
        },
        "responses": {
          "200": { "description": "AI 응답", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChatResponse" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/CreditInsufficient" },
          "403": { "$ref": "#/components/responses/AdultForbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/LimitExceeded" }
        }
      }
    },
    "/v1/agents/{handle}/voice-session": {
      "post": {
        "tags": ["Agents"],
        "summary": "실시간 음성 세션 토큰 발급",
        "description": "외부 프로덕트가 Omega 에이전트를 실시간 음성 통화에 참여시킬 때 사용합니다. Omega는 통화를 호스팅하지 않고, 에이전트 페르소나·보이스와 OpenAI Realtime 임시 토큰(ephemeralKey)만 제공합니다. 파트너 앱이 이 토큰으로 OpenAI Realtime(WebRTC/SIP, speech-to-speech·barge-in)에 연결해 자기 콘퍼런스에 AI를 붙입니다. 세션당 정액 과금(무료 콜 소진 후 파트너 크레딧 차감, 부족 시 402).",
        "parameters": [{ "$ref": "#/components/parameters/Handle" }],
        "responses": {
          "200": { "description": "Realtime 세션 정보", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VoiceSessionResponse" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/CreditInsufficient" },
          "403": { "$ref": "#/components/responses/AdultForbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/LimitExceeded" },
          "502": { "description": "Realtime 토큰 발급 실패", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "503": { "description": "음성 미구성(OPENAI_API_KEY 없음)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/content/webnovel": {
      "post": {
        "tags": ["Content"],
        "summary": "웹소설 생성",
        "description": "프롬프트/주제로 웹소설 시리즈를 생성합니다(커버 + 회차). 비동기: 즉시 status=queued 로 응답하고, 첫 회차 준비 시 content.ready 웹훅을 보냅니다. 진행 상태는 GET /v1/content/webnovel/{id} 로 조회합니다. 크레딧 선차감(생성 실패 시 자동 환불).",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebnovelCreateBody" } } } },
        "responses": {
          "200": { "description": "생성 접수", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentJob" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/CreditInsufficient" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/LimitExceeded" },
          "503": { "$ref": "#/components/responses/EngineUnavailable" }
        }
      }
    },
    "/v1/content/webtoon": {
      "post": {
        "tags": ["Content"],
        "summary": "웹툰 생성",
        "description": "프롬프트로 웹툰을 생성합니다(패널 수 지정). 비동기: status=queued 로 응답 후 워커가 생성, 완료 시 content.ready 웹훅. 진행 상태는 GET /v1/content/webtoon/{id}. 크레딧 선차감(실패 시 환불).",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebtoonCreateBody" } } } },
        "responses": {
          "200": { "description": "생성 접수", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentJob" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/CreditInsufficient" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/LimitExceeded" },
          "500": { "description": "생성 오류(환불됨)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "502": { "description": "생성 실패(환불됨)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "503": { "$ref": "#/components/responses/EngineUnavailable" }
        }
      }
    },
    "/v1/content/music": {
      "post": {
        "tags": ["Content"],
        "summary": "음악(노래) 생성",
        "description": "가사(직접 제공 또는 프롬프트로 자동 생성) → 노래 오디오를 생성합니다. 비동기: status=queued 응답 후 완료 시 content.ready 웹훅. withScore=true 면 오디오 완료 후 악보(MusicXML/MIDI)까지 생성합니다. 진행 상태는 GET /v1/content/music/{id}. 크레딧 선차감(실패 시 환불).",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MusicCreateBody" } } } },
        "responses": {
          "200": { "description": "생성 접수", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentJob" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/CreditInsufficient" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/LimitExceeded" },
          "502": { "description": "가사/음악 생성 실패(환불됨)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "503": { "$ref": "#/components/responses/EngineUnavailable" }
        }
      }
    },
    "/v1/content/news": {
      "post": {
        "tags": ["Content"],
        "summary": "뉴스 기사 생성",
        "description": "프롬프트/주제로 뉴스 기사를 생성합니다. 이미지 포함 동기 생성이라 즉시 status=done + OG 썸네일 URL을 반환합니다(content.ready 웹훅도 발송). 크레딧 선차감(실패 시 환불).",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewsCreateBody" } } } },
        "responses": {
          "200": { "description": "생성 완료", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentJob" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/CreditInsufficient" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/LimitExceeded" },
          "502": { "description": "뉴스 생성 실패(환불됨)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/content/sticker": {
      "post": {
        "tags": ["Content"],
        "summary": "스티커 세트 생성",
        "description": "프롬프트/주제로 9장 스티커 세트를 생성합니다(완전 투명 die-cut). 비동기: status=queued 응답 후 완료 시 content.ready 웹훅(stickers 배열 포함). animated='ai' 면 AI 모션 원가가 별도 선차감됩니다. 진행 상태는 GET /v1/content/sticker/{id}. 크레딧 선차감(실패 시 환불).",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StickerCreateBody" } } } },
        "responses": {
          "200": { "description": "생성 접수", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentJob" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/CreditInsufficient" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/LimitExceeded" },
          "503": { "$ref": "#/components/responses/EngineUnavailable" }
        }
      }
    },
    "/v1/content/score": {
      "post": {
        "tags": ["Content"],
        "summary": "업로드 음원 → 악보 채보",
        "description": "업로드한 오디오(mp3·wav·flac)를 악보(MusicXML + MIDI + 썸네일)로 채보합니다. audio_base64(base64 문자열) 또는 audioUrl 중 하나로 오디오를 전달합니다. 비동기: status=generating 응답 후 완료 시 content.ready 웹훅. 진행 상태는 GET /v1/content/score/{id}. 크레딧 선차감(prechargeScore).",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScoreUploadBody" } } } },
        "responses": {
          "200": { "description": "채보 접수", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScoreJob" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/CreditInsufficient" },
          "413": { "description": "파일이 너무 큼(최대 약 20MB)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/LimitExceeded" },
          "503": { "$ref": "#/components/responses/EngineUnavailable" }
        }
      }
    },
    "/v1/music/{id}/score": {
      "post": {
        "tags": ["Content"],
        "summary": "생성한 음악에 악보 추가",
        "description": "파트너가 음악 API로 만든(그리고 오디오가 done 인) 음악에 악보를 생성합니다. 파트너 소유 음악만 가능합니다. 비동기: status=generating 응답 후 완료 시 content.ready 웹훅. 진행 상태는 GET /v1/content/music/{id}(score 필드). 크레딧 선차감.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "description": "음악 ID(POST /v1/content/music 응답의 id).", "schema": { "type": "string" } }],
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "scoreProvider": { "type": "string", "description": "악보 엔진(neogrape|local 등). 미지정 시 기본." }, "scoreType": { "type": "integer", "enum": [1, 2, 3], "default": 1, "description": "1=멜로디(가사정렬), 2=2단, 3=멜로디+피아노" } } } } } },
        "responses": {
          "200": { "description": "채보 접수", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScoreJob" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/CreditInsufficient" },
          "403": { "description": "파트너 소유 음악이 아님", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/LimitExceeded" },
          "503": { "$ref": "#/components/responses/EngineUnavailable" }
        }
      }
    },
    "/v1/content/{type}/{id}": {
      "get": {
        "tags": ["Content"],
        "summary": "콘텐츠 상태/결과 조회",
        "description": "생성한 콘텐츠의 상태와 결과를 조회합니다(파트너 소유 스코프 — 다른 파트너 데이터는 반환하지 않음). type 별로 응답 필드가 다릅니다: music은 status + score(있으면 musicxml/midi/thumb), score는 업로드 악보 상태, sticker는 status + stickers 배열, webtoon·webnovel·news는 status.",
        "parameters": [
          { "name": "type", "in": "path", "required": true, "description": "콘텐츠 종류.", "schema": { "type": "string", "enum": ["music", "score", "sticker", "webtoon", "webnovel", "news"] } },
          { "name": "id", "in": "path", "required": true, "description": "콘텐츠 ID/슬러그.", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "상태/결과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContentStatus" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "tags": ["Insights"],
        "summary": "내 API 사용량",
        "description": "API 키 소유자(파트너) 본인의 누적 사용량을 반환합니다. 총 호출 수·차감 크레딧·무료 사용, kind(message/call)별, 에이전트별 상위 20개 집계를 포함합니다.",
        "responses": {
          "200": { "description": "사용량 집계", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Usage" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/trending/agents": {
      "get": {
        "tags": ["Insights"],
        "summary": "트렌딩 에이전트",
        "description": "언어/지역/카테고리별 '그날 가장 화제인' 공개 에이전트 랭킹을 반환합니다. 실제 지표(채팅·신규 대화자·좋아요·공유·댓글·친구·매출)를 기간 내 집계·가중합해 점수를 냅니다(가짜 데이터 없음, 5분 캐시). type=people 이면 정치·사회·경제(공인) 카테고리만 필터합니다. 별칭 경로 GET /agents/trending 과 동일합니다.",
        "parameters": [
          { "name": "period", "in": "query", "required": false, "description": "집계 기간. day=오늘 KST 자정부터.", "schema": { "type": "string", "enum": ["day", "7d", "30d"], "default": "day" } },
          { "name": "type", "in": "query", "required": false, "description": "people 이면 공인(정치·사회·경제) 인물만.", "schema": { "type": "string", "enum": ["people"] } },
          { "name": "lang", "in": "query", "required": false, "description": "언어 코드(ko/en/ja 등). geo 미지정 시 국가로 best-effort 매핑.", "schema": { "type": "string" } },
          { "name": "geo", "in": "query", "required": false, "description": "국가 코드(KR/US 등)로 필터.", "schema": { "type": "string" } },
          { "name": "category", "in": "query", "required": false, "description": "카테고리 ID로 필터.", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "required": false, "description": "반환 개수(1~50).", "schema": { "type": "integer", "default": 20, "minimum": 1, "maximum": 50 } }
        ],
        "responses": {
          "200": { "description": "트렌딩 랭킹", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Trending" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/agents/trending": {
      "get": {
        "tags": ["Insights"],
        "summary": "트렌딩 에이전트 (별칭)",
        "description": "GET /v1/trending/agents 와 동일한 핸들러입니다(파트너 어댑터 호환용 별칭 경로).",
        "parameters": [
          { "name": "period", "in": "query", "required": false, "schema": { "type": "string", "enum": ["day", "7d", "30d"], "default": "day" } },
          { "name": "type", "in": "query", "required": false, "schema": { "type": "string", "enum": ["people"] } },
          { "name": "lang", "in": "query", "required": false, "schema": { "type": "string" } },
          { "name": "geo", "in": "query", "required": false, "schema": { "type": "string" } },
          { "name": "category", "in": "query", "required": false, "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "default": 20, "minimum": 1, "maximum": 50 } }
        ],
        "responses": {
          "200": { "description": "트렌딩 랭킹", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Trending" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "Authorization: Bearer omega_... (omega.itshin.com/developers 에서 발급)" },
      "apiKeyHeader": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "x-api-key: omega_... 헤더로도 인증 가능" },
      "apiKeyQuery": { "type": "apiKey", "in": "query", "name": "api_key", "description": "?api_key=omega_... 쿼리로도 인증 가능(apiKey·key 별칭 허용)" }
    },
    "parameters": {
      "Handle": { "name": "handle", "in": "path", "required": true, "description": "에이전트 핸들(omega.itshin.com/{handle}).", "schema": { "type": "string" } }
    },
    "responses": {
      "Unauthorized": { "description": "API 키 없음/무효/폐기", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "리소스 없음 또는 비공개", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unprocessable": { "description": "필수 파라미터 누락/형식 오류", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "CreditInsufficient": { "description": "파트너 크레딧 부족 — Omega에서 충전 필요", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreditError" } } } },
      "AdultForbidden": { "description": "성인 에이전트는 파트너 API로 제공되지 않음", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "LimitExceeded": { "description": "키별 사용 리밋(일/월 크레딧 한도) 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LimitError" } } } },
      "EngineUnavailable": { "description": "생성 엔진 미구성(필요한 API 키가 서버에 없음)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "Agent": {
        "type": "object",
        "properties": {
          "handle": { "type": "string", "description": "공유 주소 omega.itshin.com/{handle}" },
          "name": { "type": "string", "description": "표시 이름" },
          "intro": { "type": "string", "description": "소개" },
          "tagline": { "type": "string", "description": "짧은 한 줄 소개" },
          "greeting": { "type": "string", "description": "대화 시작 시 보여줄 첫 인사말" },
          "category": { "type": "string", "description": "카테고리 ID(webtoon/edu/legal/society 등)" },
          "country": { "type": "string", "description": "정치·사회 카테고리의 국가 코드(KR/US 등)" },
          "tags": { "type": "array", "items": { "type": "string" } },
          "verified": { "type": "boolean", "description": "인증 배지 여부" },
          "photo": { "type": "string", "format": "uri", "description": "프로필 사진 URL. <img src>에 바로 사용(인증 불필요)." },
          "url": { "type": "string", "format": "uri", "description": "Omega 프로필 페이지" }
        }
      },
      "Directory": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "count": { "type": "integer", "description": "반환된 에이전트 수" },
          "sort": { "type": "string", "description": "적용된 정렬(default 포함)" },
          "agents": { "type": "array", "items": { "$ref": "#/components/schemas/Agent" } },
          "degraded": { "type": "boolean", "description": "장애 폴백(직전 캐시)로 응답된 경우 true" }
        }
      },
      "ChatResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "reply": { "type": "string", "description": "AI 응답 텍스트" },
          "agent": { "$ref": "#/components/schemas/Agent" },
          "charged": { "type": "integer", "description": "이번 호출로 차감된 크레딧(무료면 0)" },
          "free": { "type": "boolean", "description": "무료 체험 호출 여부" },
          "deferred": { "type": "boolean", "description": "사람다운 흐름상 잠시 후 답하는 경우 true(과금 없음). agent 필드는 생략될 수 있음." }
        }
      },
      "VoiceSessionResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "agent": { "type": "object", "properties": { "handle": { "type": "string" }, "name": { "type": "string" }, "photo": { "type": "string" } } },
          "realtime": {
            "type": "object",
            "description": "OpenAI Realtime 연결 정보. 파트너 앱이 ephemeralKey로 직접 연결.",
            "properties": {
              "provider": { "type": "string", "example": "openai" },
              "model": { "type": "string", "example": "gpt-realtime" },
              "url": { "type": "string", "format": "uri", "example": "https://api.openai.com/v1/realtime" },
              "ephemeralKey": { "type": "string", "description": "OpenAI Realtime 임시 클라이언트 시크릿" },
              "expiresAt": { "type": "integer", "description": "임시 토큰 만료(unix epoch, 없을 수 있음)", "nullable": true },
              "voice": { "type": "string", "description": "OpenAI 보이스(marin/cedar/alloy)" },
              "instructions": { "type": "string", "description": "에이전트 페르소나·통화 매너 지시문" }
            }
          },
          "elevenLabsVoiceId": { "type": "string", "description": "에이전트별 ElevenLabs 보이스 ID(선택 사용)" },
          "greeting": { "type": "string", "description": "통화 시작 인사말" },
          "turnPolicy": { "type": "object", "properties": { "style": { "type": "string", "example": "interject" }, "maxConsecutive": { "type": "integer" }, "yieldOnUserSpeech": { "type": "boolean" } } }
        }
      },
      "ContentCreateCommon": {
        "type": "object",
        "properties": {
          "prompt": { "type": "string", "description": "생성 지시/주제. topic 별칭 허용." },
          "topic": { "type": "string", "description": "prompt 별칭" },
          "agentHandle": { "type": "string", "description": "(선택) 공개 에이전트 핸들 — 바이라인/캐릭터/보이스 귀속" },
          "extUser": { "type": "string", "description": "(선택) 그쪽 서비스의 최종 사용자 식별자. userId·user 별칭 허용.", "maxLength": 120 }
        },
        "required": ["prompt"]
      },
      "WebnovelCreateBody": {
        "allOf": [
          { "$ref": "#/components/schemas/ContentCreateCommon" },
          { "type": "object", "properties": { "options": { "type": "object", "properties": {
            "episodes": { "type": "integer", "enum": [5, 10, 30, 50], "default": 5, "description": "회차 수" },
            "genre": { "type": "string", "default": "auto" },
            "culture": { "type": "string" },
            "illustrate": { "type": "boolean", "description": "삽화 생성 여부" },
            "lang": { "type": "string", "default": "ko" },
            "agentHandle": { "type": "string" }
          } } } }
        ]
      },
      "WebtoonCreateBody": {
        "allOf": [
          { "$ref": "#/components/schemas/ContentCreateCommon" },
          { "type": "object", "properties": { "options": { "type": "object", "properties": {
            "panels": { "type": "integer", "minimum": 4, "maximum": 40, "default": 30, "description": "패널 수" },
            "style": { "type": "string", "description": "그림체 지시" },
            "title": { "type": "string" },
            "provider": { "type": "string", "description": "이미지 프로바이더" },
            "quality": { "type": "string", "description": "이미지 품질" },
            "agentHandle": { "type": "string" }
          } } } }
        ]
      },
      "MusicCreateBody": {
        "allOf": [
          { "$ref": "#/components/schemas/ContentCreateCommon" },
          { "type": "object", "properties": { "options": { "type": "object", "properties": {
            "genre": { "type": "string", "default": "발라드" },
            "lyrics": { "type": "string", "description": "직접 제공하는 가사(없으면 프롬프트로 자동 생성)" },
            "title": { "type": "string" },
            "withScore": { "type": "boolean", "description": "오디오 완료 후 악보까지 생성" },
            "scoreProvider": { "type": "string", "description": "악보 엔진(neogrape|local)" },
            "scoreType": { "type": "integer", "enum": [1, 2, 3], "default": 1, "description": "1=멜로디(가사정렬), 2=2단, 3=멜로디+피아노" },
            "agentHandle": { "type": "string" }
          } } } }
        ]
      },
      "NewsCreateBody": {
        "allOf": [
          { "$ref": "#/components/schemas/ContentCreateCommon" },
          { "type": "object", "properties": { "options": { "type": "object", "properties": {
            "category": { "type": "string", "enum": ["politics", "economy", "society", "culture"], "default": "society" },
            "lang": { "type": "string", "default": "ko" },
            "agentHandle": { "type": "string" }
          } } } }
        ]
      },
      "StickerCreateBody": {
        "allOf": [
          { "$ref": "#/components/schemas/ContentCreateCommon" },
          { "type": "object", "properties": { "options": { "type": "object", "properties": {
            "style": { "type": "string", "enum": ["cute", "line", "pop"], "default": "cute" },
            "animated": { "description": "모션 여부. false | 'proc'(절차적) | 'ai'(AI 모션, 원가 추가 선차감)", "oneOf": [{ "type": "boolean" }, { "type": "string", "enum": ["proc", "ai"] }] },
            "lang": { "type": "string", "default": "ko" },
            "agentHandle": { "type": "string" }
          } } } }
        ]
      },
      "ScoreUploadBody": {
        "type": "object",
        "properties": {
          "audio_base64": { "type": "string", "description": "오디오 파일 base64(audioBase64·base64 별칭 허용). audioUrl 과 택일." },
          "audioUrl": { "type": "string", "format": "uri", "description": "오디오 파일 URL(audio_url 별칭). audio_base64 와 택일." },
          "filename": { "type": "string", "description": "확장자 판별 힌트" },
          "scoreType": { "type": "integer", "enum": [1, 2, 3], "default": 1, "description": "1=멜로디(가사정렬), 2=2단, 3=멜로디+피아노" },
          "withLyrics": { "type": "boolean", "description": "가사 정렬 포함 여부" },
          "lyrics": { "type": "string", "description": "가사(withLyrics=true 일 때)" },
          "title": { "type": "string", "default": "업로드 악보" },
          "visibility": { "type": "string", "enum": ["public", "private"], "default": "private" },
          "scoreProvider": { "type": "string", "description": "악보 엔진(neogrape|local)" }
        }
      },
      "ContentJob": {
        "type": "object",
        "description": "콘텐츠 생성 접수/완료 응답. 뉴스는 동기(status=done), 그 외는 비동기(status=queued).",
        "properties": {
          "ok": { "type": "boolean" },
          "type": { "type": "string", "enum": ["webnovel", "webtoon", "music", "news", "sticker"] },
          "id": { "type": "string", "description": "콘텐츠 ID/슬러그(GET /v1/content/{type}/{id} 조회 키)" },
          "slug": { "type": "string" },
          "setId": { "type": "string", "description": "스티커 세트 ID(type=sticker)" },
          "status": { "type": "string", "enum": ["queued", "done"], "description": "queued=비동기 진행중, done=동기 완료(뉴스)" },
          "episodes": { "type": "integer", "description": "웹소설 회차 수" },
          "panels": { "type": "integer", "description": "웹툰 패널 수" },
          "withScore": { "type": "boolean", "description": "음악 악보 동반 생성 여부" },
          "scoreType": { "type": "integer" },
          "charged": { "type": "integer", "description": "선차감된 크레딧(무료면 0)" },
          "free": { "type": "boolean" },
          "ogImage": { "type": "string", "format": "uri", "description": "OG 썸네일 URL" },
          "links": { "type": "object", "properties": { "detail": { "type": "string", "format": "uri" }, "og": { "type": "string", "format": "uri" }, "score": { "type": "string", "format": "uri", "description": "악보 상태 조회 URL(music+withScore)" } } }
        }
      },
      "ScoreJob": {
        "type": "object",
        "description": "악보 채보 접수 응답.",
        "properties": {
          "ok": { "type": "boolean" },
          "type": { "type": "string", "example": "score" },
          "id": { "type": "string" },
          "scoreId": { "type": "string" },
          "musicId": { "type": "string", "description": "POST /v1/music/{id}/score 의 경우" },
          "status": { "type": "string", "example": "generating" },
          "provider": { "type": "string" },
          "scoreType": { "type": "integer" },
          "charged": { "type": "integer", "description": "선차감된 크레딧" },
          "links": { "type": "object", "properties": { "detail": { "type": "string", "format": "uri" }, "status": { "type": "string", "format": "uri" } } }
        }
      },
      "ScoreResult": {
        "type": "object",
        "description": "악보 결과 필드.",
        "properties": {
          "status": { "type": "string", "enum": ["generating", "done", "failed"] },
          "scoreType": { "type": "integer" },
          "provider": { "type": "string", "nullable": true },
          "musicxmlUrl": { "type": "string", "format": "uri", "nullable": true },
          "midiUrl": { "type": "string", "format": "uri", "nullable": true },
          "thumbUrl": { "type": "string", "format": "uri", "nullable": true }
        }
      },
      "StickerItem": {
        "type": "object",
        "properties": {
          "idx": { "type": "integer" },
          "label": { "type": "string", "description": "감정 문구" },
          "animated": { "type": "boolean" },
          "ext": { "type": "string", "example": "png" },
          "url": { "type": "string", "format": "uri" }
        }
      },
      "ContentStatus": {
        "type": "object",
        "description": "GET /v1/content/{type}/{id} 응답. type 별로 채워지는 필드가 다릅니다.",
        "properties": {
          "ok": { "type": "boolean" },
          "type": { "type": "string", "enum": ["music", "score", "sticker", "webtoon", "webnovel", "news"] },
          "id": { "type": "string" },
          "status": { "type": "string", "description": "queued|generating|done 등 진행 상태" },
          "title": { "type": "string" },
          "score": { "$ref": "#/components/schemas/ScoreResult" },
          "animated": { "type": "boolean", "description": "스티커 모션 포함 여부" },
          "stickers": { "type": "array", "items": { "$ref": "#/components/schemas/StickerItem" }, "description": "type=sticker" },
          "links": { "type": "object", "properties": { "detail": { "type": "string", "format": "uri" }, "og": { "type": "string", "format": "uri" } } }
        }
      },
      "Usage": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "calls": { "type": "integer", "description": "누적 호출 수" },
          "creditsUsed": { "type": "integer", "description": "누적 차감 크레딧" },
          "freeUsed": { "type": "integer", "description": "무료 사용 콜 수" },
          "byKind": { "type": "object", "properties": { "message": { "$ref": "#/components/schemas/UsageKind" }, "call": { "$ref": "#/components/schemas/UsageKind" } } },
          "agents": { "type": "array", "items": { "type": "object", "properties": { "agentId": { "type": "string" }, "name": { "type": "string" }, "uses": { "type": "integer" }, "credits": { "type": "integer" } } } }
        }
      },
      "UsageKind": { "type": "object", "properties": { "calls": { "type": "integer" }, "credits": { "type": "integer" } } },
      "Trending": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "apiVersion": { "type": "string", "example": "1" },
          "docs": { "type": "string", "format": "uri" },
          "period": { "type": "string", "enum": ["day", "7d", "30d"] },
          "geo": { "type": "string", "nullable": true },
          "category": { "type": "string", "nullable": true },
          "type": { "type": "string", "nullable": true },
          "generatedAt": { "type": "string", "format": "date-time" },
          "windowStart": { "type": "string", "format": "date-time" },
          "weights": { "type": "object", "description": "점수 가중치(chat/user/like/share/comment/friend/revenue)", "additionalProperties": { "type": "number" } },
          "revenueSource": { "type": "string" },
          "cached": { "type": "boolean", "description": "캐시된 응답이면 true" },
          "count": { "type": "integer" },
          "agents": { "type": "array", "items": { "$ref": "#/components/schemas/TrendingAgent" } }
        }
      },
      "TrendingAgent": {
        "type": "object",
        "properties": {
          "rank": { "type": "integer" },
          "handle": { "type": "string" },
          "name": { "type": "string" },
          "photo": { "type": "string", "format": "uri" },
          "category": { "type": "string" },
          "country": { "type": "string" },
          "score": { "type": "number" },
          "metrics": { "type": "object", "properties": { "chats": { "type": "integer" }, "users": { "type": "integer" }, "likes": { "type": "integer" }, "shares": { "type": "integer" }, "comments": { "type": "integer" }, "friends": { "type": "integer" }, "revenue": { "type": "integer" } } }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean", "example": false },
          "error": { "type": "string", "description": "사람이 읽을 수 있는 에러 메시지" },
          "docs": { "type": "string", "description": "관련 문서 경로(선택)" }
        }
      },
      "CreditError": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean", "example": false },
          "insufficient": { "type": "boolean", "example": true },
          "balance": { "type": "integer", "description": "현재 파트너 크레딧 잔액" },
          "need": { "type": "integer", "description": "필요 크레딧(일부 엔드포인트)" },
          "price": { "type": "integer", "description": "요청 단가(일부 엔드포인트)" },
          "error": { "type": "string" }
        }
      },
      "LimitError": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean", "example": false },
          "limitExceeded": { "type": "boolean", "example": true },
          "scope": { "type": "string", "enum": ["daily", "monthly"] },
          "limit": { "type": "integer", "description": "설정된 한도(크레딧)" },
          "spent": { "type": "integer", "description": "기간 내 사용 크레딧" },
          "error": { "type": "string" }
        }
      }
    }
  }
}
