{
  "openapi": "3.1.0",
  "info": {
    "title": "ground.md health and telemetry API",
    "version": "1.0.0",
    "description": "This API is health checks, public aggregate telemetry, and completion-event ingestion only. It is not a PDF conversion or upload API. PDF parsing runs in the browser with WebAssembly; document bytes are not sent here. Agents must not call POST /api/v1/metrics/pdf-processed manually — the web app emits that event after a local conversion.\n\n## Versioning and deprecation\n\nThe current API version is `/api/v1`. Unversioned `/api/*` routes remain available as deprecated aliases and return `Deprecation: true`; clients should use `/api/v1/*`. Any removal will be announced here before it occurs. Responses publish the advisory `RateLimit-Policy` header; no server-enforced per-client quota exists today. A `503` response includes `Retry-After: 60`."
  },
  "servers": [
    { "url": "https://ground.md", "description": "Production; current API paths are /api/v1/*" }
  ],
  "tags": [
    { "name": "health", "description": "Liveness" },
    { "name": "telemetry", "description": "Anonymous aggregates and completion events" }
  ],
  "paths": {
    "/api/v1/health": {
      "get": {
        "operationId": "getHealthV1",
        "tags": ["health"],
        "summary": "Service health",
        "description": "Returns a liveness payload for the ground.md Worker. Does not convert PDFs or accept file uploads.",
        "responses": {
          "200": {
            "description": "Worker is reachable.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } }
            }
          },
          "500": {
            "description": "Uncaught API failure.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/api/v1/stats": {
      "get": {
        "operationId": "getPublicStatsV1",
        "tags": ["telemetry"],
        "summary": "Public aggregate conversion metrics",
        "description": "Lifetime anonymous totals written after local browser conversions. Does not include filenames, PDF bytes, or user identifiers.",
        "responses": {
          "200": {
            "description": "Current public totals.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/PublicStats" } }
            }
          },
          "503": {
            "description": "Aggregate storage is unavailable.",
            "headers": { "Retry-After": { "schema": { "type": "integer", "const": 60 } } },
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "500": {
            "description": "Uncaught API failure.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/api/v1/metrics/pdf-processed": {
      "post": {
        "operationId": "ingestPdfProcessedMetricV1",
        "tags": ["telemetry"],
        "summary": "Ingest anonymous conversion completion event",
        "description": "Accepts the anonymous completion metric emitted by the ground.md web app after a local parse. Do not call this endpoint manually. It does not accept PDF bytes and does not start a conversion.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/PdfProcessedMetric" } }
          }
        },
        "responses": {
          "204": { "description": "Event recorded. Empty body." },
          "400": {
            "description": "Body is not valid JSON or fails metric validation.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "503": {
            "description": "Metric storage is unavailable.",
            "headers": { "Retry-After": { "schema": { "type": "integer", "const": 60 } } },
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "500": {
            "description": "Uncaught API failure.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "getHealthLegacy",
        "deprecated": true,
        "tags": ["health"],
        "summary": "Deprecated service health",
        "description": "Deprecated alias for GET /api/v1/health. Returns Deprecation: true.",
        "responses": {
          "200": {
            "description": "Worker is reachable.",
            "headers": { "Deprecation": { "schema": { "type": "string", "const": "true" } } },
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } }
            }
          },
          "500": {
            "description": "Uncaught API failure.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/api/stats": {
      "get": {
        "operationId": "getPublicStatsLegacy",
        "deprecated": true,
        "tags": ["telemetry"],
        "summary": "Deprecated public aggregate conversion metrics",
        "description": "Deprecated alias for GET /api/v1/stats. Returns Deprecation: true.",
        "responses": {
          "200": {
            "description": "Current public totals.",
            "headers": { "Deprecation": { "schema": { "type": "string", "const": "true" } } },
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/PublicStats" } }
            }
          },
          "503": {
            "description": "Aggregate storage is unavailable.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "500": {
            "description": "Uncaught API failure.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/api/metrics/pdf-processed": {
      "post": {
        "operationId": "ingestPdfProcessedMetricLegacy",
        "deprecated": true,
        "tags": ["telemetry"],
        "summary": "Deprecated anonymous completion event",
        "description": "Deprecated alias for POST /api/v1/metrics/pdf-processed. Returns Deprecation: true. Do not call manually.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/PdfProcessedMetric" } }
          }
        },
        "responses": {
          "204": {
            "description": "Event recorded. Empty body.",
            "headers": { "Deprecation": { "schema": { "type": "string", "const": "true" } } }
          },
          "400": {
            "description": "Body is not valid JSON or fails metric validation.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "503": {
            "description": "Metric storage is unavailable.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "500": {
            "description": "Uncaught API failure.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": ["ok", "service"],
        "properties": {
          "ok": { "type": "boolean", "const": true },
          "service": { "type": "string", "const": "groundmd" }
        }
      },
      "PublicStats": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "jobs",
          "pagesProcessed",
          "processingTimeMs",
          "averageProcessingTimePerPageMs"
        ],
        "properties": {
          "jobs": { "type": "integer", "minimum": 0 },
          "pagesProcessed": { "type": "integer", "minimum": 0 },
          "processingTimeMs": { "type": "integer", "minimum": 0 },
          "averageProcessingTimePerPageMs": { "type": "number", "minimum": 0 }
        }
      },
      "PdfProcessedMetric": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "event",
          "processedPages",
          "processingDurationMs",
          "documentPages",
          "pagesNeedingOcr"
        ],
        "properties": {
          "event": { "type": "string", "const": "pdf_processed" },
          "processedPages": { "type": "integer", "minimum": 0, "maximum": 1000000 },
          "processingDurationMs": { "type": "integer", "minimum": 0, "maximum": 604800000 },
          "documentPages": { "type": ["integer", "null"], "minimum": 0, "maximum": 1000000 },
          "pagesNeedingOcr": { "type": ["integer", "null"], "minimum": 0, "maximum": 1000000 }
        }
      },
      "ApiError": {
        "type": "object",
        "additionalProperties": false,
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": ["code", "message", "resolution", "docs"],
            "properties": {
              "code": { "type": "string" },
              "message": { "type": "string" },
              "resolution": { "type": "string" },
              "docs": { "type": "string", "format": "uri" }
            }
          }
        }
      }
    }
  }
}
