{
  "openapi": "3.1.0",
  "info": {
    "title": "FluxProof",
    "version": "0.1.0",
    "contact": {
      "name": "Bryan",
      "email": "bryan@tryneverclosed.com"
    },
    "description": "Evidence-bearing public page monitoring. Free preflight never charges; paid requests require buyer payment authority.",
    "x-guidance": "FluxProof watches public web pages and records evidence when they change. Check a page free with POST https://fluxproof.steellantern.com/v1/preflight {\"url\":\"https://example.com\"}. Get a free key (one daily monitor for 30 days) with POST https://fluxproof.steellantern.com/v1/keys/free, or buy the $9 pass (five monitors, 15,000 checks, 30 days) with POST https://fluxproof.steellantern.com/v1/passes {\"url\":\"...\",\"idempotency_key\":\"<24-120 random chars>\"} and its 402 Stripe MPP challenge. Send the key as Authorization: Bearer <key> to POST https://fluxproof.steellantern.com/v1/monitors, then read GET /v1/monitors/{id}/changes."
  },
  "servers": [
    {
      "url": "https://fluxproof.steellantern.com"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Sent as Authorization: Bearer <key>: a 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",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "schema",
                    "fetchable",
                    "content_sha256"
                  ],
                  "properties": {
                    "schema": {
                      "type": "string",
                      "const": "fluxproof-preflight.v1"
                    },
                    "fetchable": {
                      "type": "boolean"
                    },
                    "final_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "observed_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "http_status": {
                      "type": "integer"
                    },
                    "content_type": {
                      "type": "string"
                    },
                    "bytes": {
                      "type": "integer"
                    },
                    "title": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "content_sha256": {
                      "type": "string",
                      "pattern": "^[a-f0-9]{64}$"
                    },
                    "content_included": {
                      "type": "boolean",
                      "const": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or unsupported source"
          },
          "424": {
            "description": "Upstream unavailable"
          },
          "429": {
            "description": "Rate limit"
          }
        },
        "security": []
      }
    },
    "/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",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "api_key"
                  ],
                  "properties": {
                    "schema": {
                      "type": "string",
                      "const": "fluxproof-key.v1"
                    },
                    "api_key": {
                      "type": "string",
                      "description": "Shown once."
                    },
                    "key_prefix": {
                      "type": "string"
                    },
                    "plan": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "max_monitors": {
                      "type": "integer"
                    },
                    "max_checks": {
                      "type": "integer"
                    },
                    "checks_used": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string"
                    },
                    "warning": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Free key already claimed"
          },
          "429": {
            "description": "Rate limit"
          }
        },
        "security": []
      }
    },
    "/v1/passes": {
      "post": {
        "operationId": "purchasePass",
        "summary": "Validate source, then request a buyer-authorized Stripe MPP payment",
        "x-payment-info": {
          "price": {
            "mode": "fixed",
            "currency": "USD",
            "amount": "9.00"
          },
          "protocols": [
            {
              "mpp": {
                "method": "stripe",
                "intent": "charge",
                "currency": "usd"
              }
            }
          ],
          "offers": [
            {
              "amount": "900",
              "currency": "usd",
              "intent": "charge",
              "method": "stripe",
              "description": "Thirty days of evidence-bearing change monitoring for five sources, including 15,000 checks, history, significance rules, webhooks, and agent API access."
            }
          ]
        },
        "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",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "api_key"
                  ],
                  "properties": {
                    "schema": {
                      "type": "string",
                      "const": "fluxproof-paid-pass.v1"
                    },
                    "api_key": {
                      "type": "string",
                      "description": "Shown once; five monitors and 15,000 checks for 30 days."
                    },
                    "key_prefix": {
                      "type": "string"
                    },
                    "plan": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "max_monitors": {
                      "type": "integer"
                    },
                    "max_checks": {
                      "type": "integer"
                    },
                    "checks_used": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string"
                    },
                    "request_sha256": {
                      "type": "string"
                    },
                    "content_sha256": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or payment"
          },
          "402": {
            "description": "Payment challenge; no fulfillment"
          },
          "424": {
            "description": "Source unavailable; no charge"
          }
        },
        "security": []
      }
    },
    "/v1/monitors": {
      "get": {
        "operationId": "listMonitors",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Monitors and usage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Monitors and usage for this key."
                }
              }
            }
          },
          "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. Paid passes default to 15."
                  },
                  "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",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "monitor",
                    "first_check"
                  ],
                  "properties": {
                    "schema": {
                      "type": "string",
                      "const": "fluxproof-monitor-created.v1"
                    },
                    "monitor": {
                      "type": "object"
                    },
                    "first_check": {
                      "type": "object",
                      "description": "The baseline observation: http_status, content hash, error when the first check failed."
                    },
                    "webhook_signing_secret": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "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",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The observation and its comparison with the previous one."
                }
              }
            }
          },
          "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",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Recent change receipts with before/after hashes and bounded text excerpts."
                }
              }
            }
          },
          "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",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid key"
          },
          "404": {
            "description": "Monitor not found"
          }
        }
      }
    }
  }
}