{
  "openapi": "3.0.3",
  "info": {
    "title": "pdfxdoc",
    "version": "1.0.0",
    "description": "REST API for PDF document parsing and structured data extraction. Tables, Arabic & English OCR, multiple output formats (JSON, Markdown, HTML, DOCX)."
  },
  "servers": [{ "url": "https://pdfxdoc.com", "description": "Production" }],
  "tags": [
    { "name": "Auth", "description": "Account management" },
    { "name": "Keys", "description": "API key management" },
    { "name": "Usage", "description": "Usage statistics" },
    { "name": "Conversion", "description": "PDF processing" },
    { "name": "Webhooks", "description": "Webhook secret management" },
    { "name": "Playground", "description": "Rate-limited demo (no API key)" },
    { "name": "System", "description": "Health check and contact" }
  ],
  "paths": {
    "/api/auth/register": {
      "post": {
        "summary": "Create account",
        "operationId": "register",
        "tags": ["Auth"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/RegisterRequest" }
            }
          }
        },
        "responses": {
          "200": { "description": "Account created with API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RegisterResponse" } } } },
          "400": { "description": "Invalid input or duplicate email", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/auth/login": {
      "post": {
        "summary": "Sign in",
        "operationId": "login",
        "tags": ["Auth"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LoginRequest" }
            }
          }
        },
        "responses": {
          "200": { "description": "JWT access token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginResponse" } } } },
          "401": { "description": "Invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/auth/forgot-password": {
      "post": {
        "summary": "Request password reset",
        "operationId": "forgotPassword",
        "tags": ["Auth"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ForgotPasswordRequest" }
            }
          }
        },
        "responses": {
          "200": { "description": "Always returns 200 to prevent email enumeration", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } }
        }
      }
    },
    "/api/auth/reset-password": {
      "post": {
        "summary": "Reset password",
        "operationId": "resetPassword",
        "tags": ["Auth"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ResetPasswordRequest" }
            }
          }
        },
        "responses": {
          "200": { "description": "Password reset", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } },
          "400": { "description": "Invalid token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/keys": {
      "get": {
        "summary": "List API keys",
        "operationId": "listKeys",
        "tags": ["Keys"],
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": { "description": "Array of keys", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ApiKey" } } } } }
        }
      }
    },
    "/api/keys/create": {
      "post": {
        "summary": "Create API key",
        "operationId": "createKey",
        "tags": ["Keys"],
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateKeyRequest" }
            }
          }
        },
        "responses": {
          "200": { "description": "Full key returned once", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateKeyResponse" } } } }
        }
      }
    },
    "/api/keys/{id}": {
      "delete": {
        "summary": "Revoke key",
        "operationId": "revokeKey",
        "tags": ["Keys"],
        "security": [{ "bearerAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Key revoked", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } }
        }
      }
    },
    "/api/usage": {
      "get": {
        "summary": "Usage stats",
        "operationId": "getUsage",
        "tags": ["Usage"],
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": { "description": "Usage + tier info", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UsageStats" } } } }
        }
      }
    },
    "/api/v1/upload": {
      "post": {
        "summary": "Get presigned upload URL for large files",
        "operationId": "getUploadUrl",
        "tags": ["Conversion"],
        "security": [{ "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/UploadRequest" }
            }
          }
        },
        "responses": {
          "200": { "description": "Presigned URL + r2_key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UploadUrl" } } } },
          "413": { "description": "File too large for tier", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/v1/convert": {
      "post": {
        "summary": "Submit PDF for processing (multipart or JSON with r2_key)",
        "operationId": "convert",
        "tags": ["Conversion"],
        "security": [{ "apiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": { "$ref": "#/components/schemas/ConvertMultipartRequest" }
            },
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ConvertJsonRequest" }
            }
          }
        },
        "responses": {
          "200": { "description": "Task queued with rate limit headers", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TaskInfo" } } } },
          "400": { "description": "Invalid input", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "413": { "description": "File too large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "Rate limit or concurrency cap", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/v1/result/{task_id}": {
      "get": {
        "summary": "Poll task result",
        "operationId": "getResult",
        "tags": ["Conversion"],
        "security": [{ "apiKeyAuth": [] }],
        "parameters": [{ "name": "task_id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Task status + result if done", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConvertResult" } } } },
          "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/v1/download/{task_id}": {
      "get": {
        "summary": "Download DOCX result",
        "operationId": "downloadDocx",
        "tags": ["Conversion"],
        "security": [{ "apiKeyAuth": [] }],
        "parameters": [{ "name": "task_id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "DOCX binary" },
          "404": { "description": "Not found or not ready", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/webhook-secret": {
      "get": {
        "summary": "Check webhook secret status",
        "operationId": "getWebhookSecret",
        "tags": ["Webhooks"],
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": { "description": "Secret status", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookSecretStatus" } } } }
        }
      },
      "put": {
        "summary": "Set webhook secret",
        "operationId": "setWebhookSecret",
        "tags": ["Webhooks"],
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SetWebhookSecretRequest" }
            }
          }
        },
        "responses": {
          "200": { "description": "Saved", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } },
          "400": { "description": "Secret too short", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      },
      "delete": {
        "summary": "Remove webhook secret",
        "operationId": "deleteWebhookSecret",
        "tags": ["Webhooks"],
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": { "description": "Secret removed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } }
        }
      }
    },
    "/api/tasks": {
      "get": {
        "summary": "List recent tasks",
        "operationId": "listTasks",
        "tags": ["Conversion"],
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": { "description": "Recent tasks", "content": { "application/json": { "schema": { "type": "object", "properties": { "tasks": { "type": "array", "items": { "$ref": "#/components/schemas/TaskSummary" } } } } } } }
        }
      }
    },
    "/api/playground/convert": {
      "post": {
        "summary": "Demo conversion (rate-limited, no API key)",
        "operationId": "playgroundConvert",
        "tags": ["Playground"],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": { "$ref": "#/components/schemas/PlaygroundConvertRequest" }
            }
          }
        },
        "responses": {
          "200": { "description": "Task queued", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TaskInfo" } } } },
          "400": { "description": "Invalid file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "413": { "description": "File too large (max 5MB)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "Demo limit reached", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/playground/result/{task_id}": {
      "get": {
        "summary": "Poll demo task result",
        "operationId": "playgroundResult",
        "tags": ["Playground"],
        "parameters": [{ "name": "task_id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Task status + result if done", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConvertResult" } } } },
          "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/contact": {
      "post": {
        "summary": "Send contact message",
        "operationId": "contact",
        "tags": ["System"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ContactRequest" }
            }
          }
        },
        "responses": {
          "200": { "description": "Message received", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } },
          "400": { "description": "Invalid input", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "Too many messages", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/health": {
      "get": {
        "summary": "Health check",
        "operationId": "healthCheck",
        "tags": ["System"],
        "responses": {
          "200": { "description": "System status", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HealthStatus" } } } }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-Api-Key", "description": "API key for conversion endpoints. Create one at /api/keys/create after registering." },
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "JWT session token from /api/auth/login. Used for account management endpoints." }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": { "type": "string" }
        }
      },
      "MessageResponse": {
        "type": "object",
        "properties": {
          "message": { "type": "string" }
        }
      },
      "RegisterRequest": {
        "type": "object",
        "required": ["email", "password"],
        "properties": {
          "email": { "type": "string", "format": "email" },
          "password": { "type": "string", "minLength": 8 },
          "name": { "type": "string" }
        }
      },
      "RegisterResponse": {
        "type": "object",
        "properties": {
          "api_key": { "type": "string", "description": "Full API key — shown only once" },
          "access_token": { "type": "string" },
          "token_type": { "type": "string", "enum": ["bearer"] },
          "message": { "type": "string" }
        }
      },
      "LoginRequest": {
        "type": "object",
        "required": ["email", "password"],
        "properties": {
          "email": { "type": "string" },
          "password": { "type": "string" }
        }
      },
      "LoginResponse": {
        "type": "object",
        "properties": {
          "access_token": { "type": "string" },
          "token_type": { "type": "string", "enum": ["bearer"] }
        }
      },
      "ForgotPasswordRequest": {
        "type": "object",
        "required": ["email"],
        "properties": {
          "email": { "type": "string" }
        }
      },
      "ResetPasswordRequest": {
        "type": "object",
        "required": ["token", "new_password"],
        "properties": {
          "token": { "type": "string" },
          "new_password": { "type": "string", "minLength": 8 }
        }
      },
      "CreateKeyRequest": {
        "type": "object",
        "properties": {
          "label": { "type": "string" }
        }
      },
      "CreateKeyResponse": {
        "type": "object",
        "properties": {
          "key": { "type": "string" },
          "key_prefix": { "type": "string" },
          "message": { "type": "string" }
        }
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "key_prefix": { "type": "string" },
          "label": { "type": "string", "nullable": true },
          "is_active": { "type": "boolean" },
          "created_at": { "type": "string", "format": "date-time" },
          "last_used_at": { "type": "string", "format": "date-time", "nullable": true }
        }
      },
      "UsageStats": {
        "type": "object",
        "properties": {
          "pages_today": { "type": "integer" },
          "pages_this_hour": { "type": "integer" },
          "total_tasks": { "type": "integer" },
          "tier": { "type": "string" },
          "limit_pages_per_hour": { "type": "integer" },
          "limit_pages_per_day": { "type": "integer" },
          "max_file_size_mb": { "type": "integer" },
          "max_concurrent": { "type": "integer" },
          "has_webhooks": { "type": "boolean" },
          "has_email": { "type": "boolean" }
        }
      },
      "UploadRequest": {
        "type": "object",
        "required": ["filename", "size"],
        "properties": {
          "filename": { "type": "string" },
          "size": { "type": "integer" }
        }
      },
      "UploadUrl": {
        "type": "object",
        "properties": {
          "upload_url": { "type": "string" },
          "r2_key": { "type": "string" },
          "upload_token": { "type": "string" },
          "expires_in": { "type": "integer" },
          "message": { "type": "string" }
        }
      },
      "ConvertMultipartRequest": {
        "type": "object",
        "required": ["file"],
        "properties": {
          "file": { "type": "string", "format": "binary", "description": "PDF file" },
          "output_format": { "type": "string", "enum": ["json", "docx", "md", "html"], "default": "json" },
          "ocr_lang": { "type": "string", "default": "eng" },
          "table_mode": { "type": "string", "enum": ["fast", "accurate"], "default": "accurate" },
          "webhook_url": { "type": "string", "format": "uri" }
        }
      },
      "ConvertJsonRequest": {
        "type": "object",
        "required": ["r2_key"],
        "properties": {
          "r2_key": { "type": "string" },
          "output_format": { "type": "string", "enum": ["json", "docx", "md", "html"], "default": "json" },
          "ocr_lang": { "type": "string", "default": "eng" },
          "table_mode": { "type": "string", "enum": ["fast", "accurate"], "default": "accurate" },
          "webhook_url": { "type": "string", "format": "uri" }
        }
      },
      "TaskInfo": {
        "type": "object",
        "properties": {
          "task_id": { "type": "string" },
          "status": { "type": "string", "enum": ["queued"] }
        }
      },
      "TaskSummary": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "status": { "type": "string" },
          "input_filename": { "type": "string" },
          "output_format": { "type": "string" },
          "page_count": { "type": "integer", "nullable": true },
          "duration_ms": { "type": "integer", "nullable": true },
          "error_message": { "type": "string", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "ConvertResult": {
        "type": "object",
        "properties": {
          "task_id": { "type": "string" },
          "status": { "type": "string", "enum": ["queued", "processing", "done", "failed"] },
          "document": { "nullable": true },
          "error_message": { "type": "string", "nullable": true },
          "processing_time": { "type": "number", "nullable": true }
        }
      },
      "WebhookSecretStatus": {
        "type": "object",
        "properties": {
          "configured": { "type": "boolean" },
          "created_at": { "type": "string", "format": "date-time", "nullable": true },
          "updated_at": { "type": "string", "format": "date-time", "nullable": true }
        }
      },
      "SetWebhookSecretRequest": {
        "type": "object",
        "required": ["secret"],
        "properties": {
          "secret": { "type": "string", "minLength": 16 }
        }
      },
      "PlaygroundConvertRequest": {
        "type": "object",
        "required": ["file"],
        "properties": {
          "file": { "type": "string", "format": "binary", "description": "PDF file, max 5MB" },
          "output_format": { "type": "string", "enum": ["json", "docx", "md", "html"], "default": "json" },
          "ocr_lang": { "type": "string", "default": "eng" },
          "table_mode": { "type": "string", "enum": ["fast", "accurate"], "default": "accurate" }
        }
      },
      "ContactRequest": {
        "type": "object",
        "required": ["email", "message"],
        "properties": {
          "name": { "type": "string", "maxLength": 200 },
          "email": { "type": "string", "format": "email" },
          "subject": { "type": "string", "default": "general" },
          "message": { "type": "string", "maxLength": 5000 }
        }
      },
      "HealthStatus": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "enum": ["ok", "degraded"] },
          "processor": { "type": "string", "enum": ["connected", "disconnected"] },
          "last_processor_poll": { "type": "string", "format": "date-time", "nullable": true },
          "ts": { "type": "integer" }
        }
      }
    }
  }
}
