"openapi": "3.1.0"
"info":
  "title": "NexaPDF AI API"
  "version": "1.0.0"
  "summary": "Merge, convert, compress, sign, redact and read PDFs over HTTP."
  "description": "The HTTP API behind [NexaPDF AI](https://www.nexapdfai.com) — 33 PDF tools plus an AI layer\n(summarize, translate, chat, PDF-to-Markdown).\n\n## How a tool run works\n\nEvery tool follows the same three steps:\n\n1. `POST /api/storage/presign-upload` with the file's content type, extension and byte size.\n   You get back `{ key, url }`.\n2. `PUT` the file bytes straight to `url` (S3 presigned URL). The API never sees the upload.\n3. `POST` the tool endpoint with `{ \"keys\": [key], \"options\": { ... } }`. You get back a\n   `downloadUrl` — a presigned URL for the result.\n\nLong-running tools (OCR, compress) can instead be submitted to `POST /api/jobs` and polled\nat `GET /api/jobs/{id}`.\n\n## Authentication\n\n**Optional.** Every tool here is usable without an account; anonymous callers are metered by\nIP. Sending a bearer token raises the ceilings and is required only for account-owned data\n(saved documents, subscriptions, signing history — not described here).\n\n## Rate limits\n\nResponses carry `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and\n`RateLimit-Policy`; a 429 additionally carries `Retry-After`. Self-throttle on those rather\nthan on a fixed delay.\n\n| Tier | Tool runs/min | /hour | /day |\n| --- | --- | --- | --- |\n| Guest (no token) | 20 | 200 | 500 |\n| Signed in | 60 | 1000 | 3000 |\n| Paid pass | 120 | 2000 | 6000 |\n\nPresign calls, job polling and config reads are counted in a separate, more generous\n\"plumbing\" bucket (60/min for guests) so browsing never spends a tool allowance.\n\n## Files\n\nUploads and results are removed by a one-day storage rule and are never used to train models.\n\n## Errors\n\nEvery error is JSON: `{ \"code\", \"message\", \"requestId\" }`. Branch on `code`, show `message`,\nquote `requestId` in a bug report."
  "contact":
    "name": "NexaPDF AI support"
    "url": "https://www.nexapdfai.com/contact"
    "email": "support@nexapdfai.com"
  "termsOfService": "https://www.nexapdfai.com/terms"
  "license":
    "name": "Proprietary"
    "url": "https://www.nexapdfai.com/terms"
"servers":
- "url": "https://api.nexapdfai.com"
  "description": "Production"
"externalDocs":
  "description": "Developer documentation"
  "url": "https://www.nexapdfai.com/docs"
"tags":
- "name": "Uploads"
  "description": "Getting file bytes into the pipeline."
- "name": "PDF tools"
  "description": "Synchronous PDF operations."
- "name": "AI"
  "description": "Model-backed document understanding. Metered in credits."
- "name": "Jobs"
  "description": "Asynchronous submission and polling."
- "name": "Account"
  "description": "Plan, quota and catalogue metadata."
"paths":
  "/api/storage/presign-upload":
    "post":
      "tags":
      - "Uploads"
      "operationId": "presignUpload"
      "summary": "Get a presigned S3 URL to upload a file"
      "description": "Step 1 of every tool run. Validates the declared type and size against the caller's tier cap before any bytes move, so an over-size file is refused in a few hundred bytes rather than after a 100MB upload. `PUT` the file to the returned `url`, then name the returned `key` in the tool call."
      "security":
      - 
      - "bearerAuth": []
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/PresignUploadRequest"
            "example":
              "contentType": "application/pdf"
              "extension": "pdf"
              "sizeBytes": 482113
              "tool": "merge-pdf"
      "responses":
        "200":
          "description": "A presigned upload URL and the object key to name in the tool call."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/PresignedUpload"
              "example":
                "key": "uploads/6f1c0f2e-6a1f-4a1e-9a5d-2b7c8f0e1a33.pdf"
                "url": "https://nexapdf-files-mum.s3.ap-south-1.amazonaws.com/uploads/..."
        "400":
          "description": "Invalid request body or option value."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "401":
          "description": "Sign-in required for this tool or resource."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "402":
          "description": "A paid pass is required for this tool."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "413":
          "description": "Upload exceeds the caller's size cap."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "429":
          "description": "Rate limited. `Retry-After` is the number of seconds to wait; the RateLimit-* headers describe the window that was exhausted."
          "headers":
            "Retry-After":
              "description": "Seconds to wait before retrying."
              "schema":
                "type": "integer"
                "minimum": 1
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
            "RateLimit-Policy":
              "$ref": "#/components/headers/RateLimit-Policy"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "503":
          "description": "Temporarily unable to accept work. Honour `Retry-After`."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
  "/api/pdf/{tool}":
    "post":
      "tags":
      - "PDF tools"
      "operationId": "runPdfTool"
      "summary": "Run a PDF tool on uploaded files"
      "description": "Step 3 of a tool run. `keys` are the object keys returned by presign-upload; `options` are tool-specific and all string-valued — for example `{\"ranges\": \"1-3,7\"}` for split-pdf, `{\"degrees\": \"90\"}` for rotate-pdf, `{\"password\": \"…\"}` for protect-pdf and unlock-pdf, `{\"level\": \"strong\"}` for compress-pdf. Most tools take exactly one key and refuse more than one rather than silently processing the first."
      "security":
      - 
      - "bearerAuth": []
      "parameters":
      - "name": "tool"
        "in": "path"
        "required": true
        "description": "Tool slug. Matches the URL of the tool's page on the website."
        "schema":
          "type": "string"
          "enum":
          - "merge-pdf"
          - "split-pdf"
          - "rotate-pdf"
          - "watermark-pdf"
          - "protect-pdf"
          - "unlock-pdf"
          - "jpg-to-pdf"
          - "compress-pdf"
          - "organize-pdf"
          - "page-numbers"
          - "crop-pdf"
          - "repair-pdf"
          - "pdf-to-jpg"
          - "ocr-pdf"
          - "scan-to-pdf"
          - "compare-pdf"
          - "redact-pdf"
          - "pdf-forms"
          - "pdf-to-pdfa"
          - "sign-pdf"
          - "edit-pdf"
          - "word-to-pdf"
          - "excel-to-pdf"
          - "powerpoint-to-pdf"
          - "html-to-pdf"
          - "pdf-to-word"
          - "pdf-to-excel"
          - "pdf-to-powerpoint"
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/ToolRequest"
            "example":
              "keys":
              - "uploads/6f1c0f2e-6a1f-4a1e-9a5d-2b7c8f0e1a33.pdf"
              "options":
                "ranges": "1-3,7"
      "responses":
        "200":
          "$ref": "#/components/responses/ToolResult"
        "400":
          "description": "Invalid request body or option value."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "401":
          "description": "Sign-in required for this tool or resource."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "402":
          "description": "A paid pass is required for this tool."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "413":
          "description": "Upload exceeds the caller's size cap."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "429":
          "description": "Rate limited. `Retry-After` is the number of seconds to wait; the RateLimit-* headers describe the window that was exhausted."
          "headers":
            "Retry-After":
              "description": "Seconds to wait before retrying."
              "schema":
                "type": "integer"
                "minimum": 1
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
            "RateLimit-Policy":
              "$ref": "#/components/headers/RateLimit-Policy"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "503":
          "description": "Temporarily unable to accept work. Honour `Retry-After`."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "504":
          "description": "Processing started and was abandoned. Send a smaller file."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
  "/api/ai/{operation}":
    "post":
      "tags":
      - "AI"
      "operationId": "runAiTool"
      "summary": "Run an AI operation on an uploaded document"
      "description": "Same upload-then-run shape as the PDF tools, metered additionally in AI credits. `summarize` accepts `{\"mode\": \"key-points\" | \"brief\" | \"abstract\"}`; `translate` takes `{\"targetLanguage\": \"es\"}` and returns a new PDF; `markdown` returns layout-aware Markdown for RAG pipelines; `chat` takes `{\"question\": \"…\"}` and answers from the document. A 503 `AI_UNAVAILABLE` means the model provider failed and the credit was refunded — retry is correct."
      "security":
      - 
      - "bearerAuth": []
      "parameters":
      - "name": "operation"
        "in": "path"
        "required": true
        "schema":
          "type": "string"
          "enum":
          - "summarize"
          - "translate"
          - "markdown"
          - "chat"
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/ToolRequest"
            "example":
              "keys":
              - "uploads/6f1c0f2e-6a1f-4a1e-9a5d-2b7c8f0e1a33.pdf"
              "options":
                "mode": "key-points"
      "responses":
        "200":
          "$ref": "#/components/responses/ToolResult"
        "400":
          "description": "Invalid request body or option value."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "401":
          "description": "Sign-in required for this tool or resource."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "402":
          "description": "A paid pass is required for this tool."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "413":
          "description": "Upload exceeds the caller's size cap."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "429":
          "description": "Rate limited. `Retry-After` is the number of seconds to wait; the RateLimit-* headers describe the window that was exhausted."
          "headers":
            "Retry-After":
              "description": "Seconds to wait before retrying."
              "schema":
                "type": "integer"
                "minimum": 1
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
            "RateLimit-Policy":
              "$ref": "#/components/headers/RateLimit-Policy"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "503":
          "description": "Temporarily unable to accept work. Honour `Retry-After`."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
  "/api/jobs":
    "post":
      "tags":
      - "Jobs"
      "operationId": "submitJob"
      "summary": "Submit a long-running tool as an async job"
      "description": "For tools whose work outlives a sensible request timeout. Returns immediately with a job id; poll `GET /api/jobs/{id}` until `status` is `DONE` or `FAILED`. Send an `Idempotency-Key` header and a duplicate submit returns the SAME job rather than queueing (and charging for) the work twice."
      "security":
      - 
      - "bearerAuth": []
      "parameters":
      - "$ref": "#/components/parameters/IdempotencyKey"
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/JobSubmitRequest"
            "example":
              "tool": "compress-pdf"
              "keys":
              - "uploads/6f1c0f2e-6a1f-4a1e-9a5d-2b7c8f0e1a33.pdf"
              "options":
                "level": "strong"
      "responses":
        "202":
          "description": "Accepted and queued."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/JobAccepted"
              "example":
                "jobId": "0f0a5f0e-5a3e-4a2a-9d6c-2e6b7a1d4c55"
                "status": "QUEUED"
        "400":
          "description": "Invalid request body or option value."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "401":
          "description": "Sign-in required for this tool or resource."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "402":
          "description": "A paid pass is required for this tool."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "413":
          "description": "Upload exceeds the caller's size cap."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "429":
          "description": "Rate limited. `Retry-After` is the number of seconds to wait; the RateLimit-* headers describe the window that was exhausted."
          "headers":
            "Retry-After":
              "description": "Seconds to wait before retrying."
              "schema":
                "type": "integer"
                "minimum": 1
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
            "RateLimit-Policy":
              "$ref": "#/components/headers/RateLimit-Policy"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "503":
          "description": "Temporarily unable to accept work. Honour `Retry-After`."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
  "/api/jobs/{id}":
    "get":
      "tags":
      - "Jobs"
      "operationId": "getJob"
      "summary": "Poll an async job"
      "description": "Counted in the plumbing bucket, so polling never spends a tool allowance. A job submitted with a bearer token may only be polled with that same token."
      "security":
      - 
      - "bearerAuth": []
      "parameters":
      - "name": "id"
        "in": "path"
        "required": true
        "schema":
          "type": "string"
          "format": "uuid"
      "responses":
        "200":
          "description": "Current job state; `downloadUrl` appears once `status` is `DONE`."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/JobStatus"
              "example":
                "jobId": "0f0a5f0e-5a3e-4a2a-9d6c-2e6b7a1d4c55"
                "status": "DONE"
                "downloadUrl": "https://nexapdf-files-mum.s3.ap-south-1.amazonaws.com/results/..."
                "resultBytes": 194233
        "400":
          "description": "Malformed job id."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "404":
          "description": "No such job, or it belongs to another account."
          "headers":
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
        "429":
          "description": "Rate limited. `Retry-After` is the number of seconds to wait; the RateLimit-* headers describe the window that was exhausted."
          "headers":
            "Retry-After":
              "description": "Seconds to wait before retrying."
              "schema":
                "type": "integer"
                "minimum": 1
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
            "RateLimit-Policy":
              "$ref": "#/components/headers/RateLimit-Policy"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
  "/api/pricing":
    "get":
      "tags":
      - "Account"
      "operationId": "getPricing"
      "summary": "The published plan ladder"
      "description": "Public. What each pass costs and what it unlocks."
      "security":
      - 
      "responses":
        "200":
          "description": "Plans and their limits."
          "content":
            "application/json":
              "schema":
                "type": "object"
                "additionalProperties": true
        "429":
          "description": "Rate limited. `Retry-After` is the number of seconds to wait; the RateLimit-* headers describe the window that was exhausted."
          "headers":
            "Retry-After":
              "description": "Seconds to wait before retrying."
              "schema":
                "type": "integer"
                "minimum": 1
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
            "RateLimit-Policy":
              "$ref": "#/components/headers/RateLimit-Policy"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
  "/api/config/tools":
    "get":
      "tags":
      - "Account"
      "operationId": "getToolConfig"
      "summary": "Which tools are enabled, and what each one requires"
      "description": "Public and authoritative: whether a tool needs an account or a paid pass is configuration, and this is where it is published. Read it rather than hard-coding a gate list."
      "security":
      - 
      "responses":
        "200":
          "description": "Per-tool availability and access requirements."
          "content":
            "application/json":
              "schema":
                "type": "object"
                "additionalProperties": true
        "429":
          "description": "Rate limited. `Retry-After` is the number of seconds to wait; the RateLimit-* headers describe the window that was exhausted."
          "headers":
            "Retry-After":
              "description": "Seconds to wait before retrying."
              "schema":
                "type": "integer"
                "minimum": 1
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
            "RateLimit-Policy":
              "$ref": "#/components/headers/RateLimit-Policy"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
  "/api/subscription/quota":
    "get":
      "tags":
      - "Account"
      "operationId": "getQuota"
      "summary": "Remaining AI credits for the caller"
      "description": "Public: a guest is metered too, and can read the allowance they are spending. Carries no payment or subscription detail."
      "security":
      - 
      - "bearerAuth": []
      "responses":
        "200":
          "description": "Credit balance for the caller."
          "content":
            "application/json":
              "schema":
                "type": "object"
                "additionalProperties": true
        "429":
          "description": "Rate limited. `Retry-After` is the number of seconds to wait; the RateLimit-* headers describe the window that was exhausted."
          "headers":
            "Retry-After":
              "description": "Seconds to wait before retrying."
              "schema":
                "type": "integer"
                "minimum": 1
            "RateLimit-Limit":
              "$ref": "#/components/headers/RateLimit-Limit"
            "RateLimit-Remaining":
              "$ref": "#/components/headers/RateLimit-Remaining"
            "RateLimit-Reset":
              "$ref": "#/components/headers/RateLimit-Reset"
            "RateLimit-Policy":
              "$ref": "#/components/headers/RateLimit-Policy"
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/Error"
  "/actuator/health":
    "get":
      "tags":
      - "Account"
      "operationId": "health"
      "summary": "Service health"
      "security":
      - 
      "responses":
        "200":
          "description": "`{\"status\":\"UP\"}` when the API and its dependencies are healthy."
          "content":
            "application/json":
              "schema":
                "type": "object"
                "additionalProperties": true
"components":
  "securitySchemes":
    "bearerAuth":
      "type": "http"
      "scheme": "bearer"
      "bearerFormat": "JWT"
      "description": "Optional. A Cognito ID token, sent as `Authorization: Bearer <token>`. Raises rate and size limits and unlocks account-owned resources. Every tool endpoint above also works with no token at all."
  "parameters":
    "IdempotencyKey":
      "name": "Idempotency-Key"
      "in": "header"
      "required": false
      "description": "An opaque, caller-generated string. Replaying a request with the same key returns the original result instead of doing (and charging for) the work again — which is what makes a retry after a network timeout safe."
      "schema":
        "type": "string"
        "maxLength": 200
  "headers":
    "RateLimit-Limit":
      "description": "Requests permitted in the current window."
      "schema":
        "type": "integer"
    "RateLimit-Remaining":
      "description": "Requests still permitted in the current window."
      "schema":
        "type": "integer"
    "RateLimit-Reset":
      "description": "Seconds until the window has refilled enough for one more request."
      "schema":
        "type": "integer"
    "RateLimit-Policy":
      "description": "The policy the counters describe, e.g. `20;w=60`."
      "schema":
        "type": "string"
  "responses":
    "ToolResult":
      "description": "The tool ran. `downloadUrl` is a short-lived presigned URL for the result."
      "headers":
        "RateLimit-Limit":
          "$ref": "#/components/headers/RateLimit-Limit"
        "RateLimit-Remaining":
          "$ref": "#/components/headers/RateLimit-Remaining"
        "RateLimit-Reset":
          "$ref": "#/components/headers/RateLimit-Reset"
        "RateLimit-Policy":
          "$ref": "#/components/headers/RateLimit-Policy"
      "content":
        "application/json":
          "schema":
            "$ref": "#/components/schemas/ToolResult"
          "example":
            "downloadUrl": "https://nexapdf-files-mum.s3.ap-south-1.amazonaws.com/results/..."
            "resultBytes": 194233
  "schemas":
    "Error":
      "type": "object"
      "required":
      - "code"
      - "message"
      "description": "Every failure from this API has this shape. `code` is stable and safe to branch on; `message` is written for a human and may change."
      "properties":
        "code":
          "type": "string"
          "description": "Stable machine-readable reason."
          "enum":
          - "BAD_REQUEST"
          - "PASSWORD_REJECTED"
          - "SIGN_IN_REQUIRED"
          - "FORBIDDEN"
          - "OBJECT_FORBIDDEN"
          - "NOT_FOUND"
          - "METHOD_NOT_ALLOWED"
          - "NOT_ACCEPTABLE"
          - "PREMIUM_REQUIRED"
          - "FILE_TOO_LARGE"
          - "UNSUPPORTED_MEDIA_TYPE"
          - "RATE_LIMITED"
          - "AI_BUDGET_EXCEEDED"
          - "SERVICE_BUSY"
          - "AI_UNAVAILABLE"
          - "PROCESSING_TIMEOUT"
          - "PROCESSING_FAILED"
        "message":
          "type": "string"
          "description": "What went wrong, and where possible what to do about it."
        "requestId":
          "type": "string"
          "description": "Correlates this response with the server logs. Quote it in a bug report."
        "field":
          "type": "string"
          "description": "Present when one named input caused the failure, e.g. `password`."
        "retryAfterSeconds":
          "type": "integer"
          "description": "Present on 429 — the same value as the `Retry-After` header."
      "example":
        "code": "FILE_TOO_LARGE"
        "message": "That file is 42 MB. Guests can upload up to 10 MB — sign in for 25 MB."
        "requestId": "524b70c3a73a4961a5a08b36da303705"
    "PresignUploadRequest":
      "type": "object"
      "required":
      - "contentType"
      - "extension"
      - "sizeBytes"
      "properties":
        "contentType":
          "type": "string"
          "description": "MIME type of the file, e.g. `application/pdf`."
        "extension":
          "type": "string"
          "description": "File extension without the dot, e.g. `pdf`."
        "sizeBytes":
          "type": "integer"
          "minimum": 1
          "description": "Exact byte length of the upload."
        "tool":
          "type": "string"
          "description": "The tool this upload is for. Optional, and worth sending: it moves an over-size rejection ahead of the upload instead of after it."
    "PresignedUpload":
      "type": "object"
      "required":
      - "key"
      - "url"
      "properties":
        "key":
          "type": "string"
          "description": "Object key to name in `keys` on the tool call."
        "url":
          "type": "string"
          "format": "uri"
          "description": "Short-lived presigned URL to `PUT` the bytes to."
    "ToolRequest":
      "type": "object"
      "required":
      - "keys"
      "properties":
        "keys":
          "type": "array"
          "minItems": 1
          "items":
            "type": "string"
          "description": "Object keys from presign-upload, in the order the tool should read them."
        "options":
          "type": "object"
          "additionalProperties":
            "type": "string"
          "description": "Tool-specific options. All values are strings."
    "ToolResult":
      "type": "object"
      "required":
      - "downloadUrl"
      - "resultBytes"
      "properties":
        "downloadUrl":
          "type": "string"
          "format": "uri"
          "description": "Presigned URL for the produced file."
        "resultBytes":
          "type": "integer"
          "description": "Size of the result in bytes."
        "compression":
          "type": "object"
          "additionalProperties": true
          "description": "Compress PDF only — what it did to the file, or why it could not shrink it."
    "JobSubmitRequest":
      "type": "object"
      "required":
      - "tool"
      - "keys"
      "properties":
        "tool":
          "type": "string"
          "description": "Tool slug. Only tools that run asynchronously are accepted."
        "keys":
          "type": "array"
          "minItems": 1
          "items":
            "type": "string"
        "options":
          "type": "object"
          "additionalProperties":
            "type": "string"
    "JobAccepted":
      "type": "object"
      "required":
      - "jobId"
      - "status"
      "properties":
        "jobId":
          "type": "string"
          "format": "uuid"
        "status":
          "type": "string"
          "enum":
          - "QUEUED"
          - "RUNNING"
          - "DONE"
          - "FAILED"
    "JobStatus":
      "type": "object"
      "required":
      - "jobId"
      - "status"
      "properties":
        "jobId":
          "type": "string"
          "format": "uuid"
        "status":
          "type": "string"
          "enum":
          - "QUEUED"
          - "RUNNING"
          - "DONE"
          - "FAILED"
        "downloadUrl":
          "type": "string"
          "format": "uri"
          "description": "Present once `status` is `DONE`."
        "resultBytes":
          "type": "integer"
        "error":
          "type": "string"
          "description": "Present when `status` is `FAILED`."
