openapi: 3.1.0
info:
  title: JP Transcribe API
  version: 1.3.0
  description: |
    Free Japanese speech-to-text and translation API.
    Community worker pool — run worker.py with your API key to transcribe on your CPU.
    Server demo: short clips without a worker (limited per IP/day).
    Create a key via POST /v1/keys or https://jptranscribe.com/get-key/
  license:
    name: Free use
servers:
  - url: https://api.jptranscribe.com
    description: Primary API subdomain
  - url: https://jptranscribe.com/api
    description: Same-origin proxy (browser / CORS-friendly)
paths:
  /v1/keys:
    post:
      summary: Create a free API key
      operationId: createKey
      security: []
      responses:
        "200":
          description: New API key (free forever), worker command, and limits
        "429":
          $ref: "#/components/responses/RateLimited"
  /v1/health:
    get:
      summary: Health check
      operationId: health
      security: []
      responses:
        "200":
          description: |
            Service status. `backend.pool` includes community pool stats:
            workers_online, jobs_queued, jobs_claimed, pool_wait_seconds,
            server_demo_max_seconds, server_demo_per_ip_per_day.
  /v1/account:
    get:
      summary: Account balance, pool stats, and worker status
      operationId: account
      security:
        - bearerAuth: []
      responses:
        "200":
          description: worker_online, balance (credits), pool stats, how_to_transcribe
        "401":
          $ref: "#/components/responses/Unauthorized"
  /v1/worker/heartbeat:
    post:
      summary: Register or refresh a community worker
      operationId: workerHeartbeat
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                worker_id:
                  type: string
                  description: Omit to mint a new worker id
                label:
                  type: string
      responses:
        "200":
          description: worker_id, online, ttl_seconds
        "401":
          $ref: "#/components/responses/Unauthorized"
  /v1/pool/claim:
    post:
      summary: Claim the next queued pool job for your worker
      operationId: poolClaim
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [worker_id]
              properties:
                worker_id:
                  type: string
      responses:
        "200":
          description: Job details (job_id null if queue empty) or job_id, filename, duration_seconds, translate, is_self, audio_size
        "401":
          $ref: "#/components/responses/Unauthorized"
  /v1/pool/jobs/{job_id}/audio:
    get:
      summary: Download audio for a claimed job
      operationId: poolJobAudio
      security:
        - bearerAuth: []
      parameters:
        - name: job_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Raw audio bytes (application/octet-stream)
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Not assigned to your worker
        "404":
          description: Job not found or audio expired
  /v1/pool/jobs/{job_id}/complete:
    post:
      summary: Mark a claimed job complete with transcription result
      operationId: poolJobComplete
      security:
        - bearerAuth: []
      parameters:
        - name: job_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [worker_id]
              properties:
                worker_id:
                  type: string
                text:
                  type: string
                translation:
                  type: string
      responses:
        "200":
          description: credits_earned_seconds, job_id
        "401":
          $ref: "#/components/responses/Unauthorized"
  /v1/pool/jobs/{job_id}/fail:
    post:
      summary: Mark a claimed job as failed
      operationId: poolJobFail
      security:
        - bearerAuth: []
      parameters:
        - name: job_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [worker_id]
              properties:
                worker_id:
                  type: string
                message:
                  type: string
      responses:
        "200":
          description: ok
        "401":
          $ref: "#/components/responses/Unauthorized"
  /v1/transcribe:
    post:
      summary: Transcribe Japanese audio (max 90s, 10MB)
      operationId: transcribe
      description: |
        Routing (see `processed_by` and `route_reason` in response):
        - **pool_self** — your worker is online; job runs on your CPU
        - **pool_credit** — community pool using your earned credits
        - **server_demo** — short clip transcribed on server (limited per IP/day)
        - **worker_required** — 403 when no worker, no credits, and server demo exhausted
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                url:
                  type: string
                  format: uri
                language:
                  type: string
                  default: ja
                translate:
                  type: boolean
                  default: false
      responses:
        "200":
          description: Transcription result (id, text, duration_seconds, processed_by, route_reason; translation if requested)
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: worker_required — run worker.py or use server demo
        "413":
          description: File too large
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          description: Worker timeout, capacity exceeded, or engine unavailable
  /v1/translate:
    post:
      summary: Translate Japanese text to English
      operationId: translate
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [text]
              properties:
                text:
                  type: string
      responses:
        "200":
          description: Translation result
        "401":
          $ref: "#/components/responses/Unauthorized"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  responses:
    Unauthorized:
      description: Invalid API key
    RateLimited:
      description: Rate limit exceeded (10 req/min)
