{
  "openapi": "3.0.3",
  "info": {
    "title": "PDF Craft Transform API",
    "version": "1.0.0",
    "description": "Create → upload → launch → observe → download. PDF to PDF/EPUB/Markdown; EPUB translation to EPUB. No public delete or cancel endpoint."
  },
  "servers": [
    {
      "url": "https://api.pdfcraft.ai/v1/pcc-api"
    },
    {
      "url": "https://pdf-craft-api.oomol.dev/v1/pcc-api",
      "description": "Development; use development credentials"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/transforms": {
      "post": {
        "operationId": "createTransform",
        "summary": "Create before uploading; save uuid",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "input_format"
                ],
                "additionalProperties": false,
                "properties": {
                  "input_format": {
                    "type": "string",
                    "enum": [
                      "pdf",
                      "epub"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transform"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid OOMOL Bearer credential. Gateway errors may differ from service errors."
          }
        }
      }
    },
    "/transforms/{uuid}": {
      "get": {
        "operationId": "getTransform",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transform"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid OOMOL Bearer credential. Gateway errors may differ from service errors."
          },
          "default": {
            "description": "Check HTTP status and error.code before retrying."
          }
        }
      }
    },
    "/transforms/{uuid}/upload-urls": {
      "get": {
        "operationId": "getTransformUploadUrls",
        "description": "PUT directly using returned headers, without API authorization. Existing sources return signed read URLs instead; no overwrite.",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/UploadUrls"
                    },
                    {
                      "$ref": "#/components/schemas/SignedUrls"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid OOMOL Bearer credential. Gateway errors may differ from service errors."
          },
          "default": {
            "description": "Check HTTP status and error.code before retrying."
          }
        }
      }
    },
    "/transforms/{uuid}/launch": {
      "post": {
        "operationId": "launchTransform",
        "description": "Immutable parameters; identical retries are idempotent, including terminal tasks. At most 3 running transforms per user. A rejected launch is not queued and retains its upload expiry.",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Parameters"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transform"
                }
              }
            }
          },
          "202": {
            "description": "JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transform"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid OOMOL Bearer credential. Gateway errors may differ from service errors."
          },
          "409": {
            "description": "SOURCE_FILE_MISSING, TRANSFORM_CONCURRENCY_LIMIT or TRANSFORM_ALREADY_LAUNCHED. Inspect error.code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Check HTTP status and error.code before retrying."
          }
        }
      }
    },
    "/transforms/{uuid}/observation": {
      "get": {
        "operationId": "observeTransform",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "preview_limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Observation"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid OOMOL Bearer credential. Gateway errors may differ from service errors."
          },
          "default": {
            "description": "Check HTTP status and error.code before retrying."
          }
        }
      }
    },
    "/transforms/{uuid}/download-urls": {
      "get": {
        "operationId": "getTransformDownloadUrls",
        "description": "Only succeeded tasks can download. Signatures are temporary; request again when needed. Never attach API credentials to storage requests.",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignedUrls"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid OOMOL Bearer credential. Gateway errors may differ from service errors."
          },
          "default": {
            "description": "Check HTTP status and error.code before retrying."
          }
        }
      }
    },
    "/transforms/running/": {
      "get": {
        "operationId": "listTransforms_running",
        "description": "Basic resources only, no progress or download signatures. Cursors belong to this queue; no total count. Expired tasks are hidden.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "transforms"
                  ],
                  "properties": {
                    "transforms": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Transform"
                      }
                    },
                    "next_cursor": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid OOMOL Bearer credential. Gateway errors may differ from service errors."
          },
          "default": {
            "description": "Check HTTP status and error.code before retrying."
          }
        }
      }
    },
    "/transforms/waiting-for-upload/": {
      "get": {
        "operationId": "listTransforms_waiting_for_upload",
        "description": "Basic resources only, no progress or download signatures. Cursors belong to this queue; no total count. Expired tasks are hidden.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "transforms"
                  ],
                  "properties": {
                    "transforms": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Transform"
                      }
                    },
                    "next_cursor": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid OOMOL Bearer credential. Gateway errors may differ from service errors."
          },
          "default": {
            "description": "Check HTTP status and error.code before retrying."
          }
        }
      }
    },
    "/transforms/finished/": {
      "get": {
        "operationId": "listTransforms_finished",
        "description": "Basic resources only, no progress or download signatures. Cursors belong to this queue; no total count. Expired tasks are hidden.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "transforms"
                  ],
                  "properties": {
                    "transforms": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Transform"
                      }
                    },
                    "next_cursor": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid OOMOL Bearer credential. Gateway errors may differ from service errors."
          },
          "default": {
            "description": "Check HTTP status and error.code before retrying."
          }
        }
      }
    },
    "/transforms/{uuid}/preview-assets/{stage}/{asset}": {
      "get": {
        "operationId": "getTransformPreviewAssetUrls",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "stage",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "extraction",
                "converting"
              ]
            }
          },
          {
            "name": "asset",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{64}\\.(png|pdf)$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignedUrls"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid OOMOL Bearer credential. Gateway errors may differ from service errors."
          },
          "default": {
            "description": "Check HTTP status and error.code before retrying."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    },
    "schemas": {
      "Transform": {
        "type": "object",
        "required": [
          "uuid",
          "status",
          "input_format",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "waiting_for_upload",
              "running",
              "succeeded",
              "failed",
              "cancelled"
            ]
          },
          "input_format": {
            "type": "string",
            "enum": [
              "pdf",
              "epub"
            ]
          },
          "parameters": {
            "$ref": "#/components/schemas/Parameters"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Parameters": {
        "type": "object",
        "required": [
          "output_format"
        ],
        "additionalProperties": false,
        "properties": {
          "output_format": {
            "type": "string",
            "enum": [
              "pdf",
              "epub",
              "markdown"
            ]
          },
          "footnotes": {
            "type": "string",
            "enum": [
              "disabled",
              "basic",
              "refined"
            ]
          },
          "target_language": {
            "type": "string",
            "minLength": 1
          },
          "translation_prompt": {
            "type": "string",
            "minLength": 1
          },
          "rendering_mode": {
            "type": "string",
            "enum": [
              "source",
              "replace",
              "bilingual",
              "bilingual-inline"
            ]
          }
        },
        "description": "EPUB input only outputs EPUB. Translation required for EPUB input and PDF output. PDF output only supports replace; footnotes only apply to PDF input. Omit target_language for no translation."
      },
      "SignedUrls": {
        "type": "object",
        "required": [
          "get_url",
          "head_url"
        ],
        "properties": {
          "get_url": {
            "type": "string",
            "format": "uri"
          },
          "head_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "UploadUrls": {
        "type": "object",
        "required": [
          "put_url",
          "headers"
        ],
        "properties": {
          "put_url": {
            "type": "string",
            "format": "uri"
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Observation": {
        "type": "object",
        "required": [
          "status",
          "preview"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "waiting_for_upload",
              "running",
              "succeeded",
              "failed",
              "cancelled"
            ]
          },
          "progress": {
            "type": "object",
            "required": [
              "queued"
            ],
            "properties": {
              "queued": {
                "type": "boolean"
              },
              "stage": {
                "type": "string",
                "enum": [
                  "downloading",
                  "extracting",
                  "converting",
                  "packaging",
                  "uploading"
                ]
              },
              "completed_pages": {
                "type": "integer",
                "minimum": 0
              },
              "total_pages": {
                "type": "integer",
                "minimum": 0
              },
              "completed_characters": {
                "type": "integer",
                "minimum": 0
              },
              "total_characters": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "preview": {
            "type": "object",
            "required": [
              "cursor"
            ],
            "properties": {
              "cursor": {
                "type": "string"
              },
              "updated": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "file",
                    "url"
                  ],
                  "properties": {
                    "file": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "range": {
                      "type": "object",
                      "required": [
                        "start",
                        "end"
                      ],
                      "properties": {
                        "start": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "end": {
                          "type": "integer",
                          "minimum": 1
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "download_urls": {
            "$ref": "#/components/schemas/SignedUrls"
          }
        },
        "description": "Progress only while running. Page/character counters describe the current step, not overall percentage. Preview ranges are UTF-8 bytes [start,end); read storage with Range, then commit cursor. Previews contain a bounded tail, not a full archive."
      }
    }
  }
}
