{
  "openapi": "3.0.3",
  "info": {
    "title": "Video API",
    "version": "1.0",
    "description": "Upload videos, get them transcoded (HLS + MP4), and download the results.\n\n**Authentication:** send your API key as `Authorization: Bearer vpk_...`. Each key belongs to one user,\nwho can only see their own videos. Keys are issued by the service owner.\n\n**Upload flow:** `POST /videos` → `POST /videos/{id}/parts` → PUT each part's bytes to its `url` (no auth header;\nthe URL is pre-signed, use the exact `Content-Length`) → `POST /videos/{id}/complete` → poll `GET /videos/{id}`.\n\n**Edits (trim / stitch / splice):** `POST /videos` with an `edit` of clips from your finished videos → poll status.\n\nGuides, examples in curl / Python / JavaScript, and recipes: see the API docs published with this spec.\n\n**Errors:** every error is `{\"message\", \"error\", \"requestId\"}`; program against the `error` code (see the Error schema).\nValidation errors list every problem at once in `details`, each with its field path.\n\n**Dry run:** add `?dryRun=true` to create, complete or reprocess to validate a request (against your videos' real\nlengths) without creating or starting anything.\n\n**Limits (defaults):** 5 requests/second (burst 10) and 5,000 requests/day per key; 50 new videos per 24 hours;\n20 GiB and 8 hours per file.\n\n**Don't ship a key inside an app you give to others** (a public web page, mobile app or repo). Call this API from your\nbackend and hand browsers only the pre-signed part URLs and download links.\n"
  },
  "servers": [
    {
      "url": "https://api.videopiper.dev/v1",
      "description": "Video API v1"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Videos",
      "description": "Create, list, check and delete videos (uploads and edits)."
    },
    {
      "name": "Uploads",
      "description": "Send a file's bytes in parts with pre-signed URLs, then complete or abort the upload."
    },
    {
      "name": "Processing",
      "description": "Stop processing, or process a finished video again with new options."
    },
    {
      "name": "Usage",
      "description": "Processing minutes and storage for billing."
    },
    {
      "name": "Authentication",
      "description": "Sign up and sign in with email, create sessions and API keys, manage your account."
    },
    {
      "name": "Webhooks",
      "description": "Set up an endpoint to receive events when videos finish, fail, or you reach usage thresholds.\nEach account has one webhook. Events are signed with Standard Webhooks.\n**Events:** video.ready (processing succeeded), video.failed, video.canceled, video.deleted, usage.threshold (80% and 100% of plan limits).\nEach event envelope contains id, type, createdAt and data. Signature verification uses HMAC-SHA256 of \"id.timestamp.body\" with the base64-decoded secret after \"whsec_\".\n"
    }
  ],
  "paths": {
    "/videos": {
      "get": {
        "tags": [
          "Videos"
        ],
        "operationId": "listVideos",
        "summary": "List my videos (newest first)",
        "description": "Filter by metadata: include up to 5 parameters like `metadata.lessonId=123`. Filters are AND'd together.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "nextToken",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "From the previous page"
          },
          {
            "name": "metadata.<key>",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by metadata value (up to 5 filters; dynamic parameter name)"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of videos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "videos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/VideoSummary"
                      }
                    },
                    "nextToken": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Videos"
        ],
        "operationId": "createVideo",
        "summary": "Create a video (an upload, or an edit of finished videos)",
        "description": "Send an upload request, or an `edit` (no upload) to cut, join and splice your finished videos.",
        "parameters": [
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/UploadRequest"
                  },
                  {
                    "$ref": "#/components/schemas/ImportRequest"
                  },
                  {
                    "$ref": "#/components/schemas/EditRequest"
                  }
                ]
              },
              "examples": {
                "upload": {
                  "value": {
                    "fileName": "lesson1.mp4",
                    "sizeBytes": 104857600,
                    "maxHeight": 1080
                  }
                },
                "import": {
                  "value": {
                    "sourceUrl": "https://example.com/videos/tutorial.mp4",
                    "fileName": "tutorial.mp4",
                    "maxHeight": 1080
                  }
                },
                "stitch": {
                  "value": {
                    "fileName": "Lesson 1 (final)",
                    "edit": {
                      "clips": [
                        {
                          "videoId": "0123456789abcdef0123456789abcdef"
                        },
                        {
                          "videoId": "fedcba9876543210fedcba9876543210",
                          "transition": {
                            "type": "fade",
                            "seconds": 0.75
                          }
                        }
                      ]
                    }
                  }
                },
                "cutOutMiddle": {
                  "value": {
                    "edit": {
                      "clips": [
                        {
                          "videoId": "0123456789abcdef0123456789abcdef",
                          "end": 60
                        },
                        {
                          "videoId": "0123456789abcdef0123456789abcdef",
                          "start": 90
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "videoId": {
                      "type": "string"
                    },
                    "partSize": {
                      "type": "integer",
                      "description": "Uploads only. Bytes per part (last part may be smaller)"
                    },
                    "partCount": {
                      "type": "integer",
                      "description": "Uploads only"
                    },
                    "camera": {
                      "type": "object",
                      "properties": {
                        "partSize": {
                          "type": "integer"
                        },
                        "partCount": {
                          "type": "integer"
                        }
                      }
                    },
                    "clips": {
                      "type": "integer",
                      "description": "Edits only"
                    },
                    "status": {
                      "type": "string",
                      "example": "UPLOADING"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not allow this feature (e.g. streaming, 4K, screen recording) or plan limit reached (free tier only)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Edit: a clip's videoId is not yours",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Edit: a clip's video has not finished processing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or daily video limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/videos/{videoId}": {
      "get": {
        "tags": [
          "Videos"
        ],
        "operationId": "getVideo",
        "summary": "Status, timing and (when finished) a 15-minute download link",
        "parameters": [
          {
            "$ref": "#/components/parameters/videoId"
          }
        ],
        "responses": {
          "200": {
            "description": "Current state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "videoId": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "UPLOADING",
                        "READY_TO_START",
                        "RUNNING",
                        "SUCCEEDED",
                        "FAILED",
                        "TIMED_OUT",
                        "ABORTED",
                        "PENDING_REDRIVE",
                        "HISTORY_UNAVAILABLE"
                      ]
                    },
                    "readyAt": {
                      "type": "number",
                      "description": "Unix time the output was published"
                    },
                    "downloadUrl": {
                      "type": "string",
                      "description": "Pre-signed MP4 link (SUCCEEDED only)"
                    },
                    "posterUrl": {
                      "type": "string",
                      "description": "Pre-signed JPEG still frame for thumbnails (SUCCEEDED only)"
                    },
                    "streamUrl": {
                      "type": "string",
                      "description": "HLS master playlist link (Pro and Scale plans only; valid 6 hours)"
                    },
                    "streamExpiresAt": {
                      "type": "number",
                      "description": "Unix time the streaming link expires"
                    },
                    "startedAt": {
                      "type": "number",
                      "description": "Unix time processing started"
                    },
                    "stoppedAt": {
                      "type": "number"
                    },
                    "progress": {
                      "type": "object",
                      "properties": {
                        "planned": {
                          "type": "boolean"
                        },
                        "chunksTotal": {
                          "type": "integer"
                        },
                        "chunksDone": {
                          "type": "integer"
                        },
                        "audioDone": {
                          "type": "boolean"
                        },
                        "videoSeconds": {
                          "type": "number"
                        },
                        "topRendition": {
                          "type": "string",
                          "example": "1080p"
                        }
                      }
                    },
                    "downloadExpiresInSeconds": {
                      "type": "integer"
                    },
                    "result": {
                      "type": "object",
                      "description": "SUCCEEDED only - publication status",
                      "properties": {
                        "jobId": {
                          "type": "string"
                        },
                        "downloadAvailable": {
                          "type": "boolean"
                        },
                        "streamAvailable": {
                          "type": "boolean",
                          "description": "Whether a playable streaming link exists"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Videos"
        ],
        "operationId": "deleteVideo",
        "summary": "Remove a video (irreversible; stops billing its storage)",
        "description": "Cancels any running processing, removes outputs and work files. The original upload is removed unless another video was reprocessed from it and still exists. Usage records are kept for billing history.",
        "parameters": [
          {
            "$ref": "#/components/parameters/videoId"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "videoId": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean",
                      "example": true
                    },
                    "originalDeleted": {
                      "type": "boolean",
                      "description": "Whether the original upload was also removed"
                    },
                    "freedBytes": {
                      "type": "integer",
                      "description": "Total bytes removed from storage"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Videos"
        ],
        "operationId": "updateVideo",
        "summary": "Update video metadata (keys to null to remove them)",
        "parameters": [
          {
            "$ref": "#/components/parameters/videoId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "metadata"
                ],
                "properties": {
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 500
                    },
                    "maxProperties": 20
                  }
                }
              },
              "example": {
                "metadata": {
                  "courseId": "algebra-2",
                  "archived": null
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "videoId": {
                      "type": "string"
                    },
                    "metadata": {
                      "$ref": "#/components/schemas/Metadata"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/videos/{videoId}/parts": {
      "post": {
        "tags": [
          "Uploads"
        ],
        "operationId": "getPartUrls",
        "summary": "Get pre-signed upload URLs for parts",
        "parameters": [
          {
            "$ref": "#/components/parameters/videoId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "partNumbers": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "example": [
                      1,
                      2
                    ],
                    "description": "For normal uploads (1-100 per call)"
                  },
                  "file": {
                    "type": "string",
                    "enum": [
                      "source",
                      "camera"
                    ],
                    "default": "source"
                  },
                  "parts": {
                    "type": "array",
                    "description": "For streaming uploads, each part with its exact size (>= 5 MiB except the last)",
                    "items": {
                      "type": "object",
                      "properties": {
                        "partNumber": {
                          "type": "integer"
                        },
                        "sizeBytes": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "URLs valid for 15 minutes; PUT the bytes with exactly sizeBytes as Content-Length",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "parts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "partNumber": {
                            "type": "integer"
                          },
                          "sizeBytes": {
                            "type": "integer"
                          },
                          "url": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "expiresInSeconds": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/videos/{videoId}/complete": {
      "post": {
        "tags": [
          "Uploads"
        ],
        "operationId": "completeUpload",
        "summary": "Finish the upload and start processing (optionally with redactions)",
        "parameters": [
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/videoId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "redactions": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "$ref": "#/components/schemas/Redaction"
                    }
                  },
                  "deleteOriginal": {
                    "type": "boolean",
                    "description": "Delete the unredacted upload after the output is published"
                  },
                  "trim": {
                    "$ref": "#/components/schemas/Trim"
                  },
                  "source": {
                    "type": "object",
                    "description": "Streaming uploads only",
                    "properties": {
                      "sizeBytes": {
                        "type": "integer"
                      },
                      "partCount": {
                        "type": "integer"
                      }
                    }
                  },
                  "camera": {
                    "type": "object",
                    "description": "Streaming uploads only",
                    "properties": {
                      "sizeBytes": {
                        "type": "integer"
                      },
                      "partCount": {
                        "type": "integer"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted; poll GET /videos/{videoId}"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not allow this feature (e.g. redaction) or plan limit reached (free tier only)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Upload incomplete or sizes don't match",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/videos/{videoId}/abort": {
      "post": {
        "tags": [
          "Uploads"
        ],
        "operationId": "abortUpload",
        "summary": "Cancel an unfinished upload (deletes uploaded parts and the record)",
        "parameters": [
          {
            "$ref": "#/components/parameters/videoId"
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled"
          },
          "409": {
            "description": "Upload already completed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/videos/{videoId}/cancel": {
      "post": {
        "tags": [
          "Processing"
        ],
        "operationId": "cancelProcessing",
        "summary": "Stop processing (the workflow and any of its queued or running jobs)",
        "parameters": [
          {
            "$ref": "#/components/parameters/videoId"
          }
        ],
        "responses": {
          "200": {
            "description": "Stopped; includes terminatedJobs"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/videos/{videoId}/reprocess": {
      "post": {
        "tags": [
          "Processing"
        ],
        "operationId": "reprocessVideo",
        "summary": "Process a video again as a new video (new quality, trim and/or redactions)",
        "description": "Reuses the original upload (nothing is re-sent); for an edit, renders the same clips again. Options are not inherited. Not available after deleteOriginal.",
        "parameters": [
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/videoId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "maxHeight": {
                    "type": "integer",
                    "enum": [
                      720,
                      1080,
                      1440,
                      2160
                    ]
                  },
                  "redactions": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "$ref": "#/components/schemas/Redaction"
                    }
                  },
                  "deleteOriginal": {
                    "type": "boolean",
                    "description": "Uploads only"
                  },
                  "trim": {
                    "$ref": "#/components/schemas/Trim"
                  },
                  "metadata": {
                    "$ref": "#/components/schemas/Metadata",
                    "description": "If given, replaces the original's metadata; otherwise inherited"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "New video created; poll its status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "videoId": {
                      "type": "string"
                    },
                    "reprocessOf": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not allow the requested quality or feature, or plan limit reached (free tier only); reprocessing at a lower quality if the plan allows",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Original never completed or was deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/videos/{videoId}/heartbeat": {
      "post": {
        "tags": [
          "Uploads"
        ],
        "operationId": "sendHeartbeat",
        "summary": "Keep a pre-started recording job waiting (send every ~10 s while recording)",
        "description": "If heartbeats stop for 2 minutes the waiting job gives up. To cancel on purpose, call abort.",
        "parameters": [
          {
            "$ref": "#/components/parameters/videoId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "409": {
            "description": "Not an unfinished streaming upload with prestart",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/auth/start": {
      "servers": [
        {
          "url": "https://api.videopiper.dev"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "startSignIn",
        "summary": "Email a sign-in code (public; no auth required)",
        "description": "Sends a 6-digit code to the email address. 5 attempts per code, 10 minute expiry. Rate limited 5 per email per hour, 30 per IP per hour.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "example": "user@example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Code sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sent": {
                      "type": "boolean"
                    },
                    "expiresInSeconds": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/auth/verify": {
      "servers": [
        {
          "url": "https://api.videopiper.dev"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "verifySignIn",
        "summary": "Verify a sign-in code (public; no auth required)",
        "description": "On first sign-in (new account), acceptTerms must be true. Returns a session token valid for 30 days.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "code"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "example": "user@example.com"
                  },
                  "code": {
                    "type": "string",
                    "pattern": "^\\d{6}$",
                    "example": "123456"
                  },
                  "acceptTerms": {
                    "type": "boolean",
                    "description": "Required to create an account"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sessionToken": {
                      "type": "string",
                      "example": "vps_..."
                    },
                    "expiresAt": {
                      "type": "integer",
                      "description": "Unix time"
                    },
                    "created": {
                      "type": "boolean",
                      "description": "True if a new account was created"
                    },
                    "account": {
                      "type": "object",
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "plan": {
                          "type": "string"
                        },
                        "user": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/auth/signout": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "signOut",
        "summary": "End a session (session token only)",
        "responses": {
          "200": {
            "description": "Signed out",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "signedOut": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/account": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "operationId": "getAccount",
        "summary": "Your account info, plan and this month's usage",
        "responses": {
          "200": {
            "description": "Account details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "email": {
                      "type": "string"
                    },
                    "user": {
                      "type": "string"
                    },
                    "plan": {
                      "type": "string"
                    },
                    "planName": {
                      "type": "string"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "minutes": {
                          "type": "integer",
                          "description": "Monthly processing minutes (billable)"
                        },
                        "storageGb": {
                          "type": "integer",
                          "description": "Monthly storage in GB"
                        },
                        "maxHeight": {
                          "type": "integer",
                          "enum": [
                            720,
                            1080,
                            1440,
                            2160
                          ]
                        },
                        "features": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "videosPerDay": {
                          "type": "integer"
                        },
                        "apiKeys": {
                          "type": "integer",
                          "description": "Max simultaneous API keys"
                        },
                        "overage": {
                          "type": "boolean",
                          "description": "Whether overage is billed"
                        }
                      }
                    },
                    "usage": {
                      "type": "object",
                      "properties": {
                        "month": {
                          "type": "string",
                          "example": "2026-10"
                        },
                        "billableMinutes": {
                          "type": "number"
                        },
                        "videos": {
                          "type": "integer"
                        },
                        "storedGb": {
                          "type": "number"
                        },
                        "gbMonths": {
                          "type": "number"
                        }
                      }
                    },
                    "session": {
                      "type": "boolean",
                      "description": "True if authenticated with a session token"
                    },
                    "billing": {
                      "type": "object",
                      "properties": {
                        "customer": {
                          "type": "boolean",
                          "description": "Has a Stripe customer ID"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/account/keys": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "operationId": "listApiKeys",
        "summary": "List your API keys (never the keys themselves)",
        "responses": {
          "200": {
            "description": "API keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "keyId": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "revoked": {
                            "type": "boolean"
                          },
                          "prefix": {
                            "type": "string",
                            "description": "First 8 chars of the key"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "createApiKey",
        "summary": "Create an API key (session token only)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "Optional label for the key"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "description": "The full key (shown only once)"
                    },
                    "keyId": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "note": {
                      "type": "string",
                      "description": "Copy it now: it can't be shown again"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Session required or plan limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/account/keys/{keyId}": {
      "delete": {
        "tags": [
          "Authentication"
        ],
        "operationId": "revokeApiKey",
        "summary": "Revoke an API key (session token only)",
        "parameters": [
          {
            "name": "keyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{12}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keyId": {
                      "type": "string"
                    },
                    "revoked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Session required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/webhook": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "getWebhookConfig",
        "summary": "Get your webhook configuration",
        "responses": {
          "200": {
            "description": "Webhook config",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "configured": {
                      "type": "boolean"
                    },
                    "url": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "video.ready",
                          "video.failed",
                          "video.canceled",
                          "video.deleted",
                          "usage.threshold"
                        ]
                      }
                    },
                    "enabled": {
                      "type": "boolean"
                    },
                    "secret": {
                      "type": "string",
                      "description": "whsec_-prefixed signing secret"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "put": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "setWebhookConfig",
        "summary": "Set or update your webhook endpoint",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "HTTPS only, public addresses only"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "video.ready",
                        "video.failed",
                        "video.canceled",
                        "video.deleted",
                        "usage.threshold"
                      ]
                    },
                    "description": "Defaults to all events if omitted"
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "Defaults to true"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "configured": {
                      "type": "boolean"
                    },
                    "url": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "enabled": {
                      "type": "boolean"
                    },
                    "secret": {
                      "type": "string"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhookConfig",
        "summary": "Remove your webhook endpoint",
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "configured": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/webhook/rotate": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "rotateWebhookSecret",
        "summary": "Generate a new signing secret (keep the URL)",
        "responses": {
          "200": {
            "description": "New secret",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "configured": {
                      "type": "boolean"
                    },
                    "url": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "enabled": {
                      "type": "boolean"
                    },
                    "secret": {
                      "type": "string",
                      "description": "New whsec_-prefixed secret; old one stops working immediately"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No webhook is set up yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/webhook/test": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "testWebhook",
        "summary": "Send a test event and report the result immediately",
        "responses": {
          "200": {
            "description": "Test sent and one attempt reported",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "integer",
                      "description": "HTTP status code from the endpoint"
                    },
                    "ms": {
                      "type": "integer",
                      "description": "Milliseconds to respond (or error)"
                    },
                    "error": {
                      "type": "string",
                      "nullable": true,
                      "description": "Error message if not ok"
                    },
                    "response": {
                      "type": "string",
                      "nullable": true,
                      "description": "First 500 bytes of the response body"
                    },
                    "eventId": {
                      "type": "string",
                      "description": "The test event ID"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No webhook is set up yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/webhook/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookDeliveries",
        "summary": "Recent webhook delivery attempts (kept for 30 days)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery log",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deliveries": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "eventId": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "attempt": {
                            "type": "integer"
                          },
                          "ok": {
                            "type": "boolean"
                          },
                          "status": {
                            "type": "integer",
                            "nullable": true
                          },
                          "ms": {
                            "type": "integer",
                            "description": "Milliseconds taken"
                          },
                          "error": {
                            "type": "string",
                            "nullable": true
                          },
                          "response": {
                            "type": "string",
                            "nullable": true,
                            "description": "First 500 bytes of response"
                          },
                          "at": {
                            "type": "integer",
                            "description": "Unix time"
                          },
                          "final": {
                            "type": "boolean",
                            "description": "Whether retries will stop"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/usage": {
      "get": {
        "tags": [
          "Usage"
        ],
        "operationId": "getUsage",
        "summary": "Usage for a calendar month (for billing)",
        "description": "Returns processing time and storage for a month, against the plan. Month defaults to the current UTC month.",
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^20\\d\\d-(0[1-9]|1[0-2])$"
            },
            "description": "YYYY-MM (default: current month)"
          }
        ],
        "responses": {
          "200": {
            "description": "Usage summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "month": {
                      "type": "string",
                      "example": "2026-10"
                    },
                    "complete": {
                      "type": "boolean",
                      "description": "Whether the month is complete (now >= month end)"
                    },
                    "processing": {
                      "type": "object",
                      "properties": {
                        "videos": {
                          "type": "integer",
                          "description": "Count of videos that finished processing"
                        },
                        "outputMinutes": {
                          "type": "number",
                          "description": "Sum of output duration (after trim/edit)"
                        },
                        "billableMinutes": {
                          "type": "number",
                          "description": "max(60",
                          "ceil(output seconds))/60 × quality multiplier": null
                        }
                      }
                    },
                    "storage": {
                      "type": "object",
                      "properties": {
                        "gbMonths": {
                          "type": "number",
                          "description": "Byte-time (bytes × time) for bytes kept",
                          "as GB-months": null
                        },
                        "storedGb": {
                          "type": "number",
                          "description": "What is stored right now",
                          "in gigabytes": null
                        },
                        "note": {
                          "type": "string"
                        }
                      }
                    },
                    "delivery": {
                      "type": "object",
                      "properties": {
                        "gb": {
                          "type": "null",
                          "description": "Not metered yet"
                        },
                        "note": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "vpk_ API key"
      }
    },
    "parameters": {
      "dryRun": {
        "name": "dryRun",
        "in": "query",
        "required": false,
        "schema": {
          "type": "boolean"
        },
        "description": "Validate only: nothing is created or started (answers 200 with valid: true)"
      },
      "videoId": {
        "name": "videoId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[a-f0-9]{32}$"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        },
        "description": "Unique key for idempotent requests: POST /videos, POST /videos/{id}/complete, POST /videos/{id}/reprocess. Same key with same request = replay of first response. Stored 24 hours."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every error response (from the API and from API Gateway). Program against `error`; show `message`.",
        "required": [
          "message",
          "error",
          "requestId"
        ],
        "properties": {
          "message": {
            "type": "string",
            "description": "Human-readable; wording may change",
            "example": "Daily limit reached (50 videos per 24 hours); try again later"
          },
          "error": {
            "type": "string",
            "description": "Stable machine-readable code (new codes may be added; fall back to the HTTP status)",
            "example": "DAILY_LIMIT",
            "enum": [
              "INVALID_REQUEST",
              "UNAUTHORIZED",
              "MISSING_AUTHENTICATION",
              "FORBIDDEN",
              "REQUEST_REJECTED",
              "NOT_FOUND",
              "ROUTE_NOT_FOUND",
              "METHOD_NOT_ALLOWED",
              "UPLOAD_INCOMPLETE",
              "ALREADY_COMPLETED",
              "ORIGINAL_UNAVAILABLE",
              "EDIT_SOURCE_NOT_READY",
              "NOT_AN_UPLOAD",
              "IDEMPOTENCY_KEY_REUSED",
              "IDEMPOTENCY_IN_PROGRESS",
              "INVALID_STATE",
              "TOO_LARGE",
              "RATE_LIMITED",
              "QUOTA_EXCEEDED",
              "DAILY_LIMIT",
              "VIDEOS_PAUSED",
              "PLAN_UPGRADE_REQUIRED",
              "PLAN_LIMIT_REACHED",
              "INVALID_CODE",
              "TERMS_REQUIRED",
              "SESSION_REQUIRED",
              "ACCOUNT_DISABLED",
              "EMAIL_NOT_CONFIGURED",
              "EMAIL_FAILED",
              "UPSTREAM_ERROR",
              "UNAVAILABLE"
            ]
          },
          "requestId": {
            "type": "string",
            "description": "Quote this when reporting a problem"
          },
          "details": {
            "type": "array",
            "description": "Validation errors only (400 INVALID_REQUEST) - every problem found, with its field path",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string",
                  "example": "edit.clips[0].end"
                },
                "message": {
                  "type": "string",
                  "example": "80s is past the end of video …, which is 60.0s (1:00); use null for \"to the end\""
                }
              }
            }
          }
        }
      },
      "Metadata": {
        "type": "object",
        "description": "Customer key-value pairs (up to 20 keys, 4 KB total). Keys are 1-40 [A-Za-z0-9_.-]; values are strings up to 500 chars.",
        "additionalProperties": {
          "type": "string",
          "maxLength": 500
        },
        "maxProperties": 20
      },
      "UploadRequest": {
        "type": "object",
        "required": [
          "fileName"
        ],
        "properties": {
          "fileName": {
            "type": "string",
            "example": "lesson1.mp4"
          },
          "sizeBytes": {
            "type": "integer",
            "description": "Required unless streaming",
            "example": 104857600
          },
          "maxHeight": {
            "type": "integer",
            "enum": [
              720,
              1080,
              1440,
              2160
            ],
            "default": 1080,
            "description": "Highest output quality; never upscales"
          },
          "camera": {
            "type": "object",
            "description": "Optional second file shown picture-in-picture over the first (screen + camera)",
            "properties": {
              "fileName": {
                "type": "string"
              },
              "sizeBytes": {
                "type": "integer"
              }
            }
          },
          "layout": {
            "type": "string",
            "enum": [
              "pip"
            ],
            "description": "Optional with camera"
          },
          "streaming": {
            "type": "boolean",
            "description": "Size unknown yet (e.g. still recording); parts are signed per size"
          },
          "prestart": {
            "type": "boolean",
            "description": "With streaming",
            "start the processing container now so it is warm at completion": null
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "ImportRequest": {
        "type": "object",
        "required": [
          "sourceUrl"
        ],
        "description": "Import a video from a public HTTPS URL instead of uploading. The URL is downloaded by the API.",
        "properties": {
          "sourceUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "HTTPS URL (public internet addresses only; no username/password) with the video file. At most 3 redirects, each checked. The server must answer 200; file size is checked against the upload limit."
          },
          "fileName": {
            "type": "string",
            "description": "Display name (default: last URL path segment). 1–200 characters."
          },
          "maxHeight": {
            "type": "integer",
            "enum": [
              720,
              1080,
              1440,
              2160
            ],
            "default": 1080,
            "description": "Highest output quality; never upscales"
          },
          "trim": {
            "$ref": "#/components/schemas/Trim",
            "description": "Keep only this part of the imported video"
          },
          "redactions": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/Redaction"
            },
            "description": "Regions to blur or black out"
          },
          "deleteOriginal": {
            "type": "boolean",
            "description": "Delete the imported file once the output is published"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "EditRequest": {
        "type": "object",
        "required": [
          "edit"
        ],
        "description": "A new video from clips of your finished videos, played in order (trim, stitch, splice).",
        "properties": {
          "edit": {
            "type": "object",
            "required": [
              "clips"
            ],
            "properties": {
              "clips": {
                "type": "array",
                "minItems": 1,
                "maxItems": 50,
                "items": {
                  "$ref": "#/components/schemas/Clip"
                }
              }
            }
          },
          "fileName": {
            "type": "string",
            "description": "Display name (default: Edit of N clips)"
          },
          "maxHeight": {
            "type": "integer",
            "enum": [
              720,
              1080,
              1440,
              2160
            ],
            "default": 1080
          },
          "redactions": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/Redaction"
            },
            "description": "On the edit's own timeline"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "Clip": {
        "type": "object",
        "required": [
          "videoId"
        ],
        "properties": {
          "videoId": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$",
            "description": "One of your finished (SUCCEEDED) videos; may repeat"
          },
          "start": {
            "type": "number",
            "minimum": 0,
            "default": 0,
            "description": "Seconds into that video"
          },
          "end": {
            "type": "number",
            "nullable": true,
            "description": "Seconds into that video; null = to its end"
          },
          "transition": {
            "type": "object",
            "description": "How this clip starts from the previous one (hard cut if omitted; not on the first clip)",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "fade",
                  "fadeblack"
                ],
                "default": "fade"
              },
              "seconds": {
                "type": "number",
                "minimum": 0.1,
                "maximum": 3,
                "default": 0.5
              }
            }
          }
        }
      },
      "Trim": {
        "type": "object",
        "description": "Keep only part of the upload (frame-accurate, no extra encode). Redaction times are on the trimmed timeline.",
        "properties": {
          "start": {
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "end": {
            "type": "number",
            "nullable": true,
            "description": "null = to the end"
          }
        }
      },
      "VideoSummary": {
        "type": "object",
        "properties": {
          "videoId": {
            "type": "string"
          },
          "fileName": {
            "type": "string"
          },
          "cameraFileName": {
            "type": "string"
          },
          "createdAt": {
            "type": "integer",
            "description": "Unix time"
          },
          "sizeBytes": {
            "type": "integer"
          },
          "state": {
            "type": "string",
            "enum": [
              "UPLOADING",
              "READY_TO_START",
              "SUBMITTED"
            ],
            "description": "Call GET /videos/{id} for live status"
          },
          "redactions": {
            "type": "integer"
          },
          "originalDeleted": {
            "type": "boolean"
          },
          "clips": {
            "type": "integer",
            "description": "Present for edits"
          },
          "reprocessOf": {
            "type": "string"
          },
          "trim": {
            "$ref": "#/components/schemas/Trim"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "Redaction": {
        "type": "object",
        "required": [
          "type"
        ],
        "description": "A fixed box (x, y, w, h) or a moving one (track). Not both.",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "blur",
              "box"
            ],
            "description": "box = solid black bar"
          },
          "shape": {
            "type": "string",
            "enum": [
              "rect",
              "ellipse"
            ],
            "default": "rect"
          },
          "x": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Fraction of the final frame width"
          },
          "y": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "w": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "h": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "start": {
            "type": "number",
            "default": 0,
            "description": "Seconds"
          },
          "end": {
            "type": "number",
            "nullable": true,
            "description": "Seconds; null = until the end"
          },
          "track": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "description": "Keyframes for moving content, in time order; the region glides between them (instead of x, y, w, h)",
            "items": {
              "type": "object",
              "required": [
                "time",
                "x",
                "y",
                "w",
                "h"
              ],
              "properties": {
                "time": {
                  "type": "number",
                  "minimum": 0,
                  "description": "Seconds on the output timeline"
                },
                "x": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "y": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "w": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "h": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "UNAUTHORIZED: missing, unknown or revoked API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BadRequest": {
        "description": "INVALID_REQUEST: invalid body or parameter (the message names the field)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "NOT_FOUND: no such video for this user (wrong ID or someone else's)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "RATE_LIMITED (retry with backoff), QUOTA_EXCEEDED, DAILY_LIMIT or VIDEOS_PAUSED",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unavailable": {
        "description": "UNAVAILABLE: temporary failure; retry with backoff",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "IDEMPOTENCY_KEY_REUSED, IDEMPOTENCY_IN_PROGRESS, or other conflict",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
