{
  "openapi": "3.1.0",
  "info": {
    "title": "Report Watch report submission API",
    "version": "1.0.0",
    "description": "Submit an existing report's CSV for a saved check. A person must first create an eligible monitor and provision its token in an entitled workspace. Public accounts need confirmed live payment; owner or invited-pilot access is separately controlled. This document does not expose account creation, payments, report configuration, exports or owner MCP tools. HTTP 200 acknowledges a saved check, including failed checks; inspect status and late separately. A passing check does not prove business accuracy or client delivery.",
    "contact": {"name": "Mayd-it LLC", "url": "https://mayd-it.com", "email": "bmay@mayd-it.com"}
  },
  "servers": [{"url": "https://reportwatch.mayd-it.com", "description": "Canonical Report Watch origin; never forward a token to a redirect destination."}],
  "externalDocs": {"description": "Agent integration and retry guide", "url": "https://reportwatch.mayd-it.com/guides/agent-integration.md"},
  "tags": [{"name": "Report submission", "description": "A token authorizes only the existing monitor for which it was issued."}],
  "paths": {
    "/api/ingest/{monitorId}": {
      "post": {
        "operationId": "submitReportCsv",
        "tags": ["Report submission"],
        "summary": "Check a CSV for its intended scheduled report period",
        "description": "This operation saves check metadata and can change a period's incident state and queue notifications. It is not read-only. Send raw uncompressed UTF-8 CSV, not JSON, base64, multipart data or a file URL. The report must be active, within the current plan's limit and outside a processing hold. The application accepts at most 1,048,576 actual body bytes, 100 columns, 10,000 data rows, 100 UTF-16 units per header before and after normalization, and 16,384 UTF-16 units per cell. Invalid CSV within the transport limit can produce a saved failed check with HTTP 200. Each workspace permits 1,000 new checks per rolling 24 hours and 5,000 total saved checks; failed checks count. Raw CSV rows are processed in memory, while normalized headers, rules, results, timestamps and a fingerprint are retained. Never follow redirects. Retry only a bounded number of times with the exact saved CSV, monitor ID, deadline and idempotency key; a timeout does not prove the original request was not saved. Monitor access, pause and schedule checks occur before duplicate lookup, so an old retry can become ineligible after configuration changes.",
        "security": [{"ReportToken": []}],
        "parameters": [
          {"name": "monitorId", "in": "path", "required": true, "description": "ID of the existing monitor shown by the workspace. Use the exact ID supplied during human setup; do not invent or enumerate IDs.", "schema": {"type": "string", "minLength": 1}},
          {"name": "Idempotency-Key", "in": "header", "required": true, "description": "Stable ID for one immutable submission to this monitor. Maximum 128 UTF-16 units including surrounding whitespace; the server trims it. Use a nonblank printable ASCII ID without surrounding whitespace. Reuse it only with the same CSV and deadline. A correction or intentional new evaluation needs a new ID; a correction keeps its original period's deadline.", "schema": {"type": "string", "minLength": 1, "maxLength": 128, "pattern": "\\S"}},
          {"name": "X-Report-Deadline", "in": "header", "required": true, "description": "Exactly 13 decimal digits: intended scheduled deadline in Unix milliseconds, without grace added. It must be an occurrence under the monitor's current schedule, at or after monitor creation, and no later than the next upcoming occurrence. Preserve the original report period when retrying or sending a late correction. Do not substitute current time, seconds, an ISO string or the next due value observed on a later retry. Timezone and daylight-saving schedule rules still apply.", "schema": {"type": "string", "pattern": "^[0-9]{13}$", "minLength": 13, "maxLength": 13}}
        ],
        "requestBody": {
          "required": true,
          "description": "Set Content-Type to text/csv; charset=utf-8. Omit Content-Encoding (or use identity). The 1 MiB limit is enforced on actual received bytes, not Content-Length. Do not send cookies, browser identity headers, an Origin header, or an owner's Sites service credential.",
          "content": {"text/csv": {"schema": {"type": "string", "description": "Raw UTF-8 CSV text; byte and structural bounds are described above. No CSV content or token is included in this specification."}}}
        },
        "responses": {
          "200": {"description": "A saved check or an idempotent replay. status=failed is a quality failure, not a passing report. duplicate=true returns the original checkedAt, rules and late classification without evaluating again. Inspect deadline and status, then late. Queueing an email is not proof of delivery.", "headers": {"Cache-Control": {"schema": {"type": "string", "const": "no-store"}}, "X-Content-Type-Options": {"schema": {"type": "string", "const": "nosniff"}}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CheckResult"}}}},
          "400": {"description": "Missing or invalid submission ID/deadline, or an ineligible schedule occurrence. Fix configuration; do not blindly retry.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "401": {"description": "Unknown monitor, missing token or invalid/replaced token. Review credentials; do not enumerate monitors or rotate credentials automatically.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "402": {"description": "Monitoring entitlement is absent or this monitor exceeds its plan's active-report allowance. Human billing/configuration review is required.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "403": {"description": "The report's account has no eligible workspace access. Stop for human review.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "404": {"description": "Wrong route, extra path segment or unsupported method. Do not try alternate private routes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "409": {"description": "Report paused, submission ID reused for different CSV/deadline, or access/configuration changed during the request. Review the error; do not create a fresh ID just to bypass the conflict.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "413": {"description": "Actual request body exceeds 1 MiB. Send a smaller approved report; retrying unchanged cannot help.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "415": {"description": "Unsupported content type, compression or invalid UTF-8. Fix encoding/transport rather than retrying unchanged.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "423": {"description": "Workspace is on a processing hold. Stop; only authorized human review can resolve the hold.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "429": {"description": "Application rolling 24-hour quota or total saved-check cap reached. Read the error and stop automatic retries. Waiting cannot clear the total-storage cap; ask support about export and retention. This application does not currently send Retry-After.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "503": {"description": "Application could not complete the operation. The outcome may be uncertain; bounded retries must preserve the entire original submission envelope. Stop for operator review after the retry limit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ReportToken": {"type": "http", "scheme": "bearer", "bearerFormat": "Opaque monitor token", "description": "Token issued during human setup of this monitor; currently 64 lowercase hexadecimal characters. Send one space after Bearer. This is not a ChatGPT session, owner credential, Stripe key or MCP authorization. Replacing the token invalidates the previous token. Keep it in protected secret storage and out of prompts, source files and logs."}
    },
    "schemas": {
      "Error": {"type": "object", "additionalProperties": false, "required": ["error"], "properties": {"error": {"type": "string", "minLength": 1, "description": "Human-readable application error; text is not a stable machine error code."}}},
      "RuleCheck": {"type": "object", "additionalProperties": false, "required": ["key", "label", "passed", "message"], "properties": {"key": {"type": "string", "enum": ["rules", "input", "size", "content", "format", "headers", "required_columns", "row_shape", "row_count", "date_column", "date_format", "date_future", "date_freshness"]}, "label": {"type": "string"}, "passed": {"type": "boolean"}, "message": {"type": "string", "description": "Validation diagnostic; does not include raw data-cell values."}}},
      "Rules": {
        "type": "object", "additionalProperties": false,
        "required": ["requiredColumns", "minRows", "maxRows"],
        "properties": {
          "requiredColumns": {"type": "array", "maxItems": 100, "uniqueItems": true, "items": {"type": "string", "minLength": 1, "maxLength": 100}, "description": "Saved normalized required names; limits also apply in UTF-16 units."},
          "minRows": {"type": "integer", "minimum": 0, "maximum": 10000},
          "maxRows": {"type": "integer", "minimum": 0, "maximum": 10000, "description": "At least minRows."},
          "dateColumn": {"type": "string", "minLength": 1, "maxLength": 100},
          "maxAgeHours": {"type": "number", "minimum": 1, "maximum": 8760, "description": "Saved paid-monitor freshness limit; not the free browser checker's configurable range."}
        },
        "dependentRequired": {"dateColumn": ["maxAgeHours"], "maxAgeHours": ["dateColumn"]}
      },
      "CheckResult": {
        "type": "object", "additionalProperties": false,
        "required": ["checkId", "duplicate", "status", "rowCount", "columns", "checks", "issues", "checkedAt", "deadline", "late", "rules", "proof"],
        "properties": {
          "checkId": {"type": "string", "minLength": 1, "description": "Saved check ID. A replay returns the same ID."},
          "duplicate": {"type": "boolean"},
          "status": {"type": "string", "enum": ["passed", "failed"]},
          "rowCount": {"type": "integer", "minimum": 0, "maximum": 10000, "description": "Parsed data rows; may be zero if validation exits before rows can be counted."},
          "columns": {"type": "array", "maxItems": 100, "items": {"type": "string", "maxLength": 100}, "description": "Parsed, normalized header metadata. May be empty if parsing exits early; failed headers can contain blank or duplicate names."},
          "checks": {"type": "array", "items": {"$ref": "#/components/schemas/RuleCheck"}},
          "issues": {"type": "array", "items": {"type": "string"}},
          "checkedAt": {"type": "integer", "minimum": 0, "description": "Original server check time in Unix milliseconds. Freshness is evaluated against this time, not the report deadline."},
          "deadline": {"type": "integer", "minimum": 1000000000000, "maximum": 9999999999999, "description": "Intended report deadline from the validated request header, represented as a JSON number."},
          "late": {"type": "boolean", "description": "Original receipt time was strictly later than deadline plus configured grace. A late pass applies only to this deadline."},
          "rules": {"$ref": "#/components/schemas/Rules"},
          "proof": {"type": "string", "const": "Submitted CSV only; independent client delivery is not verified."}
        }
      }
    }
  }
}
