{
 "openapi": "3.0.3",
 "servers": [
  {
   "url": "http://127.0.0.1:8080"
  }
 ],
 "info": {
  "title": "OCR API",
  "version": "1",
  "description": "Document in, per page text and words out. Error codes are fixed. Money fields are in millicents (1/1000 cent) and are operator fields. Customer documents are never stored. The operator makes keys locally with ocrctl; nothing is charged."
 },
 "paths": {
  "/": {
   "get": {
    "summary": "Entry point",
    "description": "Fixed pointers to the public routes and how to get a key; no data of its own, no key needed. Links are absolute when the server knows its public address, else relative.",
    "responses": {
     "200": {
      "description": "Links",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "service": {
           "type": "string"
          },
          "openapi": {
           "type": "string"
          },
          "catalog": {
           "type": "string"
          },
          "status": {
           "type": "string"
          },
          "health": {
           "type": "string"
          },
          "docs": {
           "type": "string"
          },
          "api_key": {
           "type": "string",
           "description": "How to get a key (the same sentence as in the 401 answer)."
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/healthz": {
   "get": {
    "summary": "Liveness",
    "responses": {
     "200": {
      "description": "ok",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "status": {
           "type": "string",
           "enum": [
            "ok"
           ]
          },
          "revision": {
           "type": "string",
           "description": "Commit the running build was made from; dev for a local build."
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/v1/status": {
   "get": {
    "summary": "Load of the service (public, no key)",
    "description": "How busy the service is and how long a new page waits, per class. Only counts and seconds; it names no key, job, address or file name. The wait uses the same estimate as queue in a job status. Limited per client address; Retry-After says when to retry.",
    "security": [],
    "responses": {
     "200": {
      "description": "Load and queue per class",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "load_percent": {
           "type": "integer",
           "minimum": 0,
           "maximum": 100,
           "description": "Occupied worker slots as a share of all slots."
          },
          "classes": {
           "type": "object",
           "description": "Keys paid and free.",
           "additionalProperties": {
            "type": "object",
            "properties": {
             "pages_queued": {
              "type": "integer",
              "description": "Pages waiting for a slot in this class."
             },
             "estimated_wait_seconds": {
              "type": "number",
              "description": "Seconds until a page that is new in this class gets a slot. Left out while no page has been measured yet."
             }
            }
           }
          }
         }
        }
       }
      }
     },
     "429": {
      "description": "Too many status requests from this address. Retry-After says when to retry."
     }
    }
   }
  },
  "/v1/catalog": {
   "get": {
    "summary": "What the service offers (public, no key)",
    "description": "Pipelines with description, capabilities, formats, features and price per page, plus the query parameters of POST /v1/jobs. The same registry as GET /v1/pipelines, the same for everyone, cached for a minute. It names no host, revision, load or worker; a pipeline that cannot run right now has available false and one generic note. Limited per client address; Retry-After says when to retry.",
    "security": [],
    "responses": {
     "200": {
      "description": "The catalog",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "schema": {
           "type": "integer",
           "description": "Version of this layout."
          },
          "pipelines": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Pipeline"
           }
          },
          "formats": {
           "type": "array",
           "items": {
            "type": "string"
           }
          },
          "features": {
           "type": "array",
           "items": {
            "type": "string"
           }
          },
          "parameters": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "name": {
              "type": "string"
             },
             "type": {
              "type": "string",
              "enum": [
               "string",
               "list",
               "boolean",
               "integer"
              ]
             },
             "required": {
              "type": "boolean"
             },
             "values": {
              "type": "array",
              "items": {
               "type": "string"
              }
             },
             "unit": {
              "type": "string"
             },
             "note": {
              "type": "string"
             }
            }
           }
          }
         }
        }
       }
      }
     },
     "429": {
      "description": "Too many catalog requests from this address. Retry-After says when to retry."
     }
    }
   }
  },
  "/openapi.json": {
   "get": {
    "summary": "This specification",
    "responses": {
     "200": {
      "description": "OpenAPI document"
     }
    }
   }
  },
  "/v1/pipelines": {
   "get": {
    "summary": "List pipelines with factor, price and capabilities",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "responses": {
     "200": {
      "description": "Pipelines",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "pipelines": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Pipeline"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Codes: rate_limited, locked_out (too many wrong keys from this address or for this key prefix; a valid key from a locked address still passes). Retry-After says when to retry.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/pipelines/{ref}": {
   "get": {
    "summary": "One pipeline",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "parameters": [
     {
      "name": "ref",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "name@version"
     }
    ],
    "responses": {
     "200": {
      "description": "Pipeline",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Pipeline"
        }
       }
      }
     },
     "401": {
      "description": "unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "not_found",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Codes: rate_limited, locked_out (too many wrong keys from this address or for this key prefix; a valid key from a locked address still passes). Retry-After says when to retry.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/usage": {
   "get": {
    "summary": "Pages used by this key",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "responses": {
     "200": {
      "description": "Usage",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Usage"
        }
       }
      }
     },
     "401": {
      "description": "unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Codes: rate_limited, locked_out (too many wrong keys from this address or for this key prefix; a valid key from a locked address still passes). Retry-After says when to retry.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/jobs": {
   "get": {
    "summary": "List the jobs of the calling key, newest first, in pages. Filter with client_ref and status; continue with next_cursor.",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "parameters": [
     {
      "name": "client_ref",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Only jobs submitted with exactly this client_ref."
     },
     {
      "name": "status",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "running",
        "done"
       ]
      },
      "description": "Only jobs in this state."
     },
     {
      "name": "limit",
      "in": "query",
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 1000,
       "default": 100
      },
      "description": "Jobs per page. Outside 1 to 1000: 400 invalid_request."
     },
     {
      "name": "cursor",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "The next_cursor of the previous page. Opaque; a bad cursor is 400 invalid_request."
     }
    ],
    "responses": {
     "200": {
      "description": "The jobs of this key",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/JobList"
        }
       }
      }
     },
     "400": {
      "description": "invalid_request",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   },
   "post": {
    "summary": "Submit a document (request body = the file: PDF, PNG, JPEG or TIFF; each TIFF directory is one page). With dry_run=1 only the exact price is returned. Send an Idempotency-Key header to make a retry safe: the same key with the same document and parameters within 24 hours returns the existing job (200, header Idempotent-Replayed: true) and charges nothing again.",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "parameters": [
     {
      "name": "Idempotency-Key",
      "in": "header",
      "required": false,
      "description": "1 to 255 printable characters, chosen by the caller (a UUID is fine). Per key holder and valid for 24 hours. Same key, same document and same parameters: 200 with the existing job, no second job, no second charge. Same key with another document or other parameters: 409 idempotency_conflict. dry_run ignores the key.",
      "schema": {
       "type": "string",
       "minLength": 1,
       "maxLength": 255
      }
     },
     {
      "name": "dpi",
      "in": "query",
      "required": false,
      "description": "Resolution at which the pages of a PDF are rendered: 150, 200 or 300. Without it the service default applies (150). Images and TIFF are read at their own resolution and ignore it (the dry run warns with dpi_ignored). The page size limit applies at this resolution: a page that is too big at the wished dpi is refused with page_too_large, it is never rendered smaller. The resolution is part of the idempotency check, is returned as dpi on every page of the result, and does not change the price per page. Any other value: 400 invalid_dpi.",
      "schema": {
       "type": "integer",
       "enum": [
        150,
        200,
        300
       ]
      }
     },
     {
      "name": "client_ref",
      "in": "query",
      "required": false,
      "description": "Your own reference (printable characters, at most 200). Stored with the job and returned in every job answer; GET /v1/jobs can filter by it.",
      "schema": {
       "type": "string",
       "maxLength": 200
      }
     },
     {
      "name": "pipeline",
      "in": "query",
      "required": false,
      "description": "name@version from /v1/pipelines. Optional only when exactly one pipeline exists.",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "formats",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Comma separated: json,text,md,hocr,pdf. A pre-check only: the call is refused with 422 capability_missing when the pipeline cannot deliver a format. It does not choose what the job stores; every format the pipeline can deliver is fetched later with GET /v1/jobs/{id}/result?format=."
     },
     {
      "name": "features",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Comma separated: word_boxes,table_cells,layout_classes,header_footer. A pre-check only, like formats: 422 capability_missing when the pipeline does not deliver a feature; the result always holds what the pipeline delivers."
     },
     {
      "name": "dry_run",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "0",
        "1",
        "true",
        "false"
       ]
      },
      "description": "Return the exact price without running"
     },
     {
      "name": "job_budget",
      "in": "query",
      "schema": {
       "type": "integer"
      },
      "description": "Millicents. Any value below the price of one page of the chosen pipeline (its minimum: 100 millicents at factor 1.0, see price_per_page_millicents in /v1/pipelines), 0 and negative values included, is refused with 422 budget_too_low; the message names the minimum. The job stops before a page that would exceed the budget; open pages are not_run and cost nothing."
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/pdf": {
       "schema": {
        "type": "string",
        "format": "binary"
       }
      },
      "image/png": {
       "schema": {
        "type": "string",
        "format": "binary"
       }
      },
      "image/jpeg": {
       "schema": {
        "type": "string",
        "format": "binary"
       }
      },
      "image/tiff": {
       "schema": {
        "type": "string",
        "format": "binary"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "dry_run result; or, with an Idempotency-Key that already stands for a job, that job again (header Idempotent-Replayed: true)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/DryRun"
        }
       }
      }
     },
     "400": {
      "description": "Codes: invalid_request, invalid_dpi (dpi is the only problem), empty_document",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "Codes: unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "402": {
      "description": "Codes: payment_required",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "Codes: idempotency_conflict (this Idempotency-Key was used within 24 hours for another document or other parameters)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "408": {
      "description": "Codes: upload_too_slow (the body arrived too slowly)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "413": {
      "description": "Codes: too_large (document over 100 MiB by default)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "415": {
      "description": "Codes: unsupported_type",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "422": {
      "description": "Codes: invalid_document, password_required, too_many_pages (over 1000 pages by default), page_too_large (a page over 10 MiB: image file, TIFF page, or PDF file size divided by pages; or over the pixel limit), render_failed, timeout, resource_limit, capability_missing, budget_too_low",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Codes: rate_limited, locked_out (Retry-After), too_many_uploads (too many uploads of this key in progress; retry later)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "500": {
      "description": "Codes: internal_error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "Codes: busy (too many uploads in progress on the service; retry later), pipeline_unavailable (no worker serves the chosen pipeline right now; also for dry_run; nothing is booked; Retry-After says when to retry, GET /v1/catalog shows which pipelines are available)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "501": {
      "description": "Codes: not_implemented",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "202": {
      "description": "Job accepted and running. Poll GET /v1/jobs/{id}; the Location header names it.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/JobAccepted"
        }
       }
      }
     }
    }
   }
  },
  "/v1/jobs/{id}": {
   "get": {
    "summary": "State and counts of a job",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "wait",
      "in": "query",
      "required": false,
      "description": "Long poll: hold the request up to this many whole seconds (0 to 60, default 0) until the job changes, then answer with the normal body. A finished job answers at once. When the service has too many waiters the answer comes at once, and the wait is shortened to the server write timeout minus 5 s (55 s by default). For bucket jobs only a change of state ends the wait at once; counted objects and pages end it after at least 3 s.",
      "schema": {
       "type": "integer",
       "minimum": 0,
       "maximum": 60,
       "default": 0
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Job",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/JobStatus"
        }
       }
      }
     },
     "401": {
      "description": "unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Codes: rate_limited, locked_out. Retry-After says when to retry.",
      "headers": {
       "Retry-After": {
        "schema": {
         "type": "integer"
        },
        "description": "Seconds"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "not_found",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   },
   "delete": {
    "summary": "Delete the stored result of a finished job now instead of after result_ttl (24 h by default). The job state and its counts stay readable; the bookings stay. Repeating the call is fine. A job that still runs is cancelled instead (202): its open pages end as not_run (counted in pages_not_run), finished pages stay booked, nothing more is charged, and the job ends with stop cancelled.",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "description": "The job was still running and is being cancelled (state cancelling); poll GET /v1/jobs/{id} until state is done",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/JobCancelling"
        }
       }
      }
     },
     "204": {
      "description": "The result is gone"
     },
     "400": {
      "description": "invalid_request",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "not_found",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/jobs/{id}/report": {
   "get": {
    "summary": "Job report: how the pages of a job were read. Kept as long as the result (result_ttl).",
    "description": "Names the pipeline file by its hash, shows every value of that file that changes the result (params, as the worker received them, with the render dpi the job chose), the model_rev the worker reported and the seconds per stage. Works for a running job (the pages read so far) and a finished one. It holds numbers, codes and hashes only, never text or file names.",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The report",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/JobReport"
        }
       }
      }
     },
     "401": {
      "description": "unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Codes: rate_limited, locked_out. Retry-After says when to retry.",
      "headers": {
       "Retry-After": {
        "schema": {
         "type": "integer"
        },
        "description": "Seconds"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "not_found (also after the retention time)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/jobs/{id}/result": {
   "get": {
    "summary": "Result of a finished job. Results are kept for a limited time (result_ttl).",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "format",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "json",
        "text",
        "md",
        "hocr",
        "pdf"
       ]
      },
      "description": "Default json. hocr needs a pipeline with the capability hocr (else capability_missing). pdf cannot be built by GET: it answers 422 source_required; use POST with the original document."
     }
    ],
    "responses": {
     "200": {
      "description": "The result",
      "headers": {
       "X-Pages-Failed": {
        "schema": {
         "type": "integer"
        },
        "description": "Number of pages with status failed; only present when above 0. The text format writes nothing for such a page."
       },
       "X-Pages-Not-Run": {
        "schema": {
         "type": "integer"
        },
        "description": "Number of pages with status not_run; only present when above 0. The text format writes nothing for such a page."
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Result"
        }
       },
       "text/plain": {
        "schema": {
         "type": "string"
        }
       },
       "text/markdown": {
        "schema": {
         "type": "string"
        }
       },
       "text/html": {
        "schema": {
         "type": "string"
        }
       }
      }
     },
     "400": {
      "description": "invalid_request",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Codes: rate_limited, locked_out. Retry-After says when to retry.",
      "headers": {
       "Retry-After": {
        "schema": {
         "type": "integer"
        },
        "description": "Seconds"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "not_found (also after the retention time)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "not_ready: the job is still running",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "422": {
      "description": "capability_missing, or source_required for format=pdf (use POST with the original)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   },
   "post": {
    "summary": "Searchable PDF of a finished job. The body is the original document (the service does not keep it); the invisible text layer is placed at the level of the pipeline (words or lines). Nothing is stored.",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "format",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string",
       "enum": [
        "pdf"
       ]
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The PDF. Pages that were not read carry a visible strip \"OCR FAILED\" or \"OCR NOT RUN\"; their numbers are in the header X-Pages-Unread.",
      "headers": {
       "X-Pages-Unread": {
        "schema": {
         "type": "string"
        },
        "description": "Comma separated numbers of the pages without text."
       }
      },
      "content": {
       "application/pdf": {
        "schema": {
         "type": "string",
         "format": "binary"
        }
       }
      }
     },
     "400": {
      "description": "invalid_request",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "not_found (also after the retention time)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "not_ready: the job is still running",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "422": {
      "description": "capability_missing, source_mismatch (page count or size differs from the job; this is no checksum, another document of the same page count and sizes is not detected), pdf_unsupported, or an upload refusal of the intake: invalid_document, password_required, too_many_pages, page_too_large, render_failed, timeout, resource_limit",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "413": {
      "description": "too_large",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "408": {
      "description": "upload_too_slow (the body arrived too slowly)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "415": {
      "description": "unsupported_type",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "rate_limited, locked_out or too_many_uploads (Retry-After)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "500": {
      "description": "internal_error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "busy: too many PDF builds or too many uploads at once (Retry-After)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    },
    "requestBody": {
     "required": true,
     "description": "The same PDF, PNG, JPEG or TIFF file that the job read.",
     "content": {
      "application/octet-stream": {
       "schema": {
        "type": "string",
        "format": "binary"
       }
      }
     }
    }
   }
  },
  "/v1/bucket-jobs": {
   "post": {
    "summary": "Start a job over an S3 bucket: every PDF under source.prefix is recognized, results and manifest.json are written under destination.prefix. The same job_id again resumes. Credentials live in RAM only.",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "job_id",
         "source",
         "destination"
        ],
        "properties": {
         "job_id": {
          "type": "string"
         },
         "pipeline": {
          "type": "string"
         },
         "formats": {
          "type": "array",
          "items": {
           "type": "string",
           "enum": [
            "json",
            "text",
            "md",
            "hocr",
            "pdf"
           ]
          },
          "description": "Output files written per object: json (default), text, md, hocr (Extension .hocr), pdf (Extension .pdf: the text layer on the original PDF; a document that cannot get one ends partial with error code pdf_unsupported and gets its other files). Chosen here, unlike the document job where formats is only a pre-check. A page with status failed or not_run shows a status line in md; text stays plain text, the status is in json."
         },
         "job_budget": {
          "type": "integer",
          "description": "Millicents. Below the price of one page of the pipeline (0 and negative included): 422 budget_too_low, the message names the minimum. Omit for no budget."
         },
         "max_objects": {
          "type": "integer"
         },
         "source": {
          "type": "object",
          "properties": {
           "endpoint": {
            "type": "string"
           },
           "region": {
            "type": "string"
           },
           "bucket": {
            "type": "string"
           },
           "prefix": {
            "type": "string"
           },
           "path_style": {
            "type": "boolean"
           },
           "access_key": {
            "type": "string"
           },
           "secret_key": {
            "type": "string"
           },
           "session_token": {
            "type": "string"
           },
           "anonymous": {
            "type": "boolean",
            "description": "Read a public bucket without credentials (unsigned requests). Explicit, never the default; excludes access_key, secret_key and session_token. Source only: the destination always needs credentials."
           }
          }
         },
         "destination": {
          "type": "object",
          "properties": {
           "endpoint": {
            "type": "string"
           },
           "region": {
            "type": "string"
           },
           "bucket": {
            "type": "string"
           },
           "prefix": {
            "type": "string"
           },
           "path_style": {
            "type": "boolean"
           },
           "access_key": {
            "type": "string"
           },
           "secret_key": {
            "type": "string"
           },
           "session_token": {
            "type": "string"
           }
          }
         }
        }
       }
      }
     }
    },
    "responses": {
     "202": {
      "description": "Accepted",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "job_id": {
           "type": "string"
          },
          "state": {
           "type": "string"
          },
          "pipeline": {
           "type": "string"
          }
         }
        }
       }
      }
     },
     "400": {
      "description": "invalid_request",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "402": {
      "description": "payment_required",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "conflict: the job id is running",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "413": {
      "description": "too_large",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "422": {
      "description": "capability_missing, budget_too_low; access_denied, no_such_bucket, wrong_region, source_unreachable: the source prefix is listed before the 202, so wrong credentials or an unreachable store fail at once and nothing starts",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "pipeline_unavailable: no worker serves the chosen pipeline right now (Retry-After); nothing is booked",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/bucket-jobs/{id}": {
   "get": {
    "summary": "State and counts of a bucket job (no names)",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "wait",
      "in": "query",
      "required": false,
      "description": "Long poll: hold the request up to this many whole seconds (0 to 60, default 0) until the job changes, then answer with the normal body. A finished job answers at once. When the service has too many waiters the answer comes at once, and the wait is shortened to the server write timeout minus 5 s (55 s by default). For bucket jobs only a change of state ends the wait at once; counted objects and pages end it after at least 3 s.",
      "schema": {
       "type": "integer",
       "minimum": 0,
       "maximum": 60,
       "default": 0
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Job",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/BucketJobStatus"
        }
       }
      }
     },
     "401": {
      "description": "unauthorized",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "not_found",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  }
 },
 "components": {
  "securitySchemes": {
   "bearerAuth": {
    "type": "http",
    "scheme": "bearer",
    "description": "Authorization: Bearer ocr_..."
   }
  },
  "schemas": {
   "Error": {
    "type": "object",
    "required": [
     "error"
    ],
    "properties": {
     "error": {
      "type": "object",
      "required": [
       "code",
       "message"
      ],
      "properties": {
       "code": {
        "type": "string",
        "enum": [
         "invalid_request",
         "invalid_dpi",
         "empty_document",
         "unauthorized",
         "payment_required",
         "not_found",
         "not_ready",
         "conflict",
         "idempotency_conflict",
         "method_not_allowed",
         "too_large",
         "unsupported_type",
         "invalid_document",
         "password_required",
         "too_many_pages",
         "page_too_large",
         "render_failed",
         "timeout",
         "resource_limit",
         "capability_missing",
         "budget_too_low",
         "source_required",
         "source_mismatch",
         "pdf_unsupported",
         "access_denied",
         "no_such_bucket",
         "wrong_region",
         "source_unreachable",
         "rate_limited",
         "locked_out",
         "upload_too_slow",
         "too_many_uploads",
         "busy",
         "forbidden",
         "unavailable",
         "internal_error",
         "not_implemented"
        ]
       },
       "message": {
        "type": "string"
       },
       "details": {
        "type": "array",
        "items": {
         "type": "string"
        },
        "description": "invalid_request: every problem found; message holds the same problems joined"
       },
       "missing": {
        "type": "array",
        "items": {
         "type": "string"
        },
        "description": "capability_missing: what the pipeline does not deliver"
       },
       "pipelines": {
        "type": "array",
        "items": {
         "type": "string"
        },
        "description": "capability_missing: pipelines that deliver everything asked"
       },
       "limit": {
        "type": "string",
        "description": "Refusals at a numeric limit (too_many_uploads, busy, rate_limited): the config name of the limit that applied (max_per_key, max_spooling, requests_per_second, max_jobs_per_key, max_in_flight)"
       },
       "limit_value": {
        "type": "integer",
        "description": "With limit: the value of that limit"
       },
       "current": {
        "type": "integer",
        "description": "With limit: how much of it this key or the service uses right now; left out for requests_per_second"
       }
      }
     }
    }
   },
   "Pipeline": {
    "type": "object",
    "properties": {
     "pipeline": {
      "type": "string"
     },
     "description": {
      "type": "string"
     },
     "factor": {
      "type": "number",
      "description": "Operator field, not used in the download edition."
     },
     "price_per_page_millicents": {
      "type": "integer",
      "description": "Operator field, not used in the download edition."
     },
     "autoregressive": {
      "type": "boolean"
     },
     "capabilities": {
      "type": "object",
      "additionalProperties": {
       "type": "boolean"
      }
     },
     "available": {
      "type": "boolean",
      "description": "false while the worker lacks a capability this pipeline needs or does not answer; a job, a dry run or a bucket job for it is then refused with 503 pipeline_unavailable and nothing is booked"
     },
     "unavailable_reason": {
      "type": "string",
      "description": "Only on GET /v1/pipelines (with a key), only when available is false: which capability is missing. The public catalog never has it."
     },
     "formats": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "features": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "orderable": {
      "type": "boolean",
      "description": "Operator field, not used in the download edition. False for a preview pipeline: it exists in the service but is not offered for ordering yet. Clients that build a call offer only orderable pipelines as a normal choice."
     },
     "preview_note": {
      "type": "string",
      "description": "Operator field, not used in the download edition. Only when orderable is false; always the same sentence."
     },
     "unavailable_note": {
      "type": "string",
      "description": "Only when available is false; always the same sentence."
     }
    }
   },
   "Usage": {
    "type": "object",
    "properties": {
     "balance_millicents": {
      "type": "integer",
      "description": "Operator field, not used in the download edition."
     },
     "spent_millicents": {
      "type": "integer",
      "description": "Operator field, not used in the download edition."
     },
     "pages_used": {
      "type": "integer"
     },
     "paid": {
      "type": "boolean",
      "description": "Operator field, not used in the download edition."
     },
     "pages_left": {
      "type": "integer",
      "nullable": true,
      "description": "Pages until the page limit of the key stops it; null for a key without that limit."
     }
    }
   },
   "DryRun": {
    "type": "object",
    "properties": {
     "dry_run": {
      "type": "boolean"
     },
     "pipeline": {
      "type": "string"
     },
     "pages": {
      "type": "integer"
     },
     "factor": {
      "type": "number",
      "description": "Operator field, not used in the download edition."
     },
     "price_per_page_millicents": {
      "type": "integer",
      "description": "Operator field, not used in the download edition."
     },
     "price_millicents": {
      "type": "integer",
      "description": "Operator field, not used in the download edition. Exact: pages times price per page"
     },
     "pages_covered": {
      "type": "integer",
      "description": "Pages that run before job_budget stops the job"
     },
     "pages_warned": {
      "type": "integer",
      "description": "Done pages that carry a warning (for example model_output_incomplete); a subset of pages_done. The warning itself stands on the page of the result."
     },
     "pages_not_run": {
      "type": "integer"
     },
     "max_charge_millicents": {
      "type": "integer",
      "description": "Operator field, not used in the download edition."
     },
     "dpi": {
      "type": "integer",
      "description": "Resolution the pages of the PDF would be rendered at (the dpi you asked for, else the default). Absent for images and TIFF."
     },
     "balance_millicents": {
      "type": "integer",
      "description": "Operator field, not used in the download edition. Only when the service bills"
     },
     "warnings": {
      "type": "array",
      "description": "What a real job would meet: the worker cannot run the pipeline or job_budget or the balance covers fewer pages than the document has. Absent when there is nothing to say.",
      "items": {
       "$ref": "#/components/schemas/DryRunWarning"
      }
     }
    }
   },
   "DryRunWarning": {
    "type": "object",
    "properties": {
     "code": {
      "type": "string",
      "enum": [
       "job_budget",
       "dpi_ignored",
       "balance_too_low",
       "balance"
      ]
     },
     "message": {
      "type": "string"
     }
    },
    "required": [
     "code",
     "message"
    ]
   },
   "JobAccepted": {
    "type": "object",
    "properties": {
     "job_id": {
      "type": "string"
     },
     "state": {
      "type": "string"
     },
     "pipeline": {
      "type": "string"
     },
     "pages": {
      "type": "integer"
     },
     "price_millicents": {
      "type": "integer",
      "description": "Operator field, not used in the download edition."
     },
     "price_per_page_millicents": {
      "type": "integer",
      "description": "Operator field, not used in the download edition."
     },
     "max_charge_millicents": {
      "type": "integer",
      "description": "Operator field, not used in the download edition. Most this job can charge: the pages job_budget allows times the page price (0 for a free key or when billing is off)."
     },
     "client_ref": {
      "type": "string",
      "description": "Your client_ref, when you sent one."
     }
    }
   },
   "JobCancelling": {
    "type": "object",
    "description": "Answer of DELETE on a job that still runs. state is cancelling only here; GET /v1/jobs/{id} shows running until the job has ended, then done.",
    "properties": {
     "job_id": {
      "type": "string"
     },
     "state": {
      "type": "string",
      "enum": [
       "cancelling"
      ]
     }
    }
   },
   "JobReport": {
    "type": "object",
    "properties": {
     "schema_version": {
      "type": "integer"
     },
     "job_id": {
      "type": "string"
     },
     "state": {
      "type": "string",
      "enum": [
       "running",
       "done"
      ]
     },
     "pipeline": {
      "type": "string"
     },
     "pipeline_hash": {
      "type": "string",
      "description": "sha256 of the pipeline file the job was accepted with; unknown for a job stored before the hash was kept."
     },
     "pipeline_changed": {
      "type": "boolean",
      "description": "True when the pipeline file has changed since the job was accepted; params still show the values the job was accepted with."
     },
     "params": {
      "type": "object",
      "additionalProperties": true,
      "description": "Every value of the pipeline file that changes the result, by step block (detector limits and thresholds, recognizer settings, model sha256 and precision, orientation, layout and table values, model run settings), as the worker received them. Paths of model files are not shown."
     },
     "model_rev": {
      "type": "array",
      "items": {
       "type": "string"
      },
      "description": "The model_rev the worker reported, one entry per distinct value over the pages read."
     },
     "models": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "block": {
         "type": "string",
         "description": "The pipeline step block that uses the model."
        },
        "name": {
         "type": "string",
         "description": "The key of the model in the block of the pipeline file (det_model, rec_model, model, weights)."
        },
        "sha256": {
         "type": "string",
         "description": "sha256 of the model file, as pinned for this release."
        },
        "precision": {
         "type": "string",
         "description": "Numeric precision of the file (fp32 or u8); left out where the pipeline file does not state one."
        }
       },
       "required": [
        "block",
        "name",
        "sha256"
       ]
      },
      "description": "The models the job was read with, from the params the job was accepted with: sha256 and precision per model. Empty for a job stored before the params were kept."
     },
     "seconds": {
      "type": "object",
      "properties": {
       "scheduler_wait": {
        "type": "number"
       },
       "render": {
        "type": "number"
       },
       "queue": {
        "type": "number"
       },
       "read": {
        "type": "number"
       },
       "worker": {
        "type": "number"
       }
      },
      "description": "Seconds: scheduler_wait (waiting for a turn among all jobs), render (page image), queue (waiting before the worker), read (the whole request to the worker, retries included), worker (the time the worker itself reports for the page)."
     },
     "page_stages": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "page": {
         "type": "integer"
        },
        "model_rev": {
         "type": "string"
        },
        "seconds": {
         "type": "object",
         "properties": {
          "scheduler_wait": {
           "type": "number"
          },
          "render": {
           "type": "number"
          },
          "queue": {
           "type": "number"
          },
          "read": {
           "type": "number"
          },
          "worker": {
           "type": "number"
          }
         },
         "description": "Seconds: scheduler_wait (waiting for a turn among all jobs), render (page image), queue (waiting before the worker), read (the whole request to the worker, retries included), worker (the time the worker itself reports for the page)."
        }
       }
      }
     },
     "pages_read_with_other_pipeline_hash": {
      "type": "integer",
      "description": "Pages read with another pipeline file than pipeline_hash (the service was restarted with a changed file during the job); left out when 0."
     },
     "pages": {
      "$ref": "#/components/schemas/JobReportPages"
     }
    },
    "required": [
     "schema_version",
     "job_id",
     "state",
     "pipeline",
     "pipeline_hash",
     "model_rev",
     "models",
     "seconds",
     "page_stages"
    ]
   },
   "JobReportPages": {
    "type": "object",
    "properties": {
     "total": {
      "type": "integer"
     },
     "done": {
      "type": "integer"
     },
     "failed": {
      "type": "integer"
     },
     "not_run": {
      "type": "integer"
     },
     "pending": {
      "type": "integer"
     }
    }
   },
   "JobStatus": {
    "type": "object",
    "properties": {
     "job_id": {
      "type": "string"
     },
     "client_ref": {
      "type": "string",
      "description": "Your client_ref from the POST, when you sent one."
     },
     "pipeline": {
      "type": "string"
     },
     "state": {
      "type": "string",
      "enum": [
       "running",
       "done"
      ]
     },
     "pages": {
      "type": "integer"
     },
     "pages_done": {
      "type": "integer"
     },
     "pages_failed": {
      "type": "integer",
      "description": "Pages that ended failed, none of them charged: the image could not be read (bad_image), the worker cannot run the pipeline (worker_unsupported), the engine failed on this page on every try (engine_error: the worker answered 500 three times), or the worker was too busy and refused the page for the whole waiting time (worker_busy: the worker was reachable but overloaded, nothing was computed), or the page of an autoregressive pipeline came back without one word although the model output was incomplete (model_output_empty: a page is never returned empty and done in silence). The job goes on with the other pages and ends as usual; a worker that is unreachable is a different case (state running, then stop worker_unavailable)."
     },
     "pages_not_run": {
      "type": "integer"
     },
     "pages_pending": {
      "type": "integer"
     },
     "stop": {
      "type": "string",
      "description": "job_budget or balance when the job stopped early; cancelled after DELETE on a running job; worker_unavailable when the OCR worker stayed unreachable too long; worker_unsupported when the worker lost a capability while the job ran; pages_failed when the job ended with not one page read because every page failed"
     },
     "charged_millicents": {
      "type": "integer"
     },
     "queue": {
      "$ref": "#/components/schemas/QueueInfo"
     }
    }
   },
   "JobList": {
    "type": "object",
    "properties": {
     "jobs": {
      "type": "array",
      "maxItems": 1000,
      "items": {
       "$ref": "#/components/schemas/JobStatus"
      }
     },
     "next_cursor": {
      "type": "string",
      "description": "Present when more jobs follow; pass it as cursor. Absent on the last page."
     }
    }
   },
   "QueueInfo": {
    "type": "object",
    "description": "Where a running job stands among all jobs waiting for the OCR machine. Paid jobs run before free ones, jobs of different customers take turns, small backlogs go first. The job keeps running while it waits; the numbers change as other jobs come and go.",
    "properties": {
     "class": {
      "type": "string",
      "enum": [
       "paid",
       "free"
      ],
      "description": "Service class of the job: paid jobs run before free ones."
     },
     "paid_pages_ahead": {
      "type": "integer",
      "description": "Free jobs only: pages of paid jobs that run before this job (part of pages_ahead)."
     },
     "position": {
      "type": "integer",
      "description": "1 = this job gets the next free slot."
     },
     "jobs_total": {
      "type": "integer",
      "description": "Jobs in the queue at this moment, so position reads as place x of y."
     },
     "pages_ahead": {
      "type": "integer",
      "description": "Pages that run before this job gets its next page."
     },
     "estimated_wait_seconds": {
      "type": "number",
      "description": "Estimated seconds until this job gets its next page. Absent until the service has measured a page. A guide, not a promise. For a free job it never exceeds max_wait_seconds."
     },
     "waited_seconds": {
      "type": "number",
      "description": "Free jobs only: seconds the job has waited for its next page."
     },
     "max_wait_seconds": {
      "type": "number",
      "description": "Free jobs only: seconds left until this job is served ahead of paid work (the free wait bound minus waited_seconds). Free jobs run on spare capacity and wait behind paid pages, but at most this long."
     },
     "message": {
      "type": "string",
      "description": "Free jobs only: one sentence on why the job waits (paid pages ahead, paid work goes first) and the latest time of its next page. For people, not for parsing."
     }
    }
   },
   "BucketJobStatus": {
    "type": "object",
    "properties": {
     "job_id": {
      "type": "string"
     },
     "pipeline": {
      "type": "string"
     },
     "state": {
      "type": "string",
      "enum": [
       "running",
       "complete",
       "incomplete",
       "failed",
       "interrupted"
      ]
     },
     "stop": {
      "type": "string",
      "description": "Why the job stopped early: job_budget, balance, max_objects, job_time (run time limit) or page_cap (a page limit set for the key or its order)"
     },
     "error": {
      "type": "string",
      "enum": [
       "output_prefix_conflict",
       "access_denied",
       "no_such_bucket",
       "wrong_region",
       "source_unreachable",
       "destination_unreachable",
       "job_timeout",
       "run_failed"
      ],
      "description": "Why a job failed or ended early. job_timeout: the run reached its time limit; the job is incomplete and the same job_id continues it. access_denied: the bucket refused the credentials or the anonymous read. no_such_bucket. wrong_region: the bucket lives in another region than the one given. source_unreachable and destination_unreachable: network, timeout or service error on that side. output_prefix_conflict: the output prefix holds another job's manifest, or source and output overlap. run_failed: anything else."
     },
     "objects": {
      "type": "integer"
     },
     "pages_done": {
      "type": "integer"
     },
     "pages_failed": {
      "type": "integer"
     },
     "pages_not_run": {
      "type": "integer"
     },
     "updated": {
      "type": "string",
      "format": "date-time"
     },
     "started": {
      "type": "string",
      "format": "date-time"
     },
     "objects_listed": {
      "type": "integer",
      "description": "PDF objects returned by the listing so far. The bucket is not pre-counted (the listing is streamed while objects run), so this is a lower bound until listing_complete is true."
     },
     "listing_complete": {
      "type": "boolean"
     },
     "objects_total": {
      "type": "integer",
      "description": "Present only once listing_complete is true: the final object count."
     },
     "percent_objects": {
      "type": "number",
      "description": "Present only once listing_complete is true: share of objects handled (processed or skipped as already done), 0 to 100."
     },
     "pages_per_min": {
      "type": "number",
      "description": "Pages per minute over the last 5 minutes."
     },
     "pages_per_min_overall": {
      "type": "number",
      "description": "Pages per minute since started."
     },
     "current_count": {
      "type": "integer"
     },
     "current_objects": {
      "type": "array",
      "items": {
       "type": "string"
      },
      "description": "Object keys (the customer's own names) being processed now. Shown only here and in the manifest, never logged or stored in the service registry."
     },
     "queue": {
      "$ref": "#/components/schemas/QueueInfo"
     },
     "error_side": {
      "type": "string",
      "enum": [
       "source",
       "destination"
      ],
      "description": "Which bucket caused the error, when a bucket did."
     }
    }
   },
   "Result": {
    "additionalProperties": false,
    "properties": {
     "not_delivered": {
      "items": {
       "pattern": "^[a-z_]{1,32}$",
       "type": "string"
      },
      "maxItems": 16,
      "type": "array"
     },
     "pages": {
      "items": {
       "$ref": "#/components/schemas/ResultPage"
      },
      "maxItems": 1000,
      "type": "array"
     },
     "pipeline": {
      "pattern": "^[a-z][a-z0-9-]{0,39}@[1-9][0-9]{0,5}$",
      "type": "string"
     },
     "schema_version": {
      "const": 1
     }
    },
    "required": [
     "schema_version",
     "pipeline",
     "not_delivered",
     "pages"
    ],
    "title": "ocr-engine result, schema version 1",
    "type": "object"
   },
   "ResultBox": {
    "items": {
     "maximum": 100000,
     "minimum": 0,
     "type": "number"
    },
    "maxItems": 4,
    "minItems": 4,
    "type": "array"
   },
   "ResultCell": {
    "additionalProperties": false,
    "properties": {
     "box": {
      "$ref": "#/components/schemas/ResultBox"
     },
     "col": {
      "maximum": 499,
      "minimum": 0,
      "type": "integer"
     },
     "colspan": {
      "maximum": 500,
      "minimum": 1,
      "type": "integer"
     },
     "row": {
      "maximum": 499,
      "minimum": 0,
      "type": "integer"
     },
     "rowspan": {
      "maximum": 500,
      "minimum": 1,
      "type": "integer"
     },
     "words": {
      "items": {
       "$ref": "#/components/schemas/ResultWord"
      },
      "maxItems": 2000,
      "type": "array"
     }
    },
    "required": [
     "row",
     "col",
     "rowspan",
     "colspan",
     "words"
    ],
    "type": "object"
   },
   "ResultLine": {
    "additionalProperties": false,
    "properties": {
     "mark": {
      "enum": [
       "#",
       "##",
       "###",
       "####",
       "#####",
       "######",
       "-"
      ]
     },
     "words": {
      "items": {
       "$ref": "#/components/schemas/ResultWord"
      },
      "maxItems": 2000,
      "minItems": 1,
      "type": "array"
     }
    },
    "required": [
     "words"
    ],
    "type": "object"
   },
   "ResultModel_run": {
    "additionalProperties": false,
    "properties": {
     "blocks": {
      "minimum": 0,
      "type": "integer"
     },
     "ctc_only": {
      "minimum": 0,
      "type": "integer"
     },
     "ctc_only_chars": {
      "minimum": 0,
      "type": "integer"
     },
     "finish_reason": {
      "maxLength": 32,
      "type": "string"
     },
     "max_tokens": {
      "minimum": 0,
      "type": "integer"
     },
     "tokens": {
      "minimum": 0,
      "type": "integer"
     },
     "truncated": {
      "type": "boolean"
     }
    },
    "required": [
     "blocks",
     "tokens",
     "max_tokens",
     "truncated",
     "ctc_only",
     "ctc_only_chars"
    ],
    "type": "object"
   },
   "ResultPage": {
    "additionalProperties": false,
    "properties": {
     "dpi": {
      "maximum": 1200,
      "minimum": 1,
      "type": "integer"
     },
     "error": {
      "$ref": "#/components/schemas/ResultPage_error"
     },
     "height": {
      "maximum": 100000,
      "minimum": 1,
      "type": "integer"
     },
     "layout": {
      "items": {
       "$ref": "#/components/schemas/ResultRegion"
      },
      "maxItems": 2000,
      "type": "array"
     },
     "lines": {
      "items": {
       "$ref": "#/components/schemas/ResultLine"
      },
      "maxItems": 20000,
      "type": "array"
     },
     "model_run": {
      "$ref": "#/components/schemas/ResultModel_run"
     },
     "page": {
      "maximum": 1000000,
      "minimum": 1,
      "type": "integer"
     },
     "rotation": {
      "enum": [
       0,
       90,
       180,
       270
      ]
     },
     "rotation_checked": {
      "type": "boolean"
     },
     "safe_ok": {
      "type": "boolean"
     },
     "status": {
      "enum": [
       "done",
       "failed",
       "not_run"
      ]
     },
     "tables": {
      "items": {
       "$ref": "#/components/schemas/ResultTable"
      },
      "maxItems": 200,
      "type": "array"
     },
     "warnings": {
      "items": {
       "$ref": "#/components/schemas/ResultWarning"
      },
      "maxItems": 1000,
      "type": "array"
     },
     "width": {
      "maximum": 100000,
      "minimum": 1,
      "type": "integer"
     }
    },
    "required": [
     "page",
     "status",
     "safe_ok",
     "lines"
    ],
    "type": "object"
   },
   "ResultPage_error": {
    "additionalProperties": false,
    "properties": {
     "code": {
      "pattern": "^[a-z][a-z0-9_]{0,39}$",
      "type": "string"
     },
     "message": {
      "maxLength": 500,
      "minLength": 1,
      "type": "string"
     }
    },
    "required": [
     "code",
     "message"
    ],
    "type": "object"
   },
   "ResultRegion": {
    "additionalProperties": false,
    "properties": {
     "box": {
      "$ref": "#/components/schemas/ResultBox"
     },
     "box_source": {
      "enum": [
       "model"
      ]
     },
     "class": {
      "enum": [
       "title",
       "section_header",
       "text",
       "list_item",
       "table",
       "figure",
       "caption",
       "footnote",
       "formula",
       "page_header",
       "page_footer",
       "page_number",
       "other"
      ]
     },
     "confidence": {
      "maximum": 1,
      "minimum": 0,
      "type": [
       "number",
       "null"
      ]
     },
     "native": {
      "maxLength": 64,
      "minLength": 1,
      "type": "string"
     }
    },
    "required": [
     "class",
     "native",
     "confidence",
     "box"
    ],
    "type": "object"
   },
   "ResultTable": {
    "additionalProperties": false,
    "properties": {
     "box": {
      "$ref": "#/components/schemas/ResultBox"
     },
     "box_source": {
      "enum": [
       "model",
       "ctc"
      ]
     },
     "cells": {
      "items": {
       "$ref": "#/components/schemas/ResultCell"
      },
      "maxItems": 20000,
      "type": "array"
     },
     "cols": {
      "maximum": 500,
      "minimum": 1,
      "type": "integer"
     },
     "rows": {
      "maximum": 500,
      "minimum": 1,
      "type": "integer"
     },
     "words_unplaced": {
      "minimum": 1,
      "type": "integer"
     }
    },
    "required": [
     "box",
     "rows",
     "cols",
     "cells"
    ],
    "type": "object"
   },
   "ResultWarning": {
    "additionalProperties": false,
    "properties": {
     "code": {
      "enum": [
       "table_not_found",
       "table_words_unplaced",
       "model_output_incomplete"
      ]
     },
     "message": {
      "maxLength": 300,
      "type": "string"
     },
     "table": {
      "minimum": 0,
      "type": "integer"
     }
    },
    "required": [
     "code",
     "message"
    ],
    "type": "object"
   },
   "ResultWord": {
    "additionalProperties": false,
    "properties": {
     "box": {
      "items": {
       "maximum": 100000,
       "minimum": 0,
       "type": "number"
      },
      "maxItems": 4,
      "minItems": 4,
      "type": "array"
     },
     "confidence": {
      "maximum": 1,
      "minimum": 0,
      "type": "number"
     },
     "confidence_min": {
      "maximum": 1,
      "minimum": 0,
      "type": "number"
     },
     "ctc_text": {
      "maxLength": 1000,
      "minLength": 1,
      "type": "string"
     },
     "flag": {
      "enum": [
       "ok",
       "differs",
       "punct_differs",
       "low_conf",
       "sup",
       "no_ctc",
       "markup",
       "not_in_model"
      ]
     },
     "source": {
      "enum": [
       "textlayer",
       "ctc",
       "vlm"
      ]
     },
     "text": {
      "maxLength": 1000,
      "minLength": 1,
      "type": "string"
     },
     "verified": {
      "type": "boolean"
     }
    },
    "required": [
     "text",
     "confidence",
     "source",
     "verified"
    ],
    "type": "object"
   }
  }
 }
}
