{
  "openapi": "3.1.0",
  "info": {
    "title": "FluxProof",
    "version": "0.1.0",
    "description": "Evidence-bearing public page monitoring. Free preflight never charges; paid requests require buyer payment authority."
  },
  "servers": [
    {
      "url": "https://fluxproof.neoaethel.workers.dev"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Buyer-owned fp_free_ or fp_live_ API key."
      }
    },
    "parameters": {
      "monitorId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      }
    }
  },
  "paths": {
    "/v1/preflight": {
      "post": {
        "operationId": "preflight",
        "summary": "Check source fetchability without payment",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public HTTP or HTTPS URL; private addresses and authenticated requests are rejected."
                  }
                },
                "required": [
                  "url"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Source metadata and hash; content not included"
          },
          "400": {
            "description": "Invalid or unsupported source"
          },
          "424": {
            "description": "Upstream unavailable"
          },
          "429": {
            "description": "Rate limit"
          }
        }
      }
    },
    "/v1/keys/free": {
      "post": {
        "operationId": "createFreeKey",
        "summary": "Create a bounded free key; copy the one-time response",
        "responses": {
          "201": {
            "description": "API key, expiration and limits"
          },
          "400": {
            "description": "Free key already claimed"
          },
          "429": {
            "description": "Rate limit"
          }
        }
      }
    },
    "/v1/passes": {
      "post": {
        "operationId": "purchasePass",
        "summary": "Validate source, then request a buyer-authorized Stripe MPP payment",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "idempotency_key"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public HTTP or HTTPS URL; private addresses and authenticated requests are rejected."
                  },
                  "idempotency_key": {
                    "type": "string",
                    "minLength": 24,
                    "maxLength": 120,
                    "pattern": "^[A-Za-z0-9_-]{24,120}$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Paid pass and API key"
          },
          "400": {
            "description": "Invalid request or payment"
          },
          "402": {
            "description": "Payment challenge; no fulfillment"
          },
          "424": {
            "description": "Source unavailable; no charge"
          }
        }
      }
    },
    "/v1/monitors": {
      "get": {
        "operationId": "listMonitors",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Monitors and usage"
          },
          "401": {
            "description": "Invalid or expired key"
          }
        }
      },
      "post": {
        "operationId": "createMonitor",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public HTTP or HTTPS URL; private addresses and authenticated requests are rejected."
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "watch_terms": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 20
                  },
                  "interval_minutes": {
                    "type": "integer",
                    "minimum": 15,
                    "description": "Free plans always use 1440 minutes."
                  },
                  "webhook_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "HTTPS only; requires paid pass."
                  }
                },
                "required": [
                  "url"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Monitor and first observation; webhook secret when configured"
          },
          "400": {
            "description": "Rejected input or capacity limit"
          },
          "401": {
            "description": "Invalid or expired key"
          }
        }
      }
    },
    "/v1/monitors/{id}/check": {
      "parameters": [
        {
          "$ref": "#/components/parameters/monitorId"
        }
      ],
      "post": {
        "operationId": "checkMonitor",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful source observation and change comparison"
          },
          "400": {
            "description": "Monitor busy or exhausted quota"
          },
          "401": {
            "description": "Invalid key"
          },
          "404": {
            "description": "Monitor not found"
          }
        }
      }
    },
    "/v1/monitors/{id}/changes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/monitorId"
        }
      ],
      "get": {
        "operationId": "listChanges",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recent changes; free plan limited to latest receipt"
          },
          "401": {
            "description": "Invalid key"
          },
          "404": {
            "description": "Monitor not found"
          }
        }
      }
    },
    "/v1/monitors/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/monitorId"
        }
      ],
      "delete": {
        "operationId": "deleteMonitor",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Deletion result"
          },
          "401": {
            "description": "Invalid key"
          },
          "404": {
            "description": "Monitor not found"
          }
        }
      }
    }
  }
}