openapi: "3.0.3"
info:
  title: CSMS External API (B2B)
  version: "1.2.0"
  description: |
    CSMS 외부 제공(B2B) 조회 전용 API. 파트너/CPO 시스템의 server-to-server 연동용입니다.

    ## 인증
    발급받은 API 키를 모든 요청의 `x-api-key` 헤더에 담아 호출합니다.
    키는 CPO 단위로 발급되며, **모든 응답은 해당 CPO 소유 자원으로 자동 스코프**됩니다.

    ## 사용량 제한 (기본 플랜)
    - 초당 10 요청 (burst 20)
    - 일 100,000 요청
    초과 시 `429 Too Many Requests`가 반환됩니다.

    ## 오류 응답 형태
    - 게이트웨이 단(키 누락/무효/쿼터 초과): `{"message": "..."}`
    - 백엔드 단(키 매핑 차단 403, 잘못된 파라미터 400, 대상 없음 404): 아래 각 응답 스키마 참조

    ## 페이지네이션
    목록 API(stations, chargers, transactions)는 공통으로 `page`(0-base, 기본 0) /
    `size`(기본 50, 최대 200) 파라미터를 받고, `items` + `page`/`size`/`totalElements`/`totalPages`
    메타를 포함해 응답합니다.

    ## 변경 정책
    URL은 `/api/v1/ext/` 고정입니다. 하위 호환을 깨지 않는 범위(필드 추가 등)에서만 변경됩니다.
servers:
  - url: https://external-api.oasis-arca.com
security:
  - ApiKeyAuth: []
tags:
  - name: stations
    description: 충전소 조회
  - name: chargers
    description: 충전기·커넥터 상태 조회
  - name: transactions
    description: 충전 트랜잭션 내역 조회

paths:
  /api/v1/ext/stations:
    get:
      tags: [stations]
      summary: 충전소 목록
      description: 키에 매핑된 CPO가 소유한 충전소 목록 (이름 오름차순, 페이지네이션).
      operationId: listStations
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Size"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PageMeta"
                  - type: object
                    required: [items]
                    properties:
                      items:
                        type: array
                        items:
                          $ref: "#/components/schemas/Station"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /api/v1/ext/stations/{stationId}:
    get:
      tags: [stations]
      summary: 충전소 상세 (소속 충전기 포함)
      description: 충전소 기본 정보에 더해 해당 충전소 소속 충전기 목록(커넥터 상태 포함)을 반환합니다.
      operationId: getStation
      parameters:
        - name: stationId
          in: path
          required: true
          schema: { type: integer, format: int64 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StationDetail"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /api/v1/ext/chargers:
    get:
      tags: [chargers]
      summary: 충전기 목록 (커넥터 상태 포함)
      description: 키에 매핑된 CPO가 소유한 충전기 목록 (ID 오름차순, 페이지네이션). 각 충전기에 커넥터별 상태가 포함됩니다.
      operationId: listChargers
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Size"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PageMeta"
                  - type: object
                    required: [items]
                    properties:
                      items:
                        type: array
                        items:
                          $ref: "#/components/schemas/Charger"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /api/v1/ext/chargers/{chargerId}:
    get:
      tags: [chargers]
      summary: 충전기 상세 (커넥터 상태 포함)
      operationId: getCharger
      parameters:
        - name: chargerId
          in: path
          required: true
          schema: { type: integer, format: int64 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Charger"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /api/v1/ext/transactions:
    get:
      tags: [transactions]
      summary: 충전 트랜잭션 내역 (기간 필터, 페이지네이션)
      description: |
        충전 시작 시각(`startTime`) 기준 내림차순. 기간은 `[from, to)` 반개구간으로 필터됩니다.
        - `from`/`to` 생략 시: `to`=현재 시각, `from`=`to`−30일
        - 최대 조회 기간: 92일 (초과 시 400)
      operationId: listTransactions
      parameters:
        - name: from
          in: query
          required: false
          description: 조회 시작 시각 (ISO-8601, 예 `2026-06-01T00:00:00Z`)
          schema: { type: string, format: date-time }
        - name: to
          in: query
          required: false
          description: 조회 종료 시각 (ISO-8601, exclusive)
          schema: { type: string, format: date-time }
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Size"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PageMeta"
                  - type: object
                    required: [items]
                    properties:
                      items:
                        type: array
                        items:
                          $ref: "#/components/schemas/Transaction"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

components:
  parameters:
    Page:
      name: page
      in: query
      required: false
      description: 0부터 시작하는 페이지 번호
      schema: { type: integer, minimum: 0, default: 0 }
    Size:
      name: size
      in: query
      required: false
      description: 페이지 크기 (최대 200)
      schema: { type: integer, minimum: 1, maximum: 200, default: 50 }

  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: CPO 단위로 발급된 API 키

  schemas:
    PageMeta:
      type: object
      required: [page, size, totalElements, totalPages]
      properties:
        page: { type: integer, description: 현재 페이지(0-base) }
        size: { type: integer, description: 페이지 크기 }
        totalElements: { type: integer, format: int64, description: 전체 건수 }
        totalPages: { type: integer, description: 전체 페이지 수 }

    Station:
      type: object
      required: [id, name, address, latitude, longitude, createdAt]
      properties:
        id: { type: integer, format: int64, example: 103 }
        sid:
          type: string
          nullable: true
          description: 환경부 충전소 ID(SID). CPO별 6자리
          example: "100001"
        name: { type: string, example: 바이온에버 본사 }
        address: { type: string, example: 경기 과천시 과천대로7나길 60 }
        detailAddress: { type: string, nullable: true, example: C동 505호 }
        postalCode: { type: string, nullable: true, example: "13840" }
        latitude: { type: number, example: 37.432265 }
        longitude: { type: number, example: 126.994741 }
        createdAt:
          type: string
          description: 등록 시각 (ISO-8601 local date-time, UTC 기준)
          example: "2026-06-15T05:16:07.760511"

    StationDetail:
      allOf:
        - $ref: "#/components/schemas/Station"
        - type: object
          required: [chargers]
          properties:
            chargers:
              type: array
              description: 해당 충전소 소속 충전기 목록 (ID 오름차순, 삭제 충전기 제외)
              items:
                $ref: "#/components/schemas/Charger"

    Charger:
      type: object
      required: [id, online, availability, ocppVersion, connectors]
      properties:
        id: { type: integer, format: int64, example: 3004 }
        stationId: { type: integer, format: int64, nullable: true, example: 103 }
        identity:
          type: string
          nullable: true
          description: OCPP 충전기 식별자
          example: TEST0001
        online: { type: boolean, description: 실시간 접속 여부 }
        availability:
          type: string
          description: 충전기 가용 상태
          enum: [Available, Unavailable, Faulted]
        ocppVersion: { type: string, example: "1.6" }
        currentFirmwareVersion: { type: string, nullable: true, example: A00.00.01 }
        lastHeartbeat:
          type: string
          format: date-time
          nullable: true
          example: "2026-07-10T06:00:53.706155Z"
        connectors:
          type: array
          items:
            $ref: "#/components/schemas/Connector"

    Connector:
      type: object
      required: [connectorId, status]
      properties:
        connectorId: { type: integer, description: 충전기 내 커넥터 번호(1-base), example: 1 }
        status:
          type: string
          description: OCPP 1.6 ChargePointStatus
          enum: [Available, Preparing, Charging, SuspendedEVSE, SuspendedEV, Finishing, Reserved, Unavailable, Faulted]
        lastStatusTime:
          type: string
          format: date-time
          nullable: true
          example: "2026-07-10T06:02:55.678207Z"

    Transaction:
      type: object
      required: [id, chargerId, connectorId, startTime]
      properties:
        id: { type: integer, format: int64, description: 트랜잭션 ID }
        chargerId: { type: integer, format: int64, example: 3004 }
        chargerIdentity:
          type: string
          nullable: true
          description: OCPP 충전기 식별자
          example: TEST0001
        connectorId: { type: integer, description: 충전기 내 커넥터 번호(1-base), example: 1 }
        startTime: { type: string, format: date-time, example: "2026-07-11T02:10:00Z" }
        stopTime:
          type: string
          format: date-time
          nullable: true
          description: 종료 시각 (진행 중이면 null)
        consumedWh:
          type: integer
          format: int64
          nullable: true
          description: 충전량(Wh). 미터값 미확정 시 null
          example: 5000
        totalFee:
          type: number
          nullable: true
          description: 요금(KRW)
          example: 1250.00
        reason:
          type: string
          nullable: true
          description: 종료 사유 (OCPP StopTransaction reason)
          example: Remote

    BackendError:
      type: object
      properties:
        error: { type: string, example: "Station not found: 1" }

  responses:
    BadRequest:
      description: 잘못된 파라미터 (기간 92일 초과, size 범위 초과 등)
      content:
        application/json:
          schema: { $ref: "#/components/schemas/BackendError" }
    Unauthorized:
      description: 게이트웨이 내부 인증 실패 (일반적으로 발생하지 않음 — 발생 시 운영팀 문의)
    Forbidden:
      description: |
        API 키 누락/무효(게이트웨이, `{"message":"Forbidden"}`) 또는 키가 차단/미등록 상태(백엔드).
    NotFound:
      description: 대상 없음 (다른 CPO 소유 자원 조회 포함)
      content:
        application/json:
          schema: { $ref: "#/components/schemas/BackendError" }
    TooManyRequests:
      description: 사용량 제한 초과 (`{"message":"Too Many Requests"}`)
