openapi: 3.1.0
info:
  title: 이 세상의 모든 API Catalog API
  version: 1.0.0-rc.19
  description: API 검색, 신뢰 단계, 목적별 추천, 랭킹, 컬렉션과 카탈로그 릴리스를 제공하는 읽기 중심 API입니다.
servers:
  - url: /
paths:
  /api/v1/search:
    get:
      summary: API 검색
      parameters:
        - {name: q, in: query, schema: {type: string, maxLength: 160}}
        - {name: category, in: query, schema: {type: string}}
        - {name: auth, in: query, schema: {type: string}}
        - {name: protocol, in: query, schema: {type: string}}
        - {name: pricing, in: query, schema: {type: string, enum: [free, freemium, paid, usage, contact, unknown]}}
        - {name: official, in: query, schema: {type: string, enum: ['0','1']}}
        - {name: sort, in: query, schema: {type: string, enum: [relevance, quality, popular, recent, name]}}
        - {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
        - {name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 24}}
      responses:
        '200':
          description: 검색 결과
          content:
            application/json:
              schema: {$ref: '#/components/schemas/SearchResponse'}
  /api/v1/apis/{slug}:
    get:
      summary: API 상세 조회
      parameters:
        - {name: slug, in: path, required: true, schema: {type: string}}
      responses:
        '200':
          description: API 상세
          content:
            application/json:
              schema:
                type: object
                required: [item]
                properties:
                  item: {$ref: '#/components/schemas/ApiDetail'}
        '404': {$ref: '#/components/responses/NotFound'}
  /api/v1/apis/{slug}/engagement:
    get:
      summary: API 제품의 30일 이용 빈도와 별표 집계
      parameters:
        - {name: slug, in: path, required: true, schema: {type: string}}
      responses:
        '200':
          description: API 제품 단위 집계. SDK 저장소 별 수와 분리됩니다.
          content:
            application/json:
              schema:
                type: object
                properties:
                  item: {$ref: '#/components/schemas/ApiEngagement'}
        '404': {$ref: '#/components/responses/NotFound'}
    post:
      summary: 동일 출처에서 API 제품 이용 이벤트 또는 별표 저장
      parameters:
        - {name: slug, in: path, required: true, schema: {type: string}}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                action: {type: string, enum: [star]}
                eventType: {type: string, enum: [detail_view, docs_click, quickstart_copy, comparison_add]}
      responses:
        '200': {description: 갱신된 집계}
        '503': {description: 데이터베이스 또는 해시 솔트가 연결되지 않음}
    delete:
      summary: 현재 방문자의 API 제품 별표 해제
      parameters:
        - {name: slug, in: path, required: true, schema: {type: string}}
      responses:
        '200': {description: 갱신된 집계}
        '503': {description: 데이터베이스 또는 해시 솔트가 연결되지 않음}
  /api/v1/categories:
    get:
      summary: 카테고리 목록
      responses:
        '200':
          description: 카테고리 목록
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items:
                    type: array
                    items: {$ref: '#/components/schemas/Category'}
  /api/v1/catalog-status:
    get:
      summary: 검증 카탈로그 집계 현황
      responses:
        '200':
          description: 후보·고유 제품·공개 제품과 검증 운영 지표
          content:
            application/json:
              schema:
                type: object
                required: [status]
                properties:
                  status: {$ref: '#/components/schemas/CatalogStatus'}
  /api/v1/decision-profiles:
    get:
      summary: API 선택 평가 프로필
      responses:
        '200': {description: 운영 안정성·빠른 연동·비용 효율·엔터프라이즈 프로필}
  /api/v1/recommendations:
    post:
      summary: 목적과 제약조건에 따른 API 추천
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                profileSlug: {type: string}
                constraints: {type: object}
                limit: {type: integer, minimum: 1, maximum: 50}
      responses:
        '200': {description: 근거 충족률과 신뢰도를 포함한 추천 결과}
  /api/v1/pricing/estimate:
    post:
      summary: 월 사용량 기준 예상 비용
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [slug, monthlyRequests]
              properties:
                slug: {type: string}
                monthlyRequests: {type: integer, minimum: 0}
      responses:
        '200': {description: 정규화된 가격 근거 기반 예상 비용}
  /api/v1/rankings:
    get:
      summary: 목적별 API 랭킹 목록
      responses:
        '200': {description: 랭킹 목록}
  /api/v1/rankings/{slug}:
    get:
      summary: 목적별 API 랭킹 상세
      parameters:
        - {name: slug, in: path, required: true, schema: {type: string}}
      responses:
        '200': {description: 근거 기반 랭킹}
        '404': {$ref: '#/components/responses/NotFound'}
  /api/v1/collections:
    get:
      summary: 검증 조건별 컬렉션 목록
      responses:
        '200': {description: 컬렉션 목록}
  /api/v1/collections/{slug}:
    get:
      summary: 컬렉션 상세
      parameters:
        - {name: slug, in: path, required: true, schema: {type: string}}
      responses:
        '200': {description: 컬렉션 API 목록}
        '404': {$ref: '#/components/responses/NotFound'}
  /api/v1/releases:
    get:
      summary: 카탈로그 릴리스 목록
      responses:
        '200': {description: 불변 카탈로그 릴리스 목록}
  /api/v1/releases/{version}:
    get:
      summary: 카탈로그 릴리스 상세
      parameters:
        - {name: version, in: path, required: true, schema: {type: string}}
      responses:
        '200': {description: 릴리스 검사와 감사 이벤트}
        '404': {$ref: '#/components/responses/NotFound'}
  /api/v1/index-releases:
    get:
      summary: 검색 인덱스 릴리스 목록
      responses:
        '200': {description: 검색 가능한 API 인덱스 버전 목록}
  /api/v1/index-releases/{version}:
    get:
      summary: 검색 인덱스 릴리스 상세
      parameters:
        - {name: version, in: path, required: true, schema: {type: string}}
      responses:
        '200': {description: 검색 인덱스 무결성 검사와 통계}
        '404': {$ref: '#/components/responses/NotFound'}
  /api/v1/health:
    get:
      summary: 서비스 상태
      responses:
        '200':
          description: 정상 또는 개발 fallback 상태
        '503':
          description: 데이터베이스 장애
  /api/v1/benchmarks:
    get:
      summary: 동일 조건 API 벤치마크 결과
      parameters:
        - in: query
          name: scenario
          schema:
            type: string
      responses:
        '200':
          description: 벤치마크 결과와 방법론 버전
          content:
            application/json:
              schema:
                type: object
                properties:
                  methodology:
                    type: string
                  items:
                    type: array
                    items:
                      type: object

  /api/v1/directories:
    get:
      summary: 산업·국가·기능 디렉터리
      parameters:
        - in: query
          name: kind
          schema: {type: string, enum: [industry, country, capability]}
      responses:
        '200': {description: 분류 디렉터리와 API 수}
  /api/v1/scale-status:
    get:
      summary: Stage 24 카탈로그 규모와 집계 경계
      responses:
        '200':
          description: 후보·identity unique·qualified_unique 제품과 분리 엔티티의 현재 정본
          content:
            application/json:
              schema:
                allOf:
                  - {$ref: '#/components/schemas/Stage24PublicStatus'}
                  - {type: object, required: [surface], properties: {surface: {type: string, const: catalog_scale}}}
  /api/rights-requests:
    post:
      summary: 정보 수정·삭제·상표·라이선스·개인정보 요청 접수
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [requestType, subject, details, requesterName, requesterEmail, relationship]
              properties:
                requestType: {type: string, enum: [correction, removal, trademark, license, privacy, other]}
                subject: {type: string, minLength: 5, maxLength: 160}
                details: {type: string, minLength: 20, maxLength: 8000}
                requesterName: {type: string}
                requesterEmail: {type: string, format: email}
                relationship: {type: string}
                apiSlug: {type: string}
                evidenceUrls: {type: array, maxItems: 10, items: {type: string, format: uri}}
      responses:
        '201': {description: 권리 요청 접수 완료}
        '400': {description: 입력 오류}
        '403': {description: same-origin 검증 실패}
        '429': {description: 요청 제한}
        '503': {description: 운영 DB 미연결, code=PERSISTENCE_UNAVAILABLE}

  /api/submissions:
    post:
      summary: API 등록 제안 접수
      description: same-origin 요청과 운영 DB가 필요하며, 저장소 미연결 상태에서는 접수 번호를 만들지 않는다.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [apiName, docsUrl, submitterEmail, relationship]
              properties:
                apiName: {type: string, minLength: 2}
                providerName: {type: string}
                websiteUrl: {type: string, format: uri}
                docsUrl: {type: string, format: uri}
                specUrl: {type: string, format: uri}
                submitterEmail: {type: string, format: email}
                relationship: {type: string, enum: [user, developer, owner, other]}
                note: {type: string}
      responses:
        '201': {description: 등록 제안 저장 완료}
        '400': {description: 입력 오류}
        '403': {description: same-origin 검증 실패}
        '429': {description: 요청 제한}
        '503': {description: 운영 DB 미연결, code=PERSISTENCE_UNAVAILABLE}

  /api/provider-claims:
    post:
      summary: 공급자 소유권 확인 요청
      description: same-origin 요청과 운영 DB가 필요하다.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [providerSlug, claimantEmail, method]
              properties:
                providerSlug: {type: string, minLength: 1, maxLength: 180}
                claimantEmail: {type: string, format: email}
                method: {type: string, enum: [dns_txt, well_known, email]}
      responses:
        '200': {description: 확인 요청 생성 및 발송 대기 등록}
        '400': {description: 입력 또는 공급자 검증 오류}
        '403': {description: same-origin 검증 실패}
        '429': {description: 요청 제한}
        '503': {description: 운영 DB 미연결, code=PERSISTENCE_UNAVAILABLE}

  /api/provider-claims/{id}/verify:
    post:
      summary: 공급자 소유권 토큰 검사
      parameters:
        - in: path
          name: id
          required: true
          schema: {type: string, format: uuid}
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                token: {type: string}
      responses:
        '200': {description: 소유권 확인 완료}
        '400': {description: 토큰 또는 소유권 검증 오류}
        '403': {description: same-origin 검증 실패}
        '429': {description: 요청 제한}
        '503': {description: 운영 DB 미연결, code=PERSISTENCE_UNAVAILABLE}

  /api/v1/operations-status:
    get:
      summary: Stage 24 카탈로그와 프로덕션 운영 증거 상태
      operationId: getOperationsStatus
      responses:
        '200':
          description: 검증된 카탈로그 제공 증거와 아직 연결되지 않은 운영 관측 상태
          content:
            application/json:
              schema:
                allOf:
                  - {$ref: '#/components/schemas/Stage24PublicStatus'}
                  - {type: object, required: [surface, operations], properties: {surface: {type: string, const: operations}, operations: {type: object, additionalProperties: true}}}
  /api/v1/launch-readiness:
    get:
      summary: Stage 24 출시 준비도
      operationId: getLaunchReadiness
      responses:
        '200':
          description: 검증된 카탈로그 데이터와 DB·Docker·Playwright·프로덕션 미완료 게이트
          content:
            application/json:
              schema:
                allOf:
                  - {$ref: '#/components/schemas/Stage24PublicStatus'}
                  - {type: object, required: [surface, readiness], properties: {surface: {type: string, const: launch_readiness}, readiness: {type: object, additionalProperties: true}}}
  /api/v1/resilience-status:
    get:
      summary: Stage 24 복원력 증거 경계
      responses:
        '200':
          description: artifact 결정성과 실제 부하·DB 복구·Docker QA 미실행 상태
          content:
            application/json:
              schema:
                allOf:
                  - {$ref: '#/components/schemas/Stage24PublicStatus'}
                  - {type: object, required: [surface, resilience], properties: {surface: {type: string, const: resilience}, resilience: {type: object, additionalProperties: true}}}
  /api/v1/autopilot-status:
    get:
      summary: Stage 24 자동화 경계
      operationId: getAutopilotStatus
      responses:
        '200':
          description: 완료된 데이터 증거 게이트와 우회하지 않은 외부 운영 게이트
          content:
            application/json:
              schema:
                allOf:
                  - {$ref: '#/components/schemas/Stage24PublicStatus'}
                  - {type: object, required: [surface, automation], properties: {surface: {type: string, const: autopilot}, automation: {type: object, additionalProperties: true}}}
  /api/v1/catalog-growth:
    get:
      summary: Stage 24 전체 카탈로그 성장 현황
      responses:
        '200':
          description: 초기 Stage 14 표본이 아닌 현재 50,389개 보수적 제품 정본
          content:
            application/json:
              schema:
                allOf:
                  - {$ref: '#/components/schemas/Stage24PublicStatus'}
                  - {type: object, required: [surface, growth], properties: {surface: {type: string, const: catalog_growth}, growth: {type: object, additionalProperties: true}}}
  /api/v1/catalog-expansion:
    get:
      summary: 버전 고정 원본 기반 API 제품 카탈로그 확장 상태
      operationId: getCatalogExpansion
      responses:
        '200':
          description: 보수적 고유 API 제품, 분리 엔티티 수와 중복 검토 상태
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true

components:
  schemas:
    Stage24PublicStatus:
      type: object
      required: [schemaVersion, stage, release, status, gate, observedAt, catalog, exclusions, semantic, countingBoundary, quality, artifact, deployment]
      properties:
        schemaVersion: {type: string, const: stage24-public-status-v1}
        stage: {type: integer, const: 24}
        release: {type: string, const: 1.0.0-rc.17}
        status: {type: string, const: catalog_release_verified}
        gate: {type: string, const: catalog_verified_production_not_approved}
        observedAt: {type: string, format: date-time}
        documentedAt: {type: string, format: date-time}
        catalog:
          type: object
          required: [candidateProducts, identityUniqueProducts, qualifiedUniqueProducts, searchableProducts, apiVersions, endpoints, sdks, mcpServers, mcpServerVersions]
          properties:
            candidateProducts: {type: integer, const: 123054}
            identityUniqueProducts: {type: integer, const: 73624}
            qualifiedUniqueProducts: {type: integer, const: 50389}
            searchableProducts: {type: integer, const: 50389}
            apiVersions: {type: integer, const: 24590}
            endpoints: {type: integer, const: 167917}
            sdks: {type: integer, const: 0}
            mcpServers: {type: integer, const: 18644}
            mcpServerVersions: {type: integer, const: 18644}
        exclusions: {type: object, additionalProperties: true}
        semantic: {type: object, additionalProperties: true}
        countingBoundary: {type: object, additionalProperties: true}
        quality: {type: object, additionalProperties: true}
        artifact: {type: object, additionalProperties: true}
        deployment:
          type: object
          required: [productionReady, database, dockerQa, dedicatedPlaywright]
          properties:
            productionReady: {type: boolean, const: false}
            database: {type: string, const: not_run}
            dockerQa: {type: string, const: not_run}
            dedicatedPlaywright: {type: string, const: not_run}
          additionalProperties: true
    CatalogStatus:
      type: object
      required: [mode, candidateRecords, uniqueApiProducts, publicApiProducts, mcpServers, verifiedBundles, sourcePolicies, policyVersion, sourceBreakdown]
      properties:
        mode: {type: string, enum: [database, bundled]}
        candidateRecords: {type: integer, minimum: 0}
        uniqueApiProducts: {type: integer, minimum: 0}
        publicApiProducts: {type: integer, minimum: 0}
        mcpServers: {type: integer, minimum: 0}
        excludedRecords: {type: integer, minimum: 0}
        mergedRecords: {type: integer, minimum: 0}
        providers: {type: integer, minimum: 0}
        endpoints: {type: integer, minimum: 0}
        verifiedSources: {type: integer, minimum: 0}
        approvedProducts: {type: integer, minimum: 0}
        blockedProducts: {type: integer, minimum: 0}
        verifiedBundles: {type: integer, minimum: 0}
        sourcePolicies: {type: integer, minimum: 0}
        openClusters: {type: integer, minimum: 0}
        approvalEvents: {type: integer, minimum: 0}
        openAudits: {type: integer, minimum: 0}
        providerVerifiedProducts: {type: integer, minimum: 0}
        editorialVerifiedProducts: {type: integer, minimum: 0}
        autoVerifiedProducts: {type: integer, minimum: 0}
        indexedProducts: {type: integer, minimum: 0}
        discoverableProducts: {type: integer, minimum: 0}
        activeReleaseProducts: {type: integer, minimum: 0}
        observedAt: {type: [string, 'null'], format: date-time}
        policyVersion: {type: string}
        sourceBreakdown:
          type: array
          items:
            type: object
            required: [slug, name, candidateCount, countedCount, mcpCount]
            properties:
              slug: {type: string}
              name: {type: string}
              candidateCount: {type: integer, minimum: 0}
              countedCount: {type: integer, minimum: 0}
              mcpCount: {type: integer, minimum: 0}
    ApiSummary:
      type: object
      required: [id, slug, name, providerName, shortDescription, categorySlug, protocols, authMethods, officiality, pricingModel, endpointCount, qualityEvaluationStatus, discoveryStatus, discoveryScore]
      properties:
        id: {type: string}
        slug: {type: string}
        name: {type: string}
        providerName: {type: string}
        shortDescription: {type: string}
        categorySlug: {type: string}
        categoryNameKo: {type: string}
        tags: {type: array, items: {type: string}}
        protocols: {type: array, items: {type: string}}
        authMethods: {type: array, items: {type: string}}
        officiality: {type: string, enum: [official, community, unverified]}
        pricingModel: {type: string, enum: [free, freemium, paid, usage, contact, unknown]}
        endpointCount: {type: integer, minimum: 0}
        qualityScore: {type: [number, 'null']}
        qualityCoverageScore: {type: [number, 'null']}
        qualityConfidenceScore: {type: [number, 'null']}
        qualityEvaluationStatus: {type: string, enum: [verified, provisional, insufficient, stale, unrated]}
        discoveryStatus: {type: string, enum: [private, indexed, auto_verified, editorial_verified, provider_verified, blocked]}
        discoveryScore: {type: number, minimum: 0, maximum: 100}
        discoveryReasons: {type: array, items: {type: object}}
        discoveryAssessedAt: {type: [string, 'null'], format: date-time}
    ApiDetail:
      allOf:
        - {$ref: '#/components/schemas/ApiSummary'}
        - type: object
          required: [description, endpoints, sources, pricingPlans, quality, alternatives, engagement]
          properties:
            description: {type: string}
            endpoints: {type: array, items: {type: object}}
            sources: {type: array, items: {type: object}}
            pricingPlans: {type: array, items: {type: object}}
            quality: {type: object}
            alternatives: {type: array, items: {$ref: '#/components/schemas/ApiSummary'}}
            engagement: {$ref: '#/components/schemas/ApiEngagement'}
    ApiEngagement:
      type: object
      required: [dataStatus, persistenceMode, windowDays, detailViews, docsClicks, quickstartCopies, comparisonAdds, uniqueVisitors, starCount, measuredAt]
      properties:
        dataStatus: {type: string, enum: [measured, not_connected]}
        persistenceMode: {type: string, enum: [database, browser_preview]}
        windowDays: {type: integer, enum: [30]}
        detailViews: {type: [integer, 'null'], minimum: 0}
        docsClicks: {type: [integer, 'null'], minimum: 0}
        quickstartCopies: {type: [integer, 'null'], minimum: 0}
        comparisonAdds: {type: [integer, 'null'], minimum: 0}
        uniqueVisitors: {type: [integer, 'null'], minimum: 0}
        starCount: {type: [integer, 'null'], minimum: 0}
        measuredAt: {type: [string, 'null'], format: date-time}
        viewerStarred: {type: boolean}
    SearchResponse:
      type: object
      required: [items, total, page, limit, elapsedMs, mode]
      properties:
        items: {type: array, items: {$ref: '#/components/schemas/ApiSummary'}}
        total: {type: integer, minimum: 0}
        page: {type: integer, minimum: 1}
        limit: {type: integer, minimum: 1}
        elapsedMs: {type: number}
        mode: {type: string, enum: [database, demo]}
    Category:
      type: object
      required: [slug, nameKo, nameEn, count]
      properties:
        slug: {type: string}
        nameKo: {type: string}
        nameEn: {type: string}
        count: {type: integer, minimum: 0}
        descriptionKo: {type: [string, 'null']}
  responses:
    NotFound:
      description: 찾을 수 없음
      content:
        application/json:
          schema:
            type: object
            required: [error]
            properties:
              error: {type: string}
