{
  "openapi": "3.1.0",
  "info": {
    "title": "KikuAI Automatic Subtitles API",
    "version": "2026-08-10",
    "description": "Upload a video, receive a trusted duration quote, explicitly approve a shared Kiku Credits charge, then retrieve timed SRT and a captioned MP4. API keys must be kept server-side."
  },
  "servers": [
    {
      "url": "https://kikuai.dev"
    }
  ],
  "security": [
    {
      "subtitleApiKey": []
    }
  ],
  "paths": {
    "/v1/auto-subtitles/jobs": {
      "post": {
        "summary": "Create a private upload quote job",
        "description": "Creates a short-lived job and direct-upload authorization. This request does not transcribe or spend credits. The account needs at least 20 available Kiku Credits.",
        "operationId": "createAutoSubtitlesJob",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateJobRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Job and short-lived direct-upload instructions created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateJobResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/CreditsRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/auto-subtitles/jobs/{job_id}": {
      "get": {
        "summary": "Read quote, processing status, or downloads",
        "operationId": "getAutoSubtitlesJob",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Current account-owned job state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "$ref": "#/components/responses/Expired"
          },
          "424": {
            "$ref": "#/components/responses/WorkerUnavailable"
          }
        }
      }
    },
    "/v1/auto-subtitles/jobs/{job_id}/start": {
      "post": {
        "summary": "Accept the quote and start processing",
        "description": "Atomically reserves the exact quoted Kiku Credits and starts transcription. Repeating the same request is idempotent and does not spend credits twice.",
        "operationId": "startAutoSubtitlesJob",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StartJobRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Credits reserved once and processing started or queued for a safe retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StartJobResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/CreditsRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "410": {
            "$ref": "#/components/responses/Expired"
          },
          "424": {
            "$ref": "#/components/responses/WorkerUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "subtitleApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "kiku_sub_*",
        "description": "Account-owned Automatic Subtitles API key. Store it only on a trusted server."
      }
    },
    "parameters": {
      "JobId": {
        "name": "job_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^asj_[a-f0-9]{32}$"
        }
      }
    },
    "schemas": {
      "CreateJobRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "protected_words": {
            "type": "array",
            "maxItems": 8,
            "description": "Optional spelling guidance, not replacement transcript text. At most 160 characters in total.",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 32
            }
          }
        }
      },
      "StartJobRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "expected_credits"
        ],
        "properties": {
          "expected_credits": {
            "type": "integer",
            "minimum": 20,
            "maximum": 500,
            "multipleOf": 20,
            "description": "Copy the current job.credits_required value. A mismatch returns 409 without charging."
          }
        }
      },
      "UploadInstructions": {
        "type": "object",
        "required": [
          "method",
          "url",
          "authorization",
          "expires_at",
          "max_video_bytes"
        ],
        "properties": {
          "method": {
            "const": "POST"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "authorization": {
            "type": "string",
            "description": "Send this exact Bearer value only to the returned upload URL."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "max_video_bytes": {
            "type": "integer",
            "const": 524288000
          },
          "accepted_content_types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "StartInstructions": {
        "type": "object",
        "required": [
          "method",
          "url",
          "expected_credits"
        ],
        "properties": {
          "method": {
            "const": "POST"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "expected_credits": {
            "type": "integer"
          }
        }
      },
      "DownloadInstructions": {
        "type": "object",
        "required": [
          "authorization",
          "expires_at",
          "video_url",
          "subtitles_url"
        ],
        "properties": {
          "authorization": {
            "type": "string",
            "description": "Send this exact Bearer value to either artifact URL."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "video_url": {
            "type": "string",
            "format": "uri"
          },
          "subtitles_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Job": {
        "type": "object",
        "required": [
          "job_id",
          "status",
          "credits_spent",
          "created_at",
          "updated_at",
          "expires_at"
        ],
        "properties": {
          "job_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_upload",
              "awaiting_payment",
              "authorized",
              "queued",
              "extracting",
              "transcribing",
              "rendering",
              "complete",
              "failed",
              "expired"
            ]
          },
          "duration_seconds": {
            "type": [
              "number",
              "null"
            ]
          },
          "credits_required": {
            "type": [
              "integer",
              "null"
            ]
          },
          "credits_spent": {
            "type": "integer",
            "minimum": 0
          },
          "progress": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "detected_language": {
            "type": [
              "string",
              "null"
            ]
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "poll_after_seconds": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Do not poll this job more frequently than this interval. Null for terminal states."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "upload": {
            "$ref": "#/components/schemas/UploadInstructions"
          },
          "start": {
            "$ref": "#/components/schemas/StartInstructions"
          },
          "downloads": {
            "$ref": "#/components/schemas/DownloadInstructions"
          }
        }
      },
      "CreateJobResponse": {
        "type": "object",
        "required": [
          "accepted",
          "api_version",
          "job",
          "pricing"
        ],
        "properties": {
          "accepted": {
            "const": true
          },
          "api_version": {
            "type": "string"
          },
          "job": {
            "$ref": "#/components/schemas/Job"
          },
          "pricing": {
            "type": "object",
            "properties": {
              "credits_per_started_minute": {
                "const": 20
              },
              "max_credits": {
                "const": 500
              },
              "charged": {
                "const": false
              },
              "note": {
                "type": "string"
              }
            }
          }
        }
      },
      "JobResponse": {
        "type": "object",
        "required": [
          "accepted",
          "api_version",
          "job"
        ],
        "properties": {
          "accepted": {
            "const": true
          },
          "api_version": {
            "type": "string"
          },
          "job": {
            "$ref": "#/components/schemas/Job"
          }
        }
      },
      "StartJobResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/JobResponse"
          },
          {
            "type": "object",
            "required": [
              "start_pending",
              "credits_spent",
              "credits_remaining"
            ],
            "properties": {
              "start_pending": {
                "type": "boolean"
              },
              "credits_spent": {
                "type": "integer"
              },
              "credits_remaining": {
                "type": "integer"
              },
              "retry": {
                "$ref": "#/components/schemas/StartInstructions"
              }
            }
          }
        ]
      },
      "Error": {
        "type": "object",
        "required": [
          "accepted",
          "code"
        ],
        "properties": {
          "accepted": {
            "const": false
          },
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "credits_required": {
            "type": "integer"
          },
          "credits_remaining": {
            "type": "integer"
          },
          "retry_after_seconds": {
            "type": "integer"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid JSON or Protected Words.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "An active Automatic Subtitles API key is required.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "CreditsRequired": {
        "description": "The shared balance cannot cover the requested action.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "The job is absent or owned by another account.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "The quote is not ready, changed, or should be retried without changing expected_credits.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Expired": {
        "description": "The 24-hour job window ended.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "WorkerUnavailable": {
        "description": "The private media worker could not be reached.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "The daily account or global quote-job limit was reached.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
