openapi: 3.0.3
info:
  title: Voice Render API
  version: 1.0.0
  license:
    name: Proprietary
  description: |
    Convert text to speech as a single synthesis job (v1).
    Requests must use a Bearer access token and an idempotency key.
    Idempotency keys are retained for 24 hours; reuse the same key only when retrying the identical request body.

servers:
  - url: https://api.example.com
    description: Example host (anonymized for portfolio)

tags:
  - name: Voice jobs
    description: Create and manage voice synthesis jobs

paths:
  /v1/voice/jobs:
    post:
      tags:
        - Voice jobs
      operationId: createVoiceJob
      summary: Create a voice job
      description: |
        Validates the request, applies policy checks, reserves quota, and enqueues a rendering job.
        On success, returns a job_id for polling and audio retrieval.
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: |
            UUID (v4) used to prevent duplicate job creation during retries.
            Must be a valid UUID v4; missing or malformed values return 400 with invalid_input.
            Use a new UUID for each new logical job; reuse the same UUID only when retrying the identical request body (for example, after a timeout or network error).
          schema:
            type: string
            format: uuid
            example: 8b3f1c8e-3f2b-4ad2-9ef0-2a8c9b2a4f19
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateVoiceJobRequest'
            examples:
              default:
                summary: Example from API reference
                value:
                  input:
                    content: Hello, world.
                    content_type: text/plain
                  voice:
                    profile_id: voice_en_us_001
                    style: neutral
                    rate: 1.0
                  output:
                    format: pcm_s16le
                    sample_rate_hz: 24000
                  client:
                    request_tag: msg-001
      responses:
        '201':
          description: Job created; body contains the new job identifier.
          headers:
            X-Quota-Limit:
              description: Quota limit for the current billing period (plan cap).
              schema:
                type: string
            X-Quota-Remaining:
              description: Remaining quota for the current billing period after this request.
              schema:
                type: string
            X-Request-Id:
              description: Server-generated identifier for this request.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateVoiceJobResponse'
              examples:
                default:
                  value:
                    job_id: 5b2a5b7a-6c2d-4d40-9e7b-1f25c08e6a1f
        '400':
          description: |
            Request invalid (missing or malformed headers such as Idempotency-Key or Content-Type, invalid JSON body,
            missing fields, wrong types, constraint violations, plan limits, invalid UTF-8, or unsupported output parameters).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalid_input:
                  value:
                    error:
                      code: invalid_input
                      message: Request body is invalid.
                      retryable: false
        '401':
          description: Missing or invalid access token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                unauthenticated:
                  value:
                    error:
                      code: unauthenticated
                      message: Missing or invalid access token.
                      retryable: false
        '402':
          description: Insufficient quota to create the job.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                quota_exceeded:
                  value:
                    error:
                      code: quota_exceeded
                      message: Insufficient quota.
                      retryable: false
        '403':
          description: Content rejected by policy.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                policy_blocked:
                  value:
                    error:
                      code: policy_blocked
                      message: Content rejected by policy.
                      retryable: false
        '409':
          description: Same Idempotency-Key reused with a different request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                idempotency_conflict:
                  value:
                    error:
                      code: idempotency_conflict
                      message: Idempotency key conflict.
                      retryable: false
        '429':
          description: Too many requests; honor Retry-After when present.
          headers:
            Retry-After:
              description: Suggested delay before retry (when provided).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                rate_limited:
                  value:
                    error:
                      code: rate_limited
                      message: Too many requests. Please retry after a short delay.
                      retryable: true
        '503':
          description: Temporary service or upstream failure; retry with exponential backoff.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                service_unavailable:
                  value:
                    error:
                      code: service_unavailable
                      message: Service temporarily unavailable.
                      retryable: true

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Access token from your identity provider.

  schemas:
    CreateVoiceJobRequest:
      type: object
      additionalProperties: false
      required:
        - input
        - voice
        - output
      properties:
        input:
          $ref: '#/components/schemas/VoiceJobInput'
        voice:
          $ref: '#/components/schemas/VoiceJobVoice'
        output:
          $ref: '#/components/schemas/VoiceJobOutput'
        client:
          $ref: '#/components/schemas/VoiceJobClient'

    VoiceJobInput:
      type: object
      additionalProperties: false
      required:
        - content
        - content_type
      properties:
        content:
          type: string
          minLength: 1
          maxLength: 2000
          description: UTF-8 text to synthesize; avoid control characters outside standard whitespace.
        content_type:
          type: string
          enum:
            - text/plain

    VoiceJobVoice:
      type: object
      additionalProperties: false
      required:
        - profile_id
      properties:
        profile_id:
          type: string
          description: Voice profile identifier.
          example: voice_en_us_001
        style:
          type: string
          default: neutral
          description: |
            Rendering style. Allowed values are defined per voice profile.
            Retrieve a profile (including supported styles) with GET /v1/voice/profiles/{profile_id},
            or list all profiles with GET /v1/voice/profiles.
        rate:
          type: number
          minimum: 0.5
          maximum: 2.0
          default: 1.0
          description: Speed multiplier.

    VoiceJobOutput:
      type: object
      additionalProperties: false
      required:
        - format
        - sample_rate_hz
      properties:
        format:
          type: string
          enum:
            - pcm_s16le
        sample_rate_hz:
          type: integer
          example: 24000
          enum:
            - 8000
            - 16000
            - 22050
            - 24000
            - 44100
          description: Sample rate in Hz. Other values return 400 with invalid_input.

    VoiceJobClient:
      type: object
      additionalProperties: false
      properties:
        request_tag:
          type: string
          maxLength: 128
          description: |
            Optional client-defined correlation string (UTF-8, no required format).
            If provided, echoed on the job resource from GET /v1/voice/jobs/{job_id}.

    CreateVoiceJobResponse:
      type: object
      additionalProperties: false
      required:
        - job_id
      properties:
        job_id:
          type: string
          format: uuid
          description: Identifier for the created job (UUID v4).

    ErrorResponse:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - retryable
          additionalProperties: false
          properties:
            code:
              type: string
              description: Stable machine-readable error code.
            message:
              type: string
              description: Human-readable detail.
            retryable:
              type: boolean
              description: Whether the client should retry without changing the request (subject to backoff and headers).
