{
  "openapi": "3.1.0",
  "info": {
    "title": "Convert.Online API",
    "description": "Convert files between more than 400 formats — images, video, audio, documents, ebooks, archives and fonts.\n\nA **job** is one conversion request. Inside it are **tasks**, named by you and wired together with `input`: something imports the source, `convert` changes the format, something exports the result. That structure is what lets one request pull a file from S3, convert it and drop it on SFTP.\n\nThe short version of the flow:\n\n1. `POST /v1/process/jobs` — create the job. For an uploaded source you get an upload target back.\n2. Send the bytes, if the source is a local file.\n3. `POST /v1/process/jobs/{id}/complete-upload` — start the conversion.\n4. `GET /v1/process/jobs/{id}` until `status` is `finished`, or subscribe to `/events`.\n5. Download from the export task's `result.url`.\n\nSet `\"sandbox\": true` on a job to validate it and get a simulated finished response — nothing converts, no quota is used, and no key is needed.",
    "version": "1.0.0",
    "contact": {
      "name": "Convert.Online support",
      "url": "https://convert.online/contact",
      "email": "support@convert.online"
    },
    "termsOfService": "https://convert.online/terms",
    "license": {
      "name": "Proprietary",
      "url": "https://convert.online/terms"
    }
  },
  "servers": [
    { "url": "https://api.convert.online", "description": "Production" }
  ],
  "externalDocs": {
    "description": "Full API documentation",
    "url": "https://convert.online/api-docs"
  },
  "tags": [
    { "name": "Jobs", "description": "Create, start and follow conversion jobs" },
    { "name": "Imports", "description": "Getting a local file to the server" },
    { "name": "Downloads", "description": "Collecting the converted file" }
  ],
  "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
  "paths": {
    "/v1/process/jobs": {
      "post": {
        "tags": ["Jobs"],
        "summary": "Create a conversion job",
        "description": "Creates a job from a map of named tasks. A task's `input` names the task it takes its source from, which is how several steps chain into one job.\n\nWhen the source is an upload, the response carries where to send the bytes and the job waits at `waiting_upload` until you call `complete-upload`.",
        "operationId": "createJob",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateJobRequest" },
              "examples": {
                "convertAnUpload": {
                  "summary": "Upload a HEIC photo and get a JPG",
                  "value": {
                    "tasks": {
                      "import-1": { "operation": "import/upload" },
                      "convert-1": { "operation": "convert", "input": "import-1", "input_format": "heic", "output_format": "jpg" },
                      "export-1": { "operation": "export/url", "input": ["convert-1"] }
                    }
                  }
                },
                "convertFromUrl": {
                  "summary": "Convert a file that is already online",
                  "value": {
                    "tasks": {
                      "import-1": { "operation": "import/url", "url": "https://example.com/report.docx" },
                      "convert-1": { "operation": "convert", "input": "import-1", "output_format": "pdf" },
                      "export-1": { "operation": "export/url", "input": ["convert-1"] }
                    }
                  }
                },
                "sandbox": {
                  "summary": "Validate the shape without converting anything",
                  "value": {
                    "sandbox": true,
                    "tasks": {
                      "import-1": { "operation": "import/url", "url": "https://example.com/a.png" },
                      "convert-1": { "operation": "convert", "input": "import-1", "output_format": "jpg" },
                      "export-1": { "operation": "export/url", "input": ["convert-1"] }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Job created",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Job" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": {
            "description": "Not enough conversion quota left on the plan",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "422": {
            "description": "The job did not validate — an unknown operation, a broken chain, or more conversions than the plan runs at once",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "503": {
            "description": "The job store is unavailable",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/v1/process/jobs/{jobId}": {
      "parameters": [{ "$ref": "#/components/parameters/JobId" }],
      "get": {
        "tags": ["Jobs"],
        "summary": "Get a job",
        "description": "Poll until `status` is `finished` or `failed`. The finished job's export task carries the download URL.",
        "operationId": "getJob",
        "responses": {
          "200": {
            "description": "The job as it stands",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Job" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "503": {
            "description": "The job store is unavailable",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/v1/process/jobs/{jobId}/complete-upload": {
      "parameters": [{ "$ref": "#/components/parameters/JobId" }],
      "post": {
        "tags": ["Jobs"],
        "summary": "Start the conversion once the bytes have arrived",
        "description": "Call this after sending the file. Until it is called the job stays at `waiting_upload` and nothing is converted.",
        "operationId": "completeUpload",
        "responses": {
          "200": {
            "description": "The job is queued",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Job" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/process/jobs/{jobId}/events": {
      "parameters": [{ "$ref": "#/components/parameters/JobId" }],
      "get": {
        "tags": ["Jobs"],
        "summary": "Follow a job as it runs (Server-Sent Events)",
        "description": "An `text/event-stream` of the job's progress, so a client can react the moment the conversion ends instead of polling.",
        "operationId": "streamJobEvents",
        "responses": {
          "200": {
            "description": "An event stream; each event carries the job in the same shape as GET /v1/process/jobs/{jobId}",
            "content": { "text/event-stream": { "schema": { "type": "string" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/imports": {
      "post": {
        "tags": ["Imports"],
        "summary": "Create an import to receive a local file",
        "description": "Returns somewhere to put the bytes. Use it for any file that is not already reachable at a URL.",
        "operationId": "createImport",
        "responses": {
          "200": {
            "description": "Where to send the file",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Import" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "503": {
            "description": "The import store is unavailable",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/v1/imports/{importId}/content": {
      "parameters": [
        {
          "name": "importId",
          "in": "path",
          "required": true,
          "description": "The import's id",
          "schema": { "type": "string", "pattern": "^[a-f0-9]{32}$" }
        },
        {
          "name": "token",
          "in": "query",
          "required": false,
          "description": "One-use upload token, as returned with the import. It stands in for the API key, so no key need appear in an upload command.",
          "schema": { "type": "string" }
        }
      ],
      "put": {
        "tags": ["Imports"],
        "summary": "Send the file's bytes",
        "description": "The body is the raw file, streamed — not a form upload. `POST` is accepted as well as `PUT`.",
        "operationId": "uploadImportContent",
        "requestBody": {
          "required": true,
          "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } }
        },
        "responses": {
          "200": {
            "description": "The file arrived",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "413": {
            "description": "The file is larger than the import ceiling",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/v1/download/{token}": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "description": "The token from a finished export task's URL",
          "schema": { "type": "string" }
        }
      ],
      "get": {
        "tags": ["Downloads"],
        "summary": "Download a converted file",
        "description": "The URL a finished `export/url` task hands back. The token is the whole credential, so the link works on its own.",
        "operationId": "downloadResult",
        "security": [],
        "responses": {
          "200": {
            "description": "The converted file",
            "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key from Dashboard → API Keys, sent as `Authorization: Bearer <key>`."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "The same key, sent in its own header instead."
      }
    },
    "parameters": {
      "JobId": {
        "name": "jobId",
        "in": "path",
        "required": true,
        "description": "The job's id",
        "schema": { "type": "string", "pattern": "^[a-f0-9]{32}$" }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "The key is missing, wrong, or restricted to other IPs",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "No such job, import or file",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "CreateJobRequest": {
        "type": "object",
        "required": ["tasks"],
        "properties": {
          "tasks": {
            "type": "object",
            "description": "Named tasks. The key is the name you refer to in another task's `input`.",
            "additionalProperties": { "$ref": "#/components/schemas/Task" }
          },
          "tag": {
            "type": "string",
            "maxLength": 255,
            "description": "Your own label, echoed back on the job."
          },
          "sandbox": {
            "type": "boolean",
            "description": "Validate and return a simulated finished job. Nothing converts, no quota is spent, no key is required."
          },
          "import_id": {
            "type": "string",
            "description": "Build the job around a file already sent to an import, instead of an `import/*` task.",
            "pattern": "^[a-f0-9]{32}$"
          }
        }
      },
      "Task": {
        "type": "object",
        "required": ["operation"],
        "properties": {
          "operation": {
            "type": "string",
            "description": "What this step does.",
            "enum": [
              "import/upload",
              "import/url",
              "import/base64",
              "import/s3",
              "import/sftp",
              "import/googlecloud",
              "convert",
              "export/url",
              "export/s3",
              "export/sftp",
              "export/googlecloud",
              "export/webhook"
            ]
          },
          "input": {
            "description": "The task this one takes its source from. `convert` names one; an export takes a list.",
            "oneOf": [
              { "type": "string" },
              { "type": "array", "items": { "type": "string" } }
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "For `import/url`: where to fetch the source from."
          },
          "input_format": {
            "type": "string",
            "description": "Source format. Optional — inferred from the file name or URL when it can be."
          },
          "output_format": {
            "type": "string",
            "description": "For `convert`: the target format, e.g. `jpg`, `pdf`, `mp3`."
          },
          "options": {
            "type": "object",
            "description": "Conversion settings — resizing, quality, codecs and so on. The names differ per conversion; see the documentation for the pair you are converting.",
            "additionalProperties": true
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "pattern": "^[a-f0-9]{32}$" },
          "tag": { "type": "string" },
          "status": {
            "type": "string",
            "description": "`waiting_upload` until the bytes arrive and `complete-upload` is called; then it queues, runs, and ends `finished` or `failed`.",
            "enum": ["waiting_upload", "waiting", "queued", "processing", "converting", "finished", "failed"]
          },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" },
          "startedAt": { "type": ["string", "null"], "format": "date-time" },
          "endedAt": { "type": ["string", "null"], "format": "date-time" },
          "tasks": {
            "type": "object",
            "additionalProperties": { "$ref": "#/components/schemas/TaskStatus" }
          },
          "result": {
            "type": ["object", "null"],
            "description": "Present once the job has finished.",
            "additionalProperties": true
          },
          "error": { "type": "string", "description": "Why the job failed; empty otherwise." },
          "links": {
            "type": "object",
            "properties": { "self": { "type": "string" } }
          }
        }
      },
      "TaskStatus": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "job": { "type": "string" },
          "operation": { "type": "string" },
          "status": { "type": "string" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "Import": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "pattern": "^[a-f0-9]{32}$" },
          "upload": {
            "type": "object",
            "description": "The ways to deliver the bytes. The signed storage URL is the direct route; the proxy accepts the same file through this API, for a network that cannot reach storage.",
            "additionalProperties": true
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "example": "error" },
          "message": { "type": "string" }
        }
      }
    }
  }
}
