{
  "openapi": "3.1.0",
  "info": {
    "title": "Ordalis API",
    "version": "1.0.0",
    "description": "AI-powered document extraction — convert PDFs, invoices, bank statements, contracts, and more into clean structured data (JSON, XLSX, CSV, XML, YAML, SQL).\n\nBase URL: `https://api.ordalis.io`\n\nAll requests require authentication via API key (recommended for servers) or JWT bearer token (for user sessions).",
    "contact": {
      "name": "Ordalis Support",
      "email": "support@ordalis.io",
      "url": "https://ordalis.io"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://ordalis.io/terms"
    }
  },
  "servers": [
    { "url": "https://api.ordalis.io", "description": "Production" },
    { "url": "https://ordalis-api.tyler-gee13.workers.dev", "description": "Production (workers.dev fallback)" }
  ],
  "security": [
    { "bearerAuth": [] },
    { "apiKeyAuth": [] }
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Health check",
        "description": "Lightweight liveness probe. Returns immediately without hitting the database.",
        "security": [],
        "responses": {
          "200": { "description": "Service is up", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } } }
        }
      }
    },
    "/v1/auth/register": {
      "post": {
        "summary": "Register a new account",
        "security": [],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RegisterRequest" } } }
        },
        "responses": {
          "201": { "description": "Account created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AuthResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/v1/auth/login": {
      "post": {
        "summary": "Login",
        "security": [],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "required": ["email", "password"],
            "properties": {
              "email": { "type": "string", "format": "email" },
              "password": { "type": "string" }
            }
          } } }
        },
        "responses": {
          "200": { "description": "Logged in", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AuthResponse" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/auth/refresh": {
      "post": {
        "summary": "Refresh an access token",
        "description": "Exchange a 30-day refresh token for a new 1-hour access token. Session must still be active.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "required": ["refresh_token"],
            "properties": { "refresh_token": { "type": "string" } }
          } } }
        },
        "responses": {
          "200": { "description": "Access token refreshed", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "token": { "type": "string" },
              "access_expires_in": { "type": "integer", "example": 3600 }
            }
          } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/auth/me": {
      "get": {
        "summary": "Get current user",
        "responses": {
          "200": { "description": "User object", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } }
        }
      }
    },
    "/v1/convert": {
      "post": {
        "summary": "Convert a document to structured data",
        "description": "Extract structured data from an uploaded file. Paid plans must send an `Idempotency-Key` header to prevent duplicate billing on retries.\n\nThe response includes a `download_url` for XLSX / CSV / binary outputs; make an authenticated GET to that URL to retrieve the file bytes.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per request; required for paid tiers.",
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file": { "type": "string", "format": "binary", "description": "File to extract" },
                  "output": { "type": "string", "enum": ["json", "xlsx", "csv", "xml", "yaml", "sql", "parquet"], "default": "json" },
                  "schema": { "type": "string", "enum": ["auto", "flat", "nested", "custom"], "default": "auto" },
                  "extraction": { "type": "string", "enum": ["full", "tables", "text", "kv", "entities"], "default": "full" },
                  "custom_schema": { "type": "string", "description": "JSON Schema as a string (when schema=custom)" },
                  "template_id": { "type": "string", "description": "Saved template ID (overrides schema)" },
                  "language": { "type": "string", "default": "auto" },
                  "engine": { "type": "string", "enum": ["cloudflare", "anthropic", "openai"], "default": "cloudflare" },
                  "model": { "type": "string", "description": "Model alias" },
                  "webhook_url": { "type": "string", "format": "uri" },
                  "async": { "type": "string", "enum": ["true", "false"], "default": "false" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Conversion completed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConversionResponse" } } } },
          "202": { "description": "Queued for async processing" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "402": { "description": "Quota exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "413": { "description": "File too large for sync processing" },
          "415": { "description": "Malware detected" },
          "503": { "description": "PII redaction failed (strict workspaces only)" }
        }
      }
    },
    "/v1/conversions/{id}/download": {
      "get": {
        "summary": "Download a conversion's output file",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "File bytes", "content": {
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": { "schema": { "type": "string", "format": "binary" } },
            "application/json": { "schema": { "type": "object" } },
            "text/csv": { "schema": { "type": "string" } }
          } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/conversions": {
      "get": {
        "summary": "List conversions",
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } }
        ],
        "responses": {
          "200": { "description": "List of conversions", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "conversions": { "type": "array", "items": { "$ref": "#/components/schemas/Conversion" } },
              "total": { "type": "integer" }
            }
          } } } }
        }
      }
    },
    "/v1/templates": {
      "get": {
        "summary": "List saved templates",
        "responses": {
          "200": { "description": "Templates", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "templates": { "type": "array", "items": { "$ref": "#/components/schemas/Template" } },
              "quota": { "type": "object" }
            }
          } } } }
        }
      },
      "post": {
        "summary": "Create a template from a column definition",
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "required": ["name", "columns"],
          "properties": {
            "name": { "type": "string" },
            "description": { "type": "string" },
            "columns": { "type": "array", "items": { "$ref": "#/components/schemas/TemplateColumn" } }
          }
        } } } },
        "responses": {
          "201": { "description": "Template created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Template" } } } }
        }
      }
    },
    "/v1/templates/from-upload": {
      "post": {
        "summary": "Create a template by analyzing an example document",
        "description": "Upload a sample file; we analyze its structure and propose columns. Returns a DRAFT template + a chat conversation ID for refinement.",
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": {
          "type": "object",
          "required": ["file"],
          "properties": {
            "file": { "type": "string", "format": "binary" },
            "name": { "type": "string" },
            "description": { "type": "string" }
          }
        } } } },
        "responses": {
          "201": { "description": "Draft template created", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "template": { "$ref": "#/components/schemas/Template" },
              "conversation_id": { "type": "string" },
              "suggested_columns": { "type": "array" }
            }
          } } } }
        }
      }
    },
    "/v1/templates/from-spec": {
      "post": {
        "summary": "Create a template from a pre-made spec file",
        "description": "Upload a JSON Schema, CSV with headers, or XLSX with headers. Creates a CONFIRMED template (no chat step).",
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": {
          "type": "object",
          "required": ["file"],
          "properties": {
            "file": { "type": "string", "format": "binary" },
            "name": { "type": "string" }
          }
        } } } },
        "responses": {
          "201": { "description": "Template created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Template" } } } }
        }
      }
    },
    "/v1/templates/{id}/confirm": {
      "patch": {
        "summary": "Confirm a draft template",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "columns": { "type": "array" },
            "name": { "type": "string" }
          }
        } } } },
        "responses": {
          "200": { "description": "Template confirmed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Template" } } } }
        }
      }
    },
    "/v1/templates/{id}/preview": {
      "post": {
        "summary": "Preview a template — run extraction on the stored example",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Preview + workbook spec", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "workbook_spec": { "type": "object" },
              "sample_data": { "type": "object" },
              "extraction_time_ms": { "type": "integer" }
            }
          } } } }
        }
      }
    },
    "/v1/keys": {
      "get": { "summary": "List API keys", "responses": { "200": { "description": "Keys" } } },
      "post": { "summary": "Create API key", "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" } } } } } }, "responses": { "201": { "description": "Created" } } }
    },
    "/v1/keys/{id}": {
      "delete": { "summary": "Revoke API key", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "204": { "description": "Revoked" } } }
    },
    "/v1/usage": {
      "get": { "summary": "Current usage + quota", "responses": { "200": { "description": "Usage stats" } } }
    },
    "/v1/billing/checkout": {
      "post": {
        "summary": "Start a Stripe checkout session for an upgrade",
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "plan": { "type": "string", "enum": ["plus", "pro", "business"] } } } } } },
        "responses": { "200": { "description": "Checkout URL" } }
      }
    },
    "/v1/billing/portal": {
      "post": { "summary": "Open the Stripe customer portal", "responses": { "200": { "description": "Portal URL" } } }
    },
    "/v1/webhooks": {
      "get": { "summary": "List configured webhooks", "responses": { "200": { "description": "Webhooks" } } },
      "post": { "summary": "Register a webhook", "responses": { "201": { "description": "Registered" } } }
    },
    "/v1/data/delete-request": {
      "post": { "summary": "Request GDPR/CCPA data deletion (30-day hold)", "responses": { "201": { "description": "Scheduled" } } },
      "get": { "summary": "List pending deletion requests", "responses": { "200": { "description": "Requests" } } }
    },
    "/v1/data/export": {
      "get": { "summary": "Export all your data (GDPR right of access)", "responses": { "200": { "description": "Export archive" } } }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Short-lived (1h) JWT access token. Obtain via /v1/auth/login or /v1/auth/register."
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Long-lived API key for server-to-server use. Prefix `sk_live_…`."
      }
    },
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "example": "ok" },
          "version": { "type": "string" }
        }
      },
      "RegisterRequest": {
        "type": "object",
        "required": ["email", "password", "accept_tos", "accept_privacy"],
        "properties": {
          "email": { "type": "string", "format": "email" },
          "password": { "type": "string", "minLength": 8 },
          "name": { "type": "string" },
          "company": { "type": "string" },
          "accept_tos": { "type": "boolean" },
          "accept_privacy": { "type": "boolean" }
        }
      },
      "AuthResponse": {
        "type": "object",
        "properties": {
          "user": { "$ref": "#/components/schemas/User" },
          "token": { "type": "string", "description": "1-hour access token" },
          "refresh_token": { "type": "string", "description": "30-day refresh token" },
          "access_expires_in": { "type": "integer" },
          "refresh_expires_in": { "type": "integer" },
          "api_key": { "type": "string", "description": "Returned once at registration" }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "email": { "type": "string", "format": "email" },
          "name": { "type": "string" },
          "plan": { "type": "string", "enum": ["free", "plus", "pro", "business", "enterprise"] },
          "role": { "type": "string" },
          "email_verified": { "type": "boolean" },
          "mfa_enabled": { "type": "boolean" }
        }
      },
      "ConversionResponse": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "conversion_id": { "type": "string" },
          "status": { "type": "string", "enum": ["completed", "failed", "queued", "processing"] },
          "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
          "pages_processed": { "type": "integer" },
          "extraction_time_ms": { "type": "integer" },
          "model": { "type": "string" },
          "parser": { "type": "string" },
          "data": { "type": "object", "description": "Extracted structured data" },
          "download_url": { "type": "string", "format": "uri" },
          "sources_url": { "type": "string", "format": "uri" }
        }
      },
      "Conversion": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "source_filename": { "type": "string" },
          "output_format": { "type": "string" },
          "status": { "type": "string" },
          "confidence": { "type": "number" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "Template": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": "string" },
          "status": { "type": "string", "enum": ["draft", "confirmed"] },
          "column_count": { "type": "integer" },
          "columns": { "type": "array", "items": { "$ref": "#/components/schemas/TemplateColumn" } },
          "times_used": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "TemplateColumn": {
        "type": "object",
        "required": ["name", "type"],
        "properties": {
          "name": { "type": "string", "description": "snake_case identifier" },
          "display_name": { "type": "string" },
          "type": { "type": "string", "enum": ["string", "number", "boolean", "date"] },
          "description": { "type": "string" },
          "example": { "type": "string" },
          "required": { "type": "boolean" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "code": { "type": "string" },
          "request_id": { "type": "string" },
          "upgrade_url": { "type": "string", "format": "uri" }
        }
      }
    },
    "responses": {
      "Unauthorized": { "description": "Authentication failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "BadRequest": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "Resource not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
