{
  "openapi": "3.1.0",
  "info": {
    "title": "Image Task API",
    "version": "2026-11-07",
    "description": "Create one asynchronous image task. service is image-to-word, image-to-excel, remove-handwriting, or document-scan. Word and Excel accept 1-20 public image URLs and return one file. Handwriting removal and document scan accept exactly one URL and return one image. Website sessions and API keys use the same JSON. A successful API-key result is a private signed URL valid for 30 minutes. ZIP and PDF are not produced."
  },
  "security": [
    {
      "UserApiKey": []
    }
  ],
  "paths": {
    "/api/v1/task": {
      "post": {
        "operationId": "createTask",
        "summary": "Create an asynchronous image task",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskInput"
              },
              "examples": {
                "word": {
                  "summary": "Image to Word",
                  "value": {
                    "service": "image-to-word",
                    "input": ["https://example.com/page.png"]
                  }
                },
                "excel": {
                  "summary": "Image to Excel",
                  "value": {
                    "service": "image-to-excel",
                    "input": ["https://example.com/page.png"]
                  }
                },
                "handwriting": {
                  "summary": "Remove handwriting",
                  "value": {
                    "service": "remove-handwriting",
                    "input": ["https://example.com/page.png"]
                  }
                },
                "scan": {
                  "summary": "Enhance a scan",
                  "value": {
                    "service": "document-scan",
                    "input": ["https://example.com/page.png"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted. Poll Location every 3 seconds.",
            "headers": {
              "Location": {
                "schema": { "type": "string" }
              },
              "Retry-After": {
                "schema": { "type": "string", "example": "3" }
              },
              "Cache-Control": {
                "schema": { "type": "string", "const": "no-store" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Envelope" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "413": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        },
        "tags": ["Tasks"],
        "description": "service and input are required. Word and Excel allow 1-20 URLs. Handwriting removal and document scan require exactly one. Returns 202 with result null. A second in-flight API task returns 429 and is not charged. Do not automatically retry POST."
      }
    },
    "/api/v1/task/{task_id}": {
      "get": {
        "operationId": "getTask",
        "summary": "Read an account-owned task",
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Check data.status.",
            "headers": {
              "Retry-After": {
                "schema": { "type": "string", "example": "3" }
              },
              "Cache-Control": {
                "schema": { "type": "string", "const": "no-store" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Envelope" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        },
        "tags": ["Tasks"],
        "description": "Poll every 3 seconds until succeeded, failed, or canceled. A success has one result. An API-key signed URL expires 30 minutes after completion. Polling does not renew it."
      },
      "delete": {
        "operationId": "cancelTask",
        "summary": "Cancel a queued or processing task",
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "The task. Status becomes canceled only when it was queued or processing.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Envelope" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" }
        },
        "tags": ["Tasks"],
        "description": "A conditional update. A concurrent success is kept and is not refunded. Canceling a queued or processing task refunds once. Succeeded, failed, and canceled tasks stay as they are."
      }
    }
  },
  "components": {
    "securitySchemes": {
      "UserApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "User API key from /settings/apikeys. A website session cookie is also accepted and uses the same body."
      }
    },
    "responses": {
      "Error": {
        "description": "Error. Inspect HTTP status and message. Honor Retry-After on 429.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": ["code", "message"],
              "properties": {
                "code": { "const": -1 },
                "message": { "type": "string" }
              }
            }
          }
        },
        "headers": {
          "Retry-After": {
            "schema": { "type": "string" }
          }
        }
      }
    },
    "schemas": {
      "TaskInput": {
        "type": "object",
        "additionalProperties": false,
        "required": ["service", "input"],
        "properties": {
          "service": {
            "type": "string",
            "enum": [
              "image-to-word",
              "image-to-excel",
              "remove-handwriting",
              "document-scan"
            ]
          },
          "input": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "description": "Public image URL strings, in page order. Exactly one input for remove-handwriting and document-scan.",
            "items": {
              "type": "string",
              "format": "uri",
              "minLength": 1,
              "maxLength": 2048,
              "pattern": "^https?://"
            }
          }
        }
      },
      "File": {
        "type": "object",
        "required": ["url", "filename", "size", "contentType"],
        "properties": {
          "url": { "type": "string" },
          "filename": { "type": "string" },
          "size": { "type": "number" },
          "contentType": { "type": "string" }
        }
      },
      "Envelope": {
        "type": "object",
        "required": ["code", "message", "data"],
        "properties": {
          "code": { "const": 0 },
          "message": { "type": "string" },
          "data": {
            "type": "object",
            "required": [
              "id",
              "service",
              "status",
              "lane",
              "input",
              "result",
              "error",
              "createdAt",
              "expiresAt"
            ],
            "properties": {
              "id": { "type": "string" },
              "service": {
                "type": "string",
                "enum": [
                  "image-to-word",
                  "image-to-excel",
                  "remove-handwriting",
                  "document-scan"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "queued",
                  "processing",
                  "succeeded",
                  "failed",
                  "canceled"
                ]
              },
              "lane": {
                "type": "string",
                "enum": ["free", "paid"],
                "description": "free waits in the free queue. paid is the fast queue. API-key tasks are paid."
              },
              "input": {
                "type": "array",
                "items": { "type": "string", "format": "uri" }
              },
              "result": {
                "anyOf": [
                  { "$ref": "#/components/schemas/File" },
                  { "type": "null" }
                ]
              },
              "error": { "type": ["string", "null"] },
              "createdAt": { "type": "string", "format": "date-time" },
              "expiresAt": {
                "type": ["string", "null"],
                "format": "date-time",
                "description": "Null until success. API-key results expire 30 minutes after completion. Polling does not extend the deadline."
              }
            }
          }
        }
      }
    }
  },
  "servers": [
    {
      "url": "https://doccrisp.com",
      "description": "Production"
    },
    {
      "url": "http://localhost:3000",
      "description": "Local development / tests"
    }
  ],
  "externalDocs": {
    "description": "API reference and Node.js / Python examples",
    "url": "/developer"
  },
  "tags": [
    {
      "name": "Tasks",
      "description": "Create, retrieve, and cancel asynchronous image tasks."
    }
  ]
}
