openapi: 3.1.0
info:
  title: QuickPronounce API
  version: "1.0.0"
  summary: English dictionary, phonetics, and pronunciation-audio REST API.
  description: |
    A REST API for English dictionary data: definitions, examples, IPA
    pronunciations (US and UK), anglicized syllable breakdowns with stress,
    synonyms, antonyms, and synthesized pronunciation audio (Google WaveNet).

    No API key is required for the dictionary and pronunciation endpoints. An
    optional `X-API-Key` header raises rate limits. CORS is open, so the API
    can be called directly from a browser.

    Public testing phase: no SLA. Rate limits are per IP, fair use.
    Freemium: the free tier stays free, and paid plans for higher request
    volumes are planned as usage grows.
  contact:
    name: QuickPronounce
    url: https://www.quickpronounce.site/contact
  license:
    name: Wiktionary content under CC BY-SA 4.0 (rewritten/enriched), see docs
    url: https://www.quickpronounce.site/api
servers:
  - url: https://api.quickpronounce.site
    description: Production

tags:
  - name: meta
    description: Health and key info
  - name: dictionary
    description: Lexical data
  - name: pronunciation
    description: Lexical data plus synthesized audio

paths:
  /:
    get:
      tags: [meta]
      operationId: getIndex
      summary: API index (also served at /v1)
      security: []
      responses:
        "200":
          description: Name, docs link, endpoint list, and rate limits
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, const: true }
                  data: { type: object }

  /openapi.yaml:
    get:
      tags: [meta]
      operationId: getOpenapi
      summary: This spec, served by the API
      security: []
      responses:
        "200":
          description: The OpenAPI document
          content:
            application/yaml:
              schema: { type: string }

  /v1/health:
    get:
      tags: [meta]
      operationId: getHealth
      summary: Liveness check
      security: []
      responses:
        "200":
          description: Service is up
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, const: true }
                  data:
                    type: object
                    properties:
                      status: { type: string, example: ok }
                      timestamp: { type: integer, example: 1787900000000 }

  /v1/dictionary/{word}:
    get:
      tags: [dictionary]
      operationId: getDictionaryWord
      summary: Full lexical record for one word
      security: []
      parameters:
        - $ref: "#/components/parameters/Word"
      responses:
        "200":
          description: Word found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, const: true }
                  data: { $ref: "#/components/schemas/DictionaryData" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /v1/pronunciation/{word}:
    get:
      tags: [pronunciation]
      operationId: getPronunciationWord
      summary: Lexical record plus a synthesized MP3 pronunciation clip
      security: []
      parameters:
        - $ref: "#/components/parameters/Word"
        - name: accent
          in: query
          required: false
          schema: { type: string, enum: [us, uk], default: us }
        - name: gender
          in: query
          required: false
          schema: { type: string, enum: [male, female], default: male }
        - name: speed
          in: query
          required: false
          schema: { type: string, enum: [slow, normal, fast], default: normal }
      responses:
        "200":
          description: Word found, audio synthesized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, const: true }
                  data: { $ref: "#/components/schemas/PronunciationData" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "500": { $ref: "#/components/responses/ServerError" }

  /v1/me:
    get:
      tags: [meta]
      operationId: getMe
      summary: Usage for the calling API key
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Key is valid
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, const: true }
                  data:
                    type: object
                    properties:
                      name: { type: string }
                      dailyLimit: { type: integer, example: 1000 }
                      usedToday: { type: integer, example: 3 }
                      remainingToday: { type: integer, example: 997 }
        "401": { $ref: "#/components/responses/Unauthorized" }

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Optional on dictionary/pronunciation (raises limits), required on /v1/me.

  parameters:
    Word:
      name: word
      in: path
      required: true
      description: Word to look up. Case-insensitive, trimmed. Letters, marks, digits, spaces, and ' - . , & allowed, up to 80 chars.
      schema: { type: string, example: serendipity }

  schemas:
    Syllable:
      type: object
      properties:
        text: { type: string, example: puhl }
        stress:
          type: integer
          enum: [0, 1, 2]
          description: 1 primary, 2 secondary, 0 unstressed
    Entry:
      type: object
      properties:
        partOfSpeech: { type: [string, "null"], example: Adjective }
        definitions:
          type: array
          items: { type: string }
        examples:
          type: array
          items: { type: string }
    DictionaryData:
      type: object
      properties:
        word: { type: string, example: wonderful }
        entries:
          type: array
          items: { $ref: "#/components/schemas/Entry" }
        defaultPartOfSpeech: { type: [string, "null"], example: Adjective }
        synonyms:
          type: array
          items: { type: string }
        antonyms:
          type: array
          items: { type: string }
        phonetics:
          type: object
          properties:
            us: { type: [string, "null"], example: "/ˈwʌndɚfl/" }
            uk: { type: [string, "null"], example: "/ˈwʌndəfl/" }
        syllables:
          type: object
          properties:
            us:
              type: array
              items: { $ref: "#/components/schemas/Syllable" }
            uk:
              type: array
              items: { $ref: "#/components/schemas/Syllable" }
    PronunciationData:
      type: object
      properties:
        word: { type: string, example: wonderful }
        meta:
          type: object
          properties:
            accent: { type: string, example: us }
            gender: { type: string, example: female }
            speed: { type: string, example: fast }
        phonetics:
          type: object
          properties:
            us: { type: [string, "null"] }
            uk: { type: [string, "null"] }
        syllables:
          type: object
          properties:
            us:
              type: array
              items: { $ref: "#/components/schemas/Syllable" }
            uk:
              type: array
              items: { $ref: "#/components/schemas/Syllable" }
        audio:
          type: object
          properties:
            content: { type: string, description: base64-encoded MP3 }
            encoding: { type: string, const: base64 }
            format: { type: string, const: mp3 }
    Error:
      type: object
      properties:
        success: { type: boolean, const: false }
        message: { type: string }

  responses:
    BadRequest:
      description: Invalid query parameter
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, message: "accent must be 'us' or 'uk'." }
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, message: "Missing X-API-Key header." }
    NotFound:
      description: Word or route not found
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, message: "Word not found in the QuickPronounce dictionary." }
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema: { type: integer }
        RateLimit-Remaining:
          schema: { type: integer }
      content:
        application/json:
          schema:
            allOf:
              - $ref: "#/components/schemas/Error"
              - type: object
                properties:
                  retryAfter: { type: integer, example: 60 }
    ServerError:
      description: Audio synthesis failed
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, message: "Failed to generate pronunciation audio. Please try again." }
