{
  "openapi": "3.1.0",
  "info": {
    "title": "Bippsi Public API",
    "version": "2.0.0",
    "description": "Public endpoints for Bippsi. Standard account connections and AI Action Keys are separate authority profiles. See https://bippsi.com/AGENTS.md for the full agent access policy.",
    "contact": {
      "name": "Bippsi",
      "url": "https://bippsi.com/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://bippsi.com/policies"
    }
  },
  "servers": [
    { "url": "https://bippsi.com/api/v1", "description": "Production" }
  ],
  "paths": {
    "/license-ninja/validate": {
      "post": {
        "summary": "Validate a License Ninja license token",
        "description": "Public POST endpoint used by software products to validate license tokens issued by License Ninja. Returns boolean true if the token is active and within (or in grace for) one of the supplied product codes, otherwise false.",
        "operationId": "validateLicenseNinjaToken",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["licenseToken", "productCodes"],
                "properties": {
                  "licenseToken": { "type": "string", "description": "License token issued via License Ninja." },
                  "productCodes": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "One or more product codes the caller wants to validate against. Token must be active for at least one of them."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result. Body is the literal JSON boolean `true` or `false`.",
            "content": {
              "application/json": {
                "schema": { "type": "boolean" }
              }
            }
          },
          "429": { "description": "Rate limit exceeded (60 requests / minute / IP)." }
        }
      }
    },
    "/verify-subscription": {
      "post": {
        "summary": "Verify an AI Certified subscription status",
        "operationId": "verifySubscription",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["site_key"],
                "properties": {
                  "site_key": { "type": "string" },
                  "site_url": { "type": "string", "format": "uri" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "active": { "type": "boolean" },
                    "expires_at": { "type": "string", "format": "date-time", "nullable": true },
                    "config_hash": { "type": "string", "nullable": true }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/scan/{domain}": {
      "get": {
        "summary": "Get the latest AI Certified scan for a domain",
        "operationId": "getScan",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "name": "domain", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Scan summary" },
          "404": { "description": "No scan found" }
        }
      }
    },
    "/ai-actions": {
      "get": {
        "summary": "Read AI Action Credit availability and Action Key limits",
        "operationId": "getAiActionSummary",
        "security": [{ "aiActionKey": ["actions:read"] }],
        "responses": {
          "200": { "description": "USD-denominated Action Credit summary." },
          "401": { "description": "Missing or invalid AI Action Key." },
          "403": { "description": "Required Action scope or site authority is absent." }
        }
      },
      "post": {
        "summary": "Execute or reconcile an AI Action",
        "description": "Execute, reserve, and release require a buyer AI Action Key. Execute and reserve also require X-Bippsi-Max-Action-USD. Refund is merchant-authorized and does not accept buyer authority. Every operation independently requires either a signed A.I. Certified request (site_token plus X-Bippsi-Action-Signature) or an Action Connector credential.",
        "operationId": "executeAiAction",
        "parameters": [
          {"name": "X-Bippsi-Max-Action-USD", "in": "header", "required": false, "description": "Required for execute and reserve. Exact buyer-authorized ceiling in USD.", "schema": {"type": "string", "pattern": "^[0-9]+(?:\\.[0-9]{1,6})?$"}},
          {"name": "X-Bippsi-Action-Signature", "in": "header", "required": false, "description": "Lowercase SHA-256 HMAC of the exact request body using the A.I. Certified site pull secret.", "schema": {"type": "string", "pattern": "^[0-9a-f]{64}$"}},
          {"name": "X-Bippsi-Action-Connector-Key", "in": "header", "required": false, "description": "Action Connector credential. Mutually exclusive with signed A.I. Certified merchant authority.", "schema": {"type": "string"}}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["operation", "action_ref"],
                "properties": {
                  "operation": {"type": "string", "enum": ["execute", "reserve", "release", "refund"]},
                  "action_ref": {"type": "string", "minLength": 16, "maxLength": 191, "description": "Buyer-key idempotency reference bound to the Action lifecycle."},
                  "site_token": {"type": "string", "description": "Required for signed A.I. Certified merchant authority."},
                  "home_url": {"type": "string", "format": "uri", "description": "Registered source URL. Either home_url or source_url is required."},
                  "source_url": {"type": "string", "format": "uri", "description": "Registered source URL. Either source_url or home_url is required."},
                  "element_id": {"type": "integer", "minimum": 1, "description": "Required for execute/reserve through the A.I. Certified HMAC adapter."},
                  "price_micro_usd": {"type": "integer", "minimum": 1, "description": "Required for execute/reserve through Action Connector; optional equality assertion for A.I. Certified."},
                  "amount_micro_usd": {"type": "integer", "minimum": 1, "description": "Optional partial-refund amount for refund."},
                  "action_reservation_id": {"type": "string", "format": "uuid"},
                  "refund_ref": {"type": "string", "minLength": 16, "maxLength": 191},
                  "reason": {"type": "string", "description": "Optional release reason."}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Canonical Action result and financial lifecycle receipt."},
          "400": {"description": "Malformed JSON, missing conditional authority, or invalid reference."},
          "401": {"description": "Buyer or merchant authority failed."},
          "402": {"description": "Insufficient Action Credit or buyer maximum exceeded."},
          "403": {"description": "Key scope, site scope, domain, or merchant authority denied."},
          "404": {"description": "Unknown operation, site, or payable element."},
          "409": {"description": "Idempotency or price state conflicts."},
          "422": {"description": "Action contract is invalid."},
          "429": {"description": "Rate limit exceeded."}
        }
      }
    },
    "/ai-action-keys": {
      "get": {
        "summary": "List AI Action Keys for the active account context",
        "description": "Requires a stage-two Bippsi browser session and an explicit active account context. Key secrets are never returned.",
        "operationId": "listAiActionKeys",
        "responses": {
          "200": {"description": "Action Key metadata, limits, status, and site bindings."},
          "401": {"description": "Authenticated browser session required."},
          "409": {"description": "An active account context is required."}
        }
      },
      "post": {
        "summary": "Issue, revoke, or compromise an AI Action Key",
        "description": "Requires a stage-two browser session, recent reauthentication, CSRF protection, and explicit account context. The issue operation returns the secret once; only its SHA-256 digest is stored.",
        "operationId": "manageAiActionKeys",
        "parameters": [
          {"name": "Idempotency-Key", "in": "header", "required": false, "schema": {"type": "string"}},
          {"name": "X-CSRF-Token", "in": "header", "required": false, "schema": {"type": "string"}}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["operation"],
                "properties": {
                  "operation": {"type": "string", "enum": ["issue", "revoke", "compromise"]},
                  "idempotency_key": {"type": "string"},
                  "label": {"type": "string"},
                  "per_action_cap_micro_usd": {"type": "integer", "minimum": 1},
                  "daily_cap_micro_usd": {"type": ["integer", "null"], "minimum": 1},
                  "site_ids": {"type": "array", "items": {"type": "integer", "minimum": 1}},
                  "action_key_id": {"type": "string", "format": "uuid"},
                  "reason": {"type": "string"}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Key lifecycle result and immutable receipt."},
          "401": {"description": "Authenticated browser session required."},
          "403": {"description": "Recent reauthentication or CSRF validation required."},
          "409": {"description": "An active account context is required."},
          "422": {"description": "Key policy or operation input is invalid."}
        }
      }
    },
    "/ai-action-keys/training": {
      "get": {
        "summary": "Read the canonical AI Action training contract",
        "description": "Requires an active AI Action Key with actions:read scope. Supports ETag revalidation and never accepts browser-session authority as a substitute.",
        "operationId": "getAiActionTraining",
        "security": [{"aiActionKey": ["actions:read"]}],
        "responses": {
          "200": {"description": "Canonical text/markdown Action protocol guidance."},
          "304": {"description": "Training contract has not changed."},
          "401": {"description": "Missing or invalid AI Action Key."},
          "403": {"description": "actions:read scope is absent."}
        }
      }
    },
    "/ai-actions/config": {
      "get": {
        "summary": "Read signed A.I. Certified AI Action configuration",
        "operationId": "getAiActionConfig",
        "responses": {
          "200": {"description": "Canonical site Action configuration."},
          "304": {"description": "Configuration has not changed."},
          "401": {"description": "Site authority failed."}
        }
      }
    },
    "/mcp": {
      "post": {
        "summary": "Model Context Protocol (MCP) JSON-RPC 2.0 endpoint for AI agents",
        "description": "Agents connect here for public discovery, account tools, and scoped AI Action Credit summaries without scraping HTML. Protocol: MCP 2024-11-05 over HTTP + JSON-RPC 2.0. The canonical tool inventory is at /.well-known/mcp.json.",
        "operationId": "mcpEndpoint",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["jsonrpc", "method"],
                "properties": {
                  "jsonrpc": { "type": "string", "enum": ["2.0"] },
                  "id":      { "oneOf": [{ "type": "integer" }, { "type": "string" }] },
                  "method":  { "type": "string", "description": "initialize | tools/list | tools/call | ping | notifications/initialized" },
                  "params":  { "type": "object" }
                }
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/call",
                "params": {
                  "name": "scan_site",
                  "arguments": { "url": "https://example.com" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "JSON-RPC 2.0 response with `result` or `error`." },
          "204": { "description": "No content (notifications only)." },
          "400": { "description": "JSON-RPC parse error." },
          "429": { "description": "Rate limit exceeded." }
        }
      },
      "get": {
        "summary": "MCP endpoint hint (human-browsable)",
        "operationId": "mcpHint",
        "description": "Returns a JSON summary of the MCP server for humans who browse the endpoint. Machines should POST JSON-RPC 2.0 messages instead.",
        "responses": {
          "200": { "description": "MCP server summary and quick-start hint" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "bippsi_*"
      },
      "aiActionKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "bippsi_action_<64 lowercase hex>"
      }
    }
  }
}
