{
  "openapi": "3.1.0",
  "info": {
    "title": "clipsdk",
    "version": "1.0.0",
    "description": "Long video in, short clips out: the best moments of a podcast, interview, webinar or stream VOD as captioned, reframed MP4s with titles and post captions. Sources up to 3 h and 5 GB, 1-20 clips of 10-90 s. Each key gets 3 running jobs and 200 jobs per UTC day; over a limit the answer is 429 RATE_LIMITED with Retry-After. Files are kept 7 days. Failed jobs cost nothing."
  },
  "externalDocs": {
    "url": "https://clipsdk.com/docs"
  },
  "servers": [
    {
      "url": "https://api.clipsdk.com"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "paths": {
    "/v1/clips": {
      "post": {
        "summary": "Clip a long video",
        "description": "Transcribes the video, ranks the moments that stand on their own and renders each one reframed to the aspect with burned-in captions. Takes minutes, so expect 202 and poll job.poll_url, or pass webhook_url. job.stage goes fetching, transcribing, ranking, reframing, rendering. A finished job is a 200 whether it succeeded or failed; failures carry job.error.code INPUT_FETCH_FAILED, INPUT_UNSUPPORTED, INPUT_TOO_LONG (over 3 h), INPUT_TOO_LARGE (over 5 GB), NO_SPEECH, INSUFFICIENT_CREDIT (the video costs more than the credit covers) or INTERNAL.",
        "parameters": [
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "description": "Seconds to hold the request open for the result (0..300). Default 60 on create, 0 on status",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 300
            }
          },
          {
            "name": "Prefer",
            "in": "header",
            "required": false,
            "description": "Alternative to ?wait, e.g. 'wait=120' or 'respond-async'",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClipsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Finished: job.status is succeeded or failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobEnvelope"
                }
              }
            }
          },
          "202": {
            "description": "Queued or running; poll job.poll_url (Location) after Retry-After seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "INVALID_REQUEST (error.issues lists { path, message }) or INVALID_JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: missing, invalid or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "PAYLOAD_TOO_LARGE: JSON body over 2 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: too many running jobs, the daily job allowance is used up, or too many failed auth attempts",
            "headers": {
              "Retry-After": {
                "description": "Seconds until a slot or the daily allowance frees up",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL: something broke on our side, safe to retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/jobs/{id}": {
      "get": {
        "summary": "Job status",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "description": "Seconds to hold the request open for the result (0..300). Default 60 on create, 0 on status",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 300
            }
          },
          {
            "name": "Prefer",
            "in": "header",
            "required": false,
            "description": "Alternative to ?wait, e.g. 'wait=120' or 'respond-async'",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Finished: job.status is succeeded or failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobEnvelope"
                }
              }
            }
          },
          "202": {
            "description": "Queued or running; poll job.poll_url (Location) after Retry-After seconds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "INVALID_REQUEST (error.issues lists { path, message }) or INVALID_JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: missing, invalid or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: unknown or expired job",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "PAYLOAD_TOO_LARGE: JSON body over 2 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: too many running jobs, the daily job allowance is used up, or too many failed auth attempts",
            "headers": {
              "Retry-After": {
                "description": "Seconds until a slot or the daily allowance frees up",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL: something broke on our side, safe to retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/uploads": {
      "post": {
        "summary": "Get an upload URL for a local file",
        "description": "Answers with a presigned upload_url valid for 1 h. PUT the raw bytes there with Content-Length (exactly size) and no Authorization header; any 2xx means stored. Then pass video_url to POST /v1/clips. Up to 5000000000 bytes (5 GB), kept 7 days.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Ticket issued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadTicket"
                }
              }
            }
          },
          "400": {
            "description": "INVALID_REQUEST: not a video filename, or over 5 GB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: missing, invalid or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: too many running jobs, the daily job allowance is used up, or too many failed auth attempts",
            "headers": {
              "Retry-After": {
                "description": "Seconds until a slot or the daily allowance frees up",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/f/{key}": {
      "get": {
        "summary": "Download an output or upload (Range supported, no auth)",
        "security": [],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File"
          },
          "206": {
            "description": "Partial content"
          },
          "404": {
            "description": "Unknown or expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "416": {
            "description": "Range not satisfiable"
          }
        }
      }
    }
  },
  "webhooks": {
    "jobFinished": {
      "post": {
        "summary": "Sent to webhook_url when a job finishes",
        "description": "Signed with Clipsdk-Signature: t=<unix>,v1=<hex HMAC-SHA256 of \"<t>.<body>\" keyed with your webhook secret>; reject timestamps older than 5 minutes. 3 attempts (now, +2 s, +8 s) with a 15 s timeout; any 2xx counts as delivered, redirects count as failures.",
        "parameters": [
          {
            "name": "Clipsdk-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Clipsdk-Job-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Clipsdk-Attempt",
            "in": "header",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 3
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "<job_id>:<status>",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "User-Agent",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "clipsdk-webhooks/1"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "csdk_live_..."
      }
    },
    "schemas": {
      "ClipsRequest": {
        "type": "object",
        "properties": {
          "video_url": {
            "type": "string",
            "maxLength": 4096,
            "format": "uri",
            "description": "Direct link to the video file, or the video_url from POST /v1/uploads. MP4, MOV, WebM or MKV, up to 3 h and 5 GB"
          },
          "clips": {
            "default": 5,
            "description": "How many clips you want, best first. Fewer when the video has fewer good moments",
            "type": "integer",
            "minimum": 1,
            "maximum": 20
          },
          "min_seconds": {
            "default": 20,
            "description": "Shortest clip, in seconds",
            "type": "integer",
            "minimum": 10,
            "maximum": 90
          },
          "max_seconds": {
            "default": 60,
            "description": "Longest clip, in seconds. At least min_seconds + 5",
            "type": "integer",
            "minimum": 10,
            "maximum": 90
          },
          "aspect": {
            "default": "9:16",
            "type": "string",
            "enum": [
              "9:16",
              "1:1",
              "4:5",
              "16:9"
            ]
          },
          "reframe": {
            "default": "auto",
            "description": "auto follows the speaker, center crops the middle, none fits the whole frame over a blurred copy",
            "type": "string",
            "enum": [
              "auto",
              "center",
              "none"
            ]
          },
          "captions": {
            "default": "bold",
            "description": "Burned-in caption style, or false for none. srt and vtt come back either way",
            "anyOf": [
              {
                "type": "boolean",
                "const": false
              },
              {
                "type": "string",
                "enum": [
                  "bold",
                  "karaoke",
                  "minimal"
                ]
              }
            ]
          },
          "language": {
            "description": "ISO 639-1 code of the spoken language. Detected when absent",
            "anyOf": [
              {
                "type": "string",
                "pattern": "^[a-zA-Z]{2}([-_][a-zA-Z0-9]{2,8})?$"
              },
              {
                "type": "null"
              }
            ]
          },
          "prompt": {
            "description": "What to look for, e.g. \"moments about pricing\"",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500
              },
              {
                "type": "null"
              }
            ]
          },
          "webhook_url": {
            "description": "Gets a signed POST with the finished job",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 4096,
                "format": "uri"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "video_url"
        ]
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "job_3k9x0c2m1q8w7e6r5t4y"
          },
          "op": {
            "type": "string",
            "const": "clips"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "succeeded",
              "failed"
            ]
          },
          "stage": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "fetching",
                  "transcribing",
                  "ranking",
                  "reframing",
                  "rendering"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "The step that is running; null while queued and once finished"
          },
          "created_at": {
            "type": "string"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "result": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "source": {
                    "type": "object",
                    "properties": {
                      "duration_s": {
                        "type": "number"
                      },
                      "language": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "speakers": {
                        "anyOf": [
                          {
                            "type": "integer",
                            "minimum": -9007199254740991,
                            "maximum": 9007199254740991
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "description": "null: speaker counting is not available yet"
                      }
                    },
                    "required": [
                      "duration_s",
                      "language",
                      "speakers"
                    ],
                    "additionalProperties": false
                  },
                  "clips": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "rank": {
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991,
                          "description": "1 is the strongest clip"
                        },
                        "score": {
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991,
                          "description": "0-100"
                        },
                        "title": {
                          "type": "string"
                        },
                        "reason": {
                          "type": "string",
                          "description": "Why the moment works on its own"
                        },
                        "start_s": {
                          "type": "number",
                          "description": "Where the clip starts in the source"
                        },
                        "end_s": {
                          "type": "number"
                        },
                        "duration_s": {
                          "type": "number"
                        },
                        "transcript": {
                          "type": "string"
                        },
                        "post_caption": {
                          "type": "string",
                          "description": "Ready-to-post caption"
                        },
                        "video": {
                          "type": "string"
                        },
                        "poster": {
                          "type": "string"
                        },
                        "srt": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "vtt": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "rank",
                        "score",
                        "title",
                        "reason",
                        "start_s",
                        "end_s",
                        "duration_s",
                        "transcript",
                        "post_caption",
                        "video",
                        "poster",
                        "srt",
                        "vtt"
                      ],
                      "additionalProperties": false
                    },
                    "description": "Best first"
                  }
                },
                "required": [
                  "source",
                  "clips"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ],
            "description": "null unless the job succeeded"
          },
          "usage": {
            "type": "object",
            "properties": {
              "source_minutes": {
                "type": "number"
              },
              "clips": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "output_seconds": {
                "type": "number"
              }
            },
            "required": [
              "source_minutes",
              "clips",
              "output_seconds"
            ],
            "additionalProperties": false,
            "description": "Zero unless the job succeeded"
          },
          "error": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "enum": [
                      "INPUT_FETCH_FAILED",
                      "INPUT_UNSUPPORTED",
                      "INPUT_TOO_LONG",
                      "INPUT_TOO_LARGE",
                      "NO_SPEECH",
                      "INSUFFICIENT_CREDIT",
                      "INTERNAL"
                    ]
                  },
                  "message": {
                    "type": "string"
                  }
                },
                "required": [
                  "code",
                  "message"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ]
          },
          "poll_url": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "op",
          "status",
          "stage",
          "created_at",
          "finished_at",
          "result",
          "usage",
          "error",
          "poll_url"
        ],
        "additionalProperties": false
      },
      "JobEnvelope": {
        "type": "object",
        "properties": {
          "job": {
            "$ref": "#/components/schemas/Job"
          }
        },
        "required": [
          "job"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "issues": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "path": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "path",
                    "message"
                  ],
                  "additionalProperties": false
                }
              }
            },
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false
      },
      "UploadRequest": {
        "type": "object",
        "properties": {
          "filename": {
            "type": "string",
            "minLength": 1
          },
          "size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5000000000
          }
        },
        "required": [
          "filename",
          "size"
        ],
        "additionalProperties": false
      },
      "UploadTicket": {
        "type": "object",
        "properties": {
          "upload_url": {
            "type": "string",
            "description": "PUT the raw bytes here, with Content-Length and no Authorization header"
          },
          "video_url": {
            "type": "string",
            "description": "Pass this to POST /v1/clips once the PUT succeeded"
          },
          "expires_at": {
            "type": "string",
            "description": "upload_url stops working after this"
          }
        },
        "required": [
          "upload_url",
          "video_url",
          "expires_at"
        ],
        "additionalProperties": false
      },
      "WebhookEvent": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "job.succeeded",
              "job.failed"
            ]
          },
          "job": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "example": "job_3k9x0c2m1q8w7e6r5t4y"
              },
              "op": {
                "type": "string",
                "const": "clips"
              },
              "status": {
                "type": "string",
                "enum": [
                  "queued",
                  "running",
                  "succeeded",
                  "failed"
                ]
              },
              "stage": {
                "anyOf": [
                  {
                    "type": "string",
                    "enum": [
                      "fetching",
                      "transcribing",
                      "ranking",
                      "reframing",
                      "rendering"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The step that is running; null while queued and once finished"
              },
              "created_at": {
                "type": "string"
              },
              "finished_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "result": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "source": {
                        "type": "object",
                        "properties": {
                          "duration_s": {
                            "type": "number"
                          },
                          "language": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "speakers": {
                            "anyOf": [
                              {
                                "type": "integer",
                                "minimum": -9007199254740991,
                                "maximum": 9007199254740991
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "null: speaker counting is not available yet"
                          }
                        },
                        "required": [
                          "duration_s",
                          "language",
                          "speakers"
                        ],
                        "additionalProperties": false
                      },
                      "clips": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "rank": {
                              "type": "integer",
                              "minimum": -9007199254740991,
                              "maximum": 9007199254740991,
                              "description": "1 is the strongest clip"
                            },
                            "score": {
                              "type": "integer",
                              "minimum": -9007199254740991,
                              "maximum": 9007199254740991,
                              "description": "0-100"
                            },
                            "title": {
                              "type": "string"
                            },
                            "reason": {
                              "type": "string",
                              "description": "Why the moment works on its own"
                            },
                            "start_s": {
                              "type": "number",
                              "description": "Where the clip starts in the source"
                            },
                            "end_s": {
                              "type": "number"
                            },
                            "duration_s": {
                              "type": "number"
                            },
                            "transcript": {
                              "type": "string"
                            },
                            "post_caption": {
                              "type": "string",
                              "description": "Ready-to-post caption"
                            },
                            "video": {
                              "type": "string"
                            },
                            "poster": {
                              "type": "string"
                            },
                            "srt": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "vtt": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "rank",
                            "score",
                            "title",
                            "reason",
                            "start_s",
                            "end_s",
                            "duration_s",
                            "transcript",
                            "post_caption",
                            "video",
                            "poster",
                            "srt",
                            "vtt"
                          ],
                          "additionalProperties": false
                        },
                        "description": "Best first"
                      }
                    },
                    "required": [
                      "source",
                      "clips"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "null unless the job succeeded"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "source_minutes": {
                    "type": "number"
                  },
                  "clips": {
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991
                  },
                  "output_seconds": {
                    "type": "number"
                  }
                },
                "required": [
                  "source_minutes",
                  "clips",
                  "output_seconds"
                ],
                "additionalProperties": false,
                "description": "Zero unless the job succeeded"
              },
              "error": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "code": {
                        "type": "string",
                        "enum": [
                          "INPUT_FETCH_FAILED",
                          "INPUT_UNSUPPORTED",
                          "INPUT_TOO_LONG",
                          "INPUT_TOO_LARGE",
                          "NO_SPEECH",
                          "INSUFFICIENT_CREDIT",
                          "INTERNAL"
                        ]
                      },
                      "message": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "code",
                      "message"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "poll_url": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "op",
              "status",
              "stage",
              "created_at",
              "finished_at",
              "result",
              "usage",
              "error",
              "poll_url"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "event",
          "job"
        ],
        "additionalProperties": false
      }
    }
  }
}