{
  "openapi": "3.1.0",
  "info": {
    "title": "Katto API",
    "version": "1.5.0",
    "description": "Turn long videos into scored, reframed, captioned 9:16 clips. Submit a video URL, poll for results. Usage draws from your plan's monthly video quota.",
    "contact": {
      "name": "Katto",
      "url": "https://katto.tech/docs/api"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://katto.tech/api/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/jobs": {
      "post": {
        "operationId": "createJob",
        "summary": "Create a clipping job",
        "description": "Submit a video URL (YouTube, Twitch, Vimeo, Rumble, Zoom, Dailymotion). Consumes one slot of your monthly video quota. Pass an Idempotency-Key header to make retries safe.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional. Makes the request idempotent: a repeat with the same key returns the original job (200, with idempotent_replay:true) instead of creating and quota-charging a second one. Recommended when you auto-retry on network errors. While the first request is still in-flight, a duplicate returns 409 (retry shortly)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public video URL.",
                    "example": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
                  },
                  "webhook_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Optional public https URL. Receives a signed POST (X-Katto-Signature: HMAC-SHA256 of timestamp.body) when the job completes."
                  },
                  "config": {
                    "type": "object",
                    "description": "Optional pre-clip settings.",
                    "properties": {
                      "genre": {
                        "type": "string",
                        "example": "podcast"
                      },
                      "clipLength": {
                        "type": "string",
                        "enum": [
                          "lt30",
                          "30_60",
                          "60_90",
                          "90_180"
                        ]
                      },
                      "customPrompt": {
                        "type": "string",
                        "maxLength": 1000
                      },
                      "topics": {
                        "type": "string",
                        "maxLength": 500
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay: the original job for a reused Idempotency-Key (no new job created, no quota charged).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string"
                    },
                    "status_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "idempotent_replay": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Job queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "example": "queued"
                    },
                    "status_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "413": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "get": {
        "operationId": "listJobs",
        "summary": "List jobs",
        "description": "List your jobs, newest first. Keyset pagination: pass the previous response's next_cursor to fetch the next page.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "The next_cursor from a previous response."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional filter, e.g. completed, queued, failed."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of jobs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jobs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "status": {
                            "type": "string"
                          },
                          "source": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "completed_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Pass as ?cursor= to fetch the next page; null when there are no more."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/jobs/{id}": {
      "get": {
        "operationId": "getJob",
        "summary": "Get a job",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job status and clips",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "processing",
                        "scoring",
                        "completed",
                        "failed",
                        "cancelled"
                      ]
                    },
                    "progress": {
                      "type": "object",
                      "properties": {
                        "step": {
                          "type": "string"
                        },
                        "pct": {
                          "type": "number"
                        }
                      }
                    },
                    "source": {
                      "type": "string"
                    },
                    "source_url": {
                      "type": "string"
                    },
                    "clips": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "url": {
                            "type": "string",
                            "format": "uri"
                          },
                          "captions_url": {
                            "type": "string",
                            "format": "uri",
                            "nullable": true
                          }
                        }
                      }
                    },
                    "error": {
                      "type": "string",
                      "nullable": true
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "completed_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "delete": {
        "operationId": "cancelJob",
        "summary": "Cancel a job",
        "description": "Cancel a still-running job (queued/processing) and refund the monthly video slot (up to the monthly refund cap). Returns 409 if the job already finished, failed, or was cancelled. Requires a key with write scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled and refunded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "example": "cancelled"
                    },
                    "refunded": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/jobs/{id}/transcript": {
      "get": {
        "operationId": "getTranscript",
        "summary": "Get a job's transcript",
        "description": "The compact, timestamped transcript of a completed job. Returns 404 while the job is still processing (or if it predates transcript storage).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Transcript segments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "lang": {
                      "type": "string",
                      "nullable": true,
                      "example": "en"
                    },
                    "segments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "start": {
                            "type": "number",
                            "description": "Seconds."
                          },
                          "end": {
                            "type": "number",
                            "description": "Seconds."
                          },
                          "text": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Get plan usage",
        "description": "Your current plan and monthly video quota.",
        "responses": {
          "200": {
            "description": "Quota snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plan": {
                      "type": "string",
                      "example": "pro"
                    },
                    "videos_used": {
                      "type": "integer",
                      "example": 6
                    },
                    "videos_limit": {
                      "type": "integer",
                      "example": 25
                    },
                    "videos_remaining": {
                      "type": "integer",
                      "example": 19
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/jobs/{id}/clips": {
      "get": {
        "operationId": "getJobClips",
        "summary": "Get a job's finished clips",
        "description": "Convenience projection of just the finished clips (9:16 MP4 url + caption SRT url + title + score). Empty list while the job is still processing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Finished clips",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string"
                    },
                    "clips": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "url": {
                            "type": "string",
                            "format": "uri"
                          },
                          "captions_url": {
                            "type": "string",
                            "format": "uri",
                            "nullable": true
                          },
                          "title": {
                            "type": "string",
                            "nullable": true
                          },
                          "score": {
                            "type": "number",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/me": {
      "get": {
        "operationId": "getMe",
        "summary": "Account & key introspection",
        "description": "Who owns this key, what it can do (scopes), and the current plan/quota. Lets a client self-configure without guessing.",
        "responses": {
          "200": {
            "description": "Account snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "plan": {
                      "type": "string",
                      "example": "pro"
                    },
                    "key": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "scopes": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "read",
                              "write"
                            ]
                          }
                        }
                      }
                    },
                    "quota": {
                      "type": "object",
                      "properties": {
                        "videos_used": {
                          "type": "integer"
                        },
                        "videos_limit": {
                          "type": "integer"
                        },
                        "videos_remaining": {
                          "type": "integer"
                        },
                        "period_start": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/uploads": {
      "post": {
        "operationId": "createUpload",
        "summary": "Reserve an upload (clip a local file)",
        "description": "Step 1 of clipping a local file. Reserves a job and returns a presigned R2 PUT url. PUT the file bytes to `upload_url` with the given Content-Type, then call POST /v1/uploads/{job_id}/complete. No quota is charged until the complete step. Requires write scope.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "filename",
                  "content_type",
                  "size"
                ],
                "properties": {
                  "filename": {
                    "type": "string",
                    "maxLength": 200,
                    "example": "podcast.mp4"
                  },
                  "content_type": {
                    "type": "string",
                    "enum": [
                      "video/mp4",
                      "video/quicktime",
                      "video/webm",
                      "video/x-matroska"
                    ]
                  },
                  "size": {
                    "type": "integer",
                    "description": "File size in bytes. Cap: 1 GB Free, 5 GB Creator."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Upload reserved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "upload_url": {
                      "type": "string",
                      "format": "uri",
                      "description": "Presigned R2 PUT url (2h expiry)."
                    },
                    "method": {
                      "type": "string",
                      "example": "PUT"
                    },
                    "headers": {
                      "type": "object",
                      "description": "Headers to send with the PUT (Content-Type)."
                    },
                    "expires_in": {
                      "type": "integer",
                      "example": 7200
                    },
                    "complete_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/uploads/{id}/complete": {
      "post": {
        "operationId": "completeUpload",
        "summary": "Finish an upload and start clipping",
        "description": "Step 2 of clipping a local file. Call after PUTting the file to `upload_url`. Verifies the file landed, charges one video of quota, and queues processing. Requires write scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The job_id from POST /v1/uploads."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "config": {
                    "type": "object",
                    "description": "Optional pre-clip settings (same as POST /v1/jobs).",
                    "properties": {
                      "genre": {
                        "type": "string"
                      },
                      "clipLength": {
                        "type": "string",
                        "enum": [
                          "lt30",
                          "30_60",
                          "60_90",
                          "90_180"
                        ]
                      },
                      "customPrompt": {
                        "type": "string",
                        "maxLength": 1000
                      },
                      "topics": {
                        "type": "string",
                        "maxLength": 500
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Queued for processing",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "example": "queued"
                    },
                    "status_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/jobs/{id}/clips/{index}/rerender": {
      "post": {
        "operationId": "rerenderClip",
        "summary": "Re-render a clip (new layout, captions, or dub)",
        "description": "Re-render one finished clip with a new reframe layout_mode and/or caption_style and/or dubbing. Does NOT consume video quota (it edits an existing clip); the original clip url is preserved and the result is versioned. Returns a rerender_id; poll GET /v1/jobs/{id}/rerenders/{rerender_id}. Requires write scope. The clip must carry editing metadata (recent jobs).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "index",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9
            },
            "description": "0-based clip index."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Provide at least one of layout_mode, caption_style, dub.",
                "properties": {
                  "layout_mode": {
                    "type": "string",
                    "enum": [
                      "face_tracking",
                      "wide",
                      "split_screen",
                      "stacked",
                      "passthrough",
                      "grid_3",
                      "grid_4"
                    ]
                  },
                  "caption_style": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "dub": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "en",
                        "es",
                        "fr",
                        "it",
                        "pt",
                        "hi",
                        "ja",
                        "zh"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Re-render queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rerender_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "example": "queued"
                    },
                    "status_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/jobs/{id}/rerenders/{rerender_id}": {
      "get": {
        "operationId": "getRerender",
        "summary": "Poll a re-render",
        "description": "Poll a re-render started by POST .../rerender. status is one of queued/processing/completed/failed; clip_url is populated when completed.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "rerender_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Re-render status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rerender_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "processing",
                        "completed",
                        "failed"
                      ]
                    },
                    "clip_url": {
                      "type": "string",
                      "format": "uri",
                      "nullable": true
                    },
                    "captions_url": {
                      "type": "string",
                      "format": "uri",
                      "nullable": true
                    },
                    "duration": {
                      "type": "number",
                      "nullable": true
                    },
                    "error": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/brand-kit": {
      "get": {
        "operationId": "getBrandKit",
        "summary": "Get your brand kits",
        "description": "Your saved brand kits (colors, caption font/position, default layout, watermark url). Read scope.",
        "responses": {
          "200": {
            "description": "Brand kits",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "brand_kits": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "primary_color": {
                            "type": "string"
                          },
                          "accent_color": {
                            "type": "string"
                          },
                          "caption_font": {
                            "type": "string"
                          },
                          "caption_position": {
                            "type": "string"
                          },
                          "default_layout": {
                            "type": "string"
                          },
                          "watermark_url": {
                            "type": "string",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/webhook": {
      "get": {
        "operationId": "getWebhook",
        "summary": "Get your webhook signing secret",
        "description": "Your webhook signing secret + how to verify signed completion callbacks. Generated on first access. Read scope.",
        "responses": {
          "200": {
            "description": "Webhook signing details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "signing_secret": {
                      "type": "string"
                    },
                    "signature_header": {
                      "type": "string",
                      "example": "X-Katto-Signature"
                    },
                    "timestamp_header": {
                      "type": "string",
                      "example": "X-Katto-Timestamp"
                    },
                    "scheme": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key created at https://katto.tech/dashboard/api-keys, sent as `Authorization: Bearer sk_live_...`."
      }
    },
    "responses": {
      "Error": {
        "description": "Error",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}
