# Saju API — OpenAPI 3.1 specification
#
# X-API-Key header auth follows the Swagger / Speakeasy 2026 pattern
# (https://swagger.io/docs/specification/v3_0/authentication/api-keys/,
#  https://www.speakeasy.com/openapi/security/security-schemes/security-api-key).

openapi: 3.1.0

info:
  title: Saju API
  version: 0.2.0
  summary: Korean Four Pillars of Destiny (Saju / Bazi) — REST API in 10 languages
  description: |
    B2B REST API for Korean astrology (사주팔자, Saju / Bazi). Calculate the
    Four Pillars from a solar birthdate, interpret Ten Gods and Yongshin (useful
    god), score two-person compatibility, and return daily fortune snapshots.

    **Authentication:** X-API-Key header. Get a free key at
    `POST /v1/keys/create`. Free tier: 100 requests/day, no credit card.

    **Languages:** ko, en, ja, zh, es, pt-br, vi, id, hi, th.
  contact:
    name: KunStudio
    url: https://saju-api.pages.dev
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary

servers:
  - url: https://saju-api.pages.dev
    description: Production

tags:
  - name: Saju
    description: Four-pillar calculation and interpretation
  - name: Compatibility
    description: Two-person 궁합 score
  - name: Daily
    description: Daily fortune snapshot (cacheable)
  - name: Account
    description: API key issuance and usage

security:
  - ApiKeyAuth: []

paths:
  /api/v1/calculate:
    post:
      tags: [Saju]
      summary: Calculate the Four Pillars from a solar birthdate
      operationId: calculate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BirthRequest'
            examples:
              tigerMale:
                value: { year: 1990, month: 5, day: 15, hour: 14, gender: M, lang: en }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalculateResponse'
        '400': { $ref: '#/components/responses/InvalidInput' }
        '401': { $ref: '#/components/responses/InvalidApiKey' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/v1/interpret:
    post:
      tags: [Saju]
      summary: Full interpretation with Ten Gods, Yongshin, Daeun, and i18n summaries
      operationId: interpret
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BirthRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InterpretResponse'
        '400': { $ref: '#/components/responses/InvalidInput' }
        '401': { $ref: '#/components/responses/InvalidApiKey' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/v1/compatibility:
    post:
      tags: [Compatibility]
      summary: Two-person 궁합 compatibility score (0..100)
      operationId: compatibility
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [person_a, person_b]
              properties:
                person_a: { $ref: '#/components/schemas/BirthRequest' }
                person_b: { $ref: '#/components/schemas/BirthRequest' }
                lang:     { $ref: '#/components/schemas/LangEnum' }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompatibilityResponse'
        '400': { $ref: '#/components/responses/InvalidInput' }
        '401': { $ref: '#/components/responses/InvalidApiKey' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/v1/daily:
    get:
      tags: [Daily]
      summary: Daily fortune snapshot for a given Day Master + date
      operationId: daily
      parameters:
        - name: day_master
          in: query
          required: true
          schema: { type: string, example: '갑' }
          description: Korean stem character (갑..계) or English alias (wood_yang, water_yin, ...)
        - name: date
          in: query
          required: false
          schema: { type: string, format: date, example: '2026-05-28' }
        - name: lang
          in: query
          required: false
          schema: { $ref: '#/components/schemas/LangEnum' }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DailyResponse'
        '400': { $ref: '#/components/responses/InvalidInput' }
        '401': { $ref: '#/components/responses/InvalidApiKey' }

  /api/v1/keys/create:
    post:
      tags: [Account]
      summary: Issue a new API key (free tier self-serve; paid tiers require token)
      operationId: createKey
      security: []  # public, no key needed to mint a free key
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email:
                  type: string
                  format: email
                  example: dev@yourcompany.io
                tier:
                  $ref: '#/components/schemas/TierEnum'
                tier_token:
                  type: string
                  description: Required if tier != free. Provided by checkout webhook or admin.
      responses:
        '200':
          description: OK — key issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateKeyResponse'
        '400': { $ref: '#/components/responses/InvalidInput' }
        '401':
          description: paid tier requested without valid tier_token
        '409':
          description: email already has a key for this tier

  /api/v1/usage:
    get:
      tags: [Account]
      summary: Current usage and limits for the supplied API key
      operationId: usage
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageResponse'
        '401': { $ref: '#/components/responses/InvalidApiKey' }

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        Saju API key. Format: `sajuapi_<tier>_<32+ random chars>`.
        Get one at `POST /v1/keys/create`.

  responses:
    InvalidInput:
      description: Body or query validation failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InvalidApiKey:
      description: Missing or malformed X-API-Key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Daily quota for your tier has been exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

  schemas:
    LangEnum:
      type: string
      enum: [ko, en, ja, zh, es, pt-br, vi, id, hi, th]
      default: ko

    TierEnum:
      type: string
      enum: [free, dev, pro, enterprise]
      default: free

    BirthRequest:
      type: object
      required: [year, month, day, hour, gender]
      properties:
        year:   { type: integer, minimum: 1920, maximum: 2050, example: 1990 }
        month:  { type: integer, minimum: 1,    maximum: 12,   example: 5 }
        day:    { type: integer, minimum: 1,    maximum: 31,   example: 15 }
        hour:   { type: integer, minimum: -1,   maximum: 23,   example: 14, description: '-1 if unknown' }
        gender: { type: string,  enum: [M, F],                example: M }
        lang:   { $ref: '#/components/schemas/LangEnum' }

    Pillar:
      type: object
      properties:
        stem:         { type: string, example: '경' }
        branch:       { type: string, example: '오' }
        stem_hanja:   { type: string, example: '庚' }
        branch_hanja: { type: string, example: '午' }

    CalculateResponse:
      type: object
      properties:
        input: { type: object }
        pillars:
          type: object
          properties:
            year:  { $ref: '#/components/schemas/Pillar' }
            month: { $ref: '#/components/schemas/Pillar' }
            day:   { $ref: '#/components/schemas/Pillar' }
            hour:  { $ref: '#/components/schemas/Pillar', nullable: true }
        elements:
          type: object
          properties:
            wood:  { type: integer, example: 0 }
            fire:  { type: integer, example: 2 }
            earth: { type: integer, example: 2 }
            metal: { type: integer, example: 3 }
            water: { type: integer, example: 1 }
        day_master:
          type: object
          properties:
            stem:     { type: string, example: '경' }
            element:  { type: string, example: metal }
            polarity: { type: string, example: yang }
        zodiac: { type: string, example: horse }
        tier:   { $ref: '#/components/schemas/TierEnum' }
        remaining:
          type: integer
          nullable: true

    InterpretResponse:
      allOf:
        - $ref: '#/components/schemas/CalculateResponse'
        - type: object
          properties:
            ten_gods:     { type: array, items: { type: object } }
            life_stages:  { type: object }
            hidden_stems: { type: object }
            interactions: { type: object }
            yongshin:     { type: object }
            daeun:        { type: object }
            summary:
              type: object
              properties:
                lang: { $ref: '#/components/schemas/LangEnum' }
                pillars:
                  type: array
                  items:
                    type: object
                    properties:
                      position:       { type: string, example: year }
                      position_label: { type: string, example: 'Year Pillar' }
                      stem_god:       { type: string, example: 'Peer (Bi Jian) — independent, self-driven, values fairness' }
                      branch_god:     { type: string }
                yongshin:
                  type: object
                  properties:
                    strength:             { type: string, example: strong }
                    strength_label:       { type: string, example: 'Strong Day Master' }
                    useful_element:       { type: string, example: water }
                    useful_element_label: { type: string, example: 'Water' }
                    favored_element:      { type: string }
                    favored_element_label:{ type: string }
                    avoid_element:        { type: string }
                    avoid_element_label:  { type: string }

    CompatibilityResponse:
      type: object
      properties:
        score:
          type: integer
          minimum: 0
          maximum: 100
          example: 72
        breakdown:
          type: object
          properties:
            element_balance:     { type: integer }
            day_master_relation: { type: integer }
            branch_harmony:      { type: integer }
            branch_clash:        { type: integer }
        person_a: { type: object }
        person_b: { type: object }
        tier:      { $ref: '#/components/schemas/TierEnum' }
        remaining: { type: integer, nullable: true }

    DailyResponse:
      type: object
      properties:
        date:        { type: string, format: date, example: '2026-05-28' }
        day_pillar:  { $ref: '#/components/schemas/Pillar' }
        day_master:  { type: string, example: '갑' }
        score:       { type: integer, minimum: 0, maximum: 100, example: 67 }
        advice_key:
          type: string
          enum: [favor_action, favor_planning, rest_recover]
        advice_label: { type: string, example: 'Good day for planning' }
        lang:         { $ref: '#/components/schemas/LangEnum' }
        tier:         { $ref: '#/components/schemas/TierEnum' }
        remaining:    { type: integer, nullable: true }

    CreateKeyResponse:
      type: object
      properties:
        api_key:           { type: string, example: 'sajuapi_free_aBcD1234efGh5678ijKl9012mnOp3456' }
        tier:              { $ref: '#/components/schemas/TierEnum' }
        email:             { type: string, format: email }
        created_at:        { type: string, format: date-time }
        daily_limit:
          type: integer
          nullable: true
        rps:               { type: integer, example: 1 }
        monthly_price_usd: { type: integer, example: 0 }
        note:              { type: string }

    UsageResponse:
      type: object
      properties:
        api_key_prefix:    { type: string, example: 'sajuapi_pro_aBcD...' }
        tier:              { $ref: '#/components/schemas/TierEnum' }
        daily_limit:
          type: integer
          nullable: true
        used_today:        { type: integer, example: 42 }
        remaining:
          type: integer
          nullable: true
        reset_at_utc:      { type: string, format: date-time }
        rps:               { type: integer }
        monthly_price_usd: { type: integer }

    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:  { type: string, example: invalid_input }
        reason: { type: string, example: year_out_of_range }
        tier:   { type: string }
        limit:  { type: integer }
