{
  "openapi": "3.1.0",
  "info": {
    "title": "JP Transcribe API",
    "version": "1.8.56",
    "description": "Free Japanese speech-to-text and translation API.\nDaily quota: 8 hours per IP per UTC day (resets 00:00 GMT).\nLive ASR chunks ≤30s. Remaining hours: GET /v1/free.\nCreate a key via POST /v1/keys or https://jptranscribe.com/get-key/\nSchema: https://jptranscribe.com/spec/openapi.yaml\n",
    "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) and daily quota limits"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "summary": "Health check",
        "operationId": "health",
        "security": [],
        "responses": {
          "200": {
            "description": "Service status. `backend.asr_workers`, `backend.capacity`, and\n`backend.pool.shared_free` reflect local Oracle RAM / warm workers\n(daily budget, slots, remaining seconds).\n"
          }
        }
      }
    },
    "/v1/free": {
      "get": {
        "summary": "Shared free pool snapshot",
        "operationId": "freePool",
        "security": [],
        "responses": {
          "200": {
            "description": "Public. Returns `shared_free` for the caller IP: daily/hourly budget\nfrom unused server RAM, unique IPs in the last 20s, remaining\nseconds, and clip cap. Read-only — does not mark the IP online.\n"
          }
        }
      }
    },
    "/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 a Japanese audio chunk (max 30s window, 10MB; use live for continuous)",
        "operationId": "transcribe",
        "description": "Routing (see `processed_by` and `route_reason` in response):\n- **server_demo** — under daily IP quota; runs on the server\n- **server_demo** — shared grid free path (2h/IP/day, capacity available)\n- **pool_credit** — community pool; costs 1.5× audio seconds in credits\n- **quota_exhausted** — 403 when daily IP quota is used (resets 00:00 GMT)\n",
        "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": "quota_exhausted — wait for 00:00 GMT reset"
          },
          "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 (30 req/min)"
      }
    }
  }
}
