{
  "openapi": "3.1.0",
  "info": {
    "title": "Doomersion API",
    "version": "1.0.0",
    "description": "Process YouTube videos at scale. Get transcripts with word-level timing and speaker diarization — ready for language learning, subtitles, or content analysis.\n\nThe API is asynchronous: submit a batch of YouTube URLs, poll for completion, then retrieve results from your S3-compatible bucket.",
    "contact": {
      "email": "hello@doomlingo.ai",
      "url": "https://doomersion.com/api"
    }
  },
  "servers": [
    {
      "url": "https://api.doomlingo.ai",
      "description": "Production"
    }
  ],
  "security": [
    { "apiKey": [] }
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Health check",
        "operationId": "healthCheck",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is running",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string", "example": "ok" },
                    "workers": { "type": "integer", "example": 4 }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/jobs": {
      "post": {
        "summary": "Submit a processing job",
        "operationId": "createJob",
        "description": "Submit YouTube URLs for download, transcription (ElevenLabs Scribe v2 with speaker diarization and word-level timing), and upload to your S3-compatible bucket.\n\nJobs are processed asynchronously. Poll `GET /jobs/{job_id}` for status.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/JobRequest" },
              "examples": {
                "video_job": {
                  "summary": "Process individual videos",
                  "value": {
                    "type": "video",
                    "urls": [
                      "https://www.youtube.com/shorts/abc123def45",
                      "https://www.youtube.com/watch?v=xyz789ghi01"
                    ],
                    "bucket": {
                      "name": "your-bucket",
                      "endpoint": "https://ACCOUNT_ID.r2.cloudflarestorage.com",
                      "access_key": "YOUR_ACCESS_KEY",
                      "secret_key": "YOUR_SECRET_KEY",
                      "region": "auto"
                    }
                  }
                },
                "channel_job": {
                  "summary": "List all videos from a channel",
                  "value": {
                    "type": "channel",
                    "urls": ["https://www.youtube.com/@channelname"],
                    "bucket": {
                      "name": "your-bucket",
                      "endpoint": "https://s3.us-east-1.amazonaws.com",
                      "access_key": "YOUR_ACCESS_KEY",
                      "secret_key": "YOUR_SECRET_KEY",
                      "region": "us-east-1"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job created and queued",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JobCreated" }
              }
            }
          },
          "400": { "description": "Invalid request (bad type, empty URLs, missing or incomplete bucket object)" },
          "401": { "description": "Invalid or missing API key" }
        }
      }
    },
    "/jobs/{job_id}": {
      "get": {
        "summary": "Check job status",
        "operationId": "getJob",
        "description": "Poll this endpoint every 10–30 seconds until `status` is `\"completed\"`.",
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "Job ID returned from POST /jobs"
          }
        ],
        "responses": {
          "200": {
            "description": "Job status and progress",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JobStatus" }
              }
            }
          },
          "404": { "description": "Job not found" }
        }
      }
    },
    "/jobs/{job_id}/results": {
      "get": {
        "summary": "Get per-video results",
        "operationId": "getJobResults",
        "description": "Returns the processing result for each URL in the job, including S3 paths to the uploaded .mp4 and .json files.",
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Per-video results with S3 paths",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JobResults" }
              }
            }
          },
          "404": { "description": "Job not found" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key provided by Doomersion. Contact hello@doomlingo.ai to request access."
      }
    },
    "schemas": {
      "JobRequest": {
        "type": "object",
        "required": ["type", "urls", "bucket"],
        "properties": {
          "type": {
            "type": "string",
            "enum": ["video", "channel"],
            "description": "`video` to process individual YouTube videos, `channel` to list all videos from a channel"
          },
          "urls": {
            "type": "array",
            "items": { "type": "string", "format": "uri" },
            "maxItems": 10000,
            "description": "YouTube URLs to process. Supported formats: youtube.com/shorts/ID, youtube.com/watch?v=ID, youtu.be/ID, youtube.com/embed/ID"
          },
          "bucket": {
            "$ref": "#/components/schemas/BucketConfig",
            "description": "Your S3-compatible output bucket. Required on every job. Credentials must allow PutObject. Not stored after the job completes."
          }
        }
      },
      "BucketConfig": {
        "type": "object",
        "required": ["name", "endpoint", "access_key", "secret_key", "region"],
        "description": "S3-compatible bucket credentials for uploading processed .mp4 and .json files. Works with AWS S3, Cloudflare R2, MinIO, etc.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Bucket name",
            "example": "your-bucket"
          },
          "endpoint": {
            "type": "string",
            "format": "uri",
            "description": "S3 API endpoint URL",
            "example": "https://ACCOUNT_ID.r2.cloudflarestorage.com"
          },
          "access_key": {
            "type": "string",
            "description": "Access key ID with PutObject permission on the bucket"
          },
          "secret_key": {
            "type": "string",
            "description": "Secret access key"
          },
          "region": {
            "type": "string",
            "description": "Region string (`auto` for Cloudflare R2; e.g. `us-east-1` for AWS)",
            "example": "auto"
          }
        }
      },
      "JobCreated": {
        "type": "object",
        "properties": {
          "job_id": { "type": "string", "format": "uuid", "example": "550e8400-e29b-41d4-a716-446655440000" },
          "status": { "type": "string", "example": "queued" },
          "video_count": { "type": "integer", "example": 3 }
        }
      },
      "JobStatus": {
        "type": "object",
        "properties": {
          "job_id": { "type": "string", "format": "uuid" },
          "type": { "type": "string", "enum": ["video", "channel"] },
          "status": {
            "type": "string",
            "enum": ["queued", "processing", "completed"],
            "description": "Jobs progress: queued → processing → completed"
          },
          "total": { "type": "integer", "description": "Total URLs in the job" },
          "completed": { "type": "integer", "description": "Successfully processed" },
          "failed": { "type": "integer", "description": "Failed to process" },
          "created_at": { "type": "number", "description": "Unix timestamp" },
          "completed_at": { "type": "number", "nullable": true, "description": "Unix timestamp, null while in progress" }
        }
      },
      "JobResults": {
        "type": "object",
        "properties": {
          "job_id": { "type": "string", "format": "uuid" },
          "status": { "type": "string" },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/VideoResult" }
          }
        }
      },
      "VideoResult": {
        "type": "object",
        "properties": {
          "video_url": { "type": "string", "format": "uri", "description": "Original YouTube URL" },
          "status": {
            "type": "string",
            "enum": ["queued", "processing", "done", "failed"]
          },
          "result_path": {
            "type": "string",
            "nullable": true,
            "description": "S3 path prefix. Two files exist: {path}.mp4 (video) and {path}.json (transcript)",
            "example": "s3://bucket/550e8400-.../abc123def45"
          },
          "error": { "type": "string", "nullable": true, "description": "Error message if status is failed" }
        }
      },
      "TranscriptOutput": {
        "type": "object",
        "description": "Contents of the {video_id}.json file uploaded to the bucket",
        "properties": {
          "video_url": { "type": "string", "format": "uri" },
          "video_id": { "type": "string", "description": "11-character YouTube video ID" },
          "duration_s": { "type": "number", "description": "Video duration in seconds" },
          "language": { "type": "string", "description": "Detected language (ISO 639 code: ja, en, ko, es, etc.)" },
          "transcript": { "type": "string", "description": "Full transcript as plain text" },
          "segments": {
            "type": "array",
            "description": "Speech segments split by speaker turn",
            "items": {
              "type": "object",
              "properties": {
                "text": { "type": "string", "description": "Segment text" },
                "speaker": { "type": "string", "description": "Speaker ID (speaker_0, speaker_1, ...)" },
                "start_ms": { "type": "integer", "description": "Segment start time in milliseconds" },
                "end_ms": { "type": "integer", "description": "Segment end time in milliseconds" },
                "words": {
                  "type": "array",
                  "description": "Word-level timestamps",
                  "items": {
                    "type": "object",
                    "properties": {
                      "word": { "type": "string" },
                      "start_ms": { "type": "integer" },
                      "end_ms": { "type": "integer" }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
