{
  "openapi": "3.1.0",
  "info": {
    "title": "FileWhirl Conversion & Compression API",
    "version": "1.0.0",
    "description": "Convert and compress files programmatically. One endpoint, authenticated by an `x-api-key` header (no JWT). POST a file or a public URL to create a job, then GET to poll for a signed output URL. Outputs are deleted after 1 hour. See https://filewhirl.com/developers.",
    "contact": { "name": "FileWhirl", "url": "https://filewhirl.com/developers" }
  },
  "servers": [
    { "url": "https://fkgvpgpdkktikfzspnib.supabase.co/functions/v1", "description": "Production" }
  ],
  "security": [{ "ApiKeyAuth": [] }],
  "paths": {
    "/convert-api": {
      "post": {
        "summary": "Create a conversion or compression job",
        "description": "Upload a file as multipart/form-data, or send a public URL as JSON. Set `to` to the target format; pass the same format as the input to compress it. Returns 202 with a job id, token and status_url. One usage unit is metered per job (refunded if the job fails to queue).",
        "operationId": "createJob",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file", "to"],
                "properties": {
                  "file": { "type": "string", "format": "binary", "description": "The input file (up to 2 GB)." },
                  "to": { "type": "string", "description": "Target format, e.g. \"jpg\", \"mp3\", \"pdf\". Same as input = compress.", "example": "jpg" },
                  "from": { "type": "string", "description": "Optional source format override (inferred from the filename otherwise)." },
                  "notify_email": { "type": "string", "format": "email", "description": "Optional: email when the job finishes." }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["to", "url"],
                "properties": {
                  "to": { "type": "string", "example": "mp3" },
                  "url": { "type": "string", "format": "uri", "description": "Public http(s) link to the source file.", "example": "https://example.com/clip.mp4" },
                  "from": { "type": "string" },
                  "notify_email": { "type": "string", "format": "email" }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job queued.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobCreated" } } }
          },
          "400": { "description": "Missing `to`, or no file/url provided.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Invalid or missing x-api-key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "402": { "description": "Quota exceeded — subscribe or buy credits.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/QuotaError" } } } },
          "500": { "description": "Server error (upload/metering/internal). Safe to retry.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "get": {
        "summary": "Poll a job's status",
        "description": "Poll every ~1.5s until `status` is `done` or `error`. On success, `output_url` is a signed link valid for 1 hour.",
        "operationId": "getJob",
        "parameters": [
          { "name": "id", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" } },
          { "name": "token", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": {
            "description": "Current job status.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobStatus" } } }
          },
          "400": { "description": "id and token required.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Invalid or missing x-api-key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Unknown job id/token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key", "description": "A key from your FileWhirl account, formatted `ff_live_…`." }
    },
    "schemas": {
      "JobCreated": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "token": { "type": "string", "format": "uuid" },
          "status": { "type": "string", "enum": ["queued"] },
          "billing": { "type": "string", "description": "Which budget the unit was drawn from.", "enum": ["subscription", "credits"] },
          "status_url": { "type": "string", "format": "uri", "description": "Pre-built GET URL to poll this job." }
        }
      },
      "JobStatus": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "enum": ["queued", "processing", "done", "error"] },
          "output_url": { "type": "string", "format": "uri", "description": "Signed download URL (valid 1 hour). Present when status=done." },
          "output_size": { "type": "integer", "description": "Output size in bytes." },
          "engine": { "type": "string", "description": "Engine that produced the output, e.g. \"ffmpeg\"." },
          "error": { "type": "string", "description": "Failure reason. Present when status=error." }
        }
      },
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string" } }
      },
      "QuotaError": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "example": "quota exceeded" },
          "reason": { "type": "string", "description": "Machine-readable reason, e.g. \"no_quota\"." },
          "hint": { "type": "string" }
        }
      }
    }
  }
}
