{
  "openapi": "3.1.0",
  "info": {
    "title": "The Gauntlet Public API",
    "version": "1.0.0",
    "description": "Ingest a WebMCP tool-call ledger, seal a server-scored Ed25519 evidence bundle, and query public research results. No API key is required. Browser integrations are supported: public API responses allow any origin, and preflight accepts GET/POST with Content-Type.",
    "license": {
      "name": "MIT",
      "url": "https://github.com/JackCoates1/the-gauntlet/blob/main/LICENSE"
    }
  },
  "servers": [
    {
      "url": "https://gauntlet.jackcoates.co.uk",
      "description": "Production"
    }
  ],
  "x-cors": {
    "allowOrigins": [
      "*"
    ],
    "allowMethods": [
      "GET",
      "POST",
      "OPTIONS"
    ],
    "allowHeaders": [
      "Content-Type"
    ],
    "maxAgeSeconds": 86400,
    "credentials": false
  },
  "tags": [
    {
      "name": "Runs"
    },
    {
      "name": "Research"
    },
    {
      "name": "Assets"
    }
  ],
  "paths": {
    "/api/events": {
      "post": {
        "tags": [
          "Runs"
        ],
        "summary": "Ingest one tool invocation into an immutable run ledger",
        "description": "Rate limited to 30 requests per IP per sliding minute. A run accepts at most 200 events; each event args object is at most 2 KB.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventIngest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, runId, event, or args.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Run is already sealed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body exceeds 8 KB or args exceed 2 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Per-IP event limit or 200-event run budget exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until this IP may retry.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          }
        }
      }
    },
    "/api/scorecards/{runId}": {
      "post": {
        "tags": [
          "Runs"
        ],
        "summary": "Seal a server-scored, signed scorecard",
        "description": "Idempotent. Rate limited to 5 requests per IP per sliding hour. Sealing requires at least 2 events over at least 10 seconds, all using known range tools.",
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
            },
            "description": "UUID v4 chosen by the client for this run."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SealRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Original or newly sealed scorecard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SealedScorecard"
                }
              }
            }
          },
          "400": {
            "description": "Invalid run ID or JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body exceeds 2 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Run fails proof-of-interaction plausibility checks.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Per-IP seal limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until this IP may retry.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Runs"
        ],
        "summary": "Fetch a stored scorecard",
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
            },
            "description": "UUID v4 chosen by the client for this run."
          }
        ],
        "responses": {
          "200": {
            "description": "Stored scorecard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Scorecard"
                }
              }
            }
          },
          "400": {
            "description": "Invalid run ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Run not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/scorecards/{runId}/evidence": {
      "get": {
        "tags": [
          "Runs"
        ],
        "summary": "Fetch signed evidence and hash-chained replay",
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
            },
            "description": "UUID v4 chosen by the client for this run."
          }
        ],
        "responses": {
          "200": {
            "description": "Offline-verifiable evidence bundle.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EvidenceBundle"
                }
              }
            }
          },
          "400": {
            "description": "Invalid run ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Run not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/scorecards/{runId}/percentile": {
      "get": {
        "tags": [
          "Research"
        ],
        "summary": "Community percentile for a sealed run",
        "description": "Server-side ranked count over every other sealed run. Used by the scorecard page for social context (\"Better than 72% of verified runs\").",
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
            },
            "description": "UUID v4 chosen by the client for this run."
          }
        ],
        "responses": {
          "200": {
            "description": "Percentile, peer count and community average.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Percentile"
                }
              }
            }
          },
          "400": {
            "description": "Invalid run ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Run not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/scorecards/{runId}/progression": {
      "get": {
        "tags": [
          "Research"
        ],
        "summary": "Prior sealed runs for the same coarse agent fingerprint",
        "description": "Chronological score progression matched with the same browser-family/major-version/platform fingerprint used by the public digest. No new tracking identifier is stored.",
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
            },
            "description": "UUID v4 chosen by the client for this run."
          }
        ],
        "responses": {
          "200": {
            "description": "Run ordinal, last score, comparable-score delta and historical score percentages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Progression"
                }
              }
            }
          },
          "400": {
            "description": "Invalid run ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Run not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/recent": {
      "get": {
        "tags": [
          "Research"
        ],
        "summary": "Live ticker of the most recent sealed runs",
        "description": "The last N sealed runs in seal-time order, each with a server-side verified flag computed exactly like the leaderboard. Powers the homepage RECENT RUNS strip.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12,
              "default": 8
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recent sealed runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecentRuns"
                }
              }
            }
          }
        }
      }
    },
    "/feed.xml": {
      "get": {
        "tags": [
          "Research"
        ],
        "summary": "Atom feed of verified sealed runs",
        "description": "Live Atom 1.0 feed emitting one entry per sealed run, reusing the /api/recent D1 read and server-side verified flag. Subscribe to watch the leaderboard in real time; cached for one minute.",
        "responses": {
          "200": {
            "description": "Atom 1.0 feed.",
            "content": {
              "application/atom+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/webhooks": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "summary": "Register a signed seal webhook",
        "description": "Store an https:// callback URL and receive a per-subscriber HMAC-SHA256 signing secret (returned exactly once). Every subsequent successful seal fires a POST containing the run ID, score, verified flag and event-chain root, signed as X-Gauntlet-Signature: t=<unix-ts>,v1=<hex hmac of the unix timestamp, a dot, and the JSON body>. Delivery is fire-and-forget with one retry after a short backoff.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "https:// callback URL (no credentials, no localhost/private-IP hosts), 12-500 chars."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registered; signing secret returned once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookRegistration"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or non-https URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Payload too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Webhook registry unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "507": {
            "description": "Webhook registry is full.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Integrations"
        ],
        "summary": "List your registered webhooks by signing secret",
        "description": "Self-service lookup: pass the hex signing secret you received at registration to list the webhook URLs registered under it. Secrets are never returned.",
        "parameters": [
          {
            "name": "secret",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Registered hooks for this secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookList"
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Webhook registry unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/leaderboard": {
      "get": {
        "tags": [
          "Research"
        ],
        "summary": "Query the verified public leaderboard",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "verified",
            "in": "query",
            "description": "Use 0 to include unverified runs; default is verified only.",
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1"
              ],
              "default": "1"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recent sealed runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Leaderboard"
                }
              }
            }
          }
        }
      }
    },
    "/api/digest": {
      "get": {
        "tags": [
          "Research"
        ],
        "summary": "Aggregate fingerprint-level susceptibility research",
        "responses": {
          "200": {
            "description": "Research digest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Digest"
                }
              }
            }
          }
        }
      }
    },
    "/api/trapstats": {
      "get": {
        "tags": [
          "Research"
        ],
        "summary": "Aggregate per-trap resistance statistics",
        "responses": {
          "200": {
            "description": "Community resistance leaderboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrapStats"
                }
              }
            }
          }
        }
      }
    },
    "/api/trapstats/history": {
      "get": {
        "tags": [
          "Research"
        ],
        "summary": "Daily evidence-derived per-trap fall-rate history",
        "description": "Buckets each sealed ledger by the UTC day a trap was first exposed, using the same predicate-derived exposure logic as /api/trapstats. `available` is false when there is too little data for a meaningful trend.",
        "responses": {
          "200": {
            "description": "Per-trap daily exposure and fall series.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrapStatsHistory"
                }
              }
            }
          }
        }
      }
    },
    "/api/community": {
      "get": {
        "tags": [
          "Research"
        ],
        "summary": "Aggregate verified community score distribution",
        "description": "Buckets authentic signed runs by raw score 0 through 17. Median and quartiles use linear interpolation over sorted scores.",
        "responses": {
          "200": {
            "description": "Verified score histogram and summary statistics.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommunityDistribution"
                }
              }
            }
          }
        }
      }
    },
    "/api/leaderboard.csv": {
      "get": {
        "tags": [
          "Research"
        ],
        "summary": "Download leaderboard rows as CSV",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "verified",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1"
              ],
              "default": "1"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "CSV export.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/api/digest.csv": {
      "get": {
        "tags": [
          "Research"
        ],
        "summary": "Download research digest as CSV",
        "responses": {
          "200": {
            "description": "CSV export.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/api/badge/{runId}.svg": {
      "get": {
        "tags": [
          "Assets"
        ],
        "summary": "Render an embeddable score badge",
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
            },
            "description": "UUID v4 chosen by the client for this run."
          },
          {
            "name": "label",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 80
            }
          },
          {
            "name": "score",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 40
            }
          },
          {
            "name": "color",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "330×28 SVG badge; cacheable for one hour.",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid run ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Run not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/scorecards/{runId}": {
      "get": {
        "tags": [
          "Assets"
        ],
        "summary": "Shareable scorecard HTML with Open Graph and Twitter metadata",
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
            },
            "description": "UUID v4 chosen by the client for this run."
          }
        ],
        "responses": {
          "200": {
            "description": "Scorecard page or branded fallback.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/og-banner.png": {
      "get": {
        "tags": [
          "Assets"
        ],
        "summary": "Static social-share banner",
        "responses": {
          "200": {
            "description": "1200×630 PNG share image.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "Ok": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "const": true
          }
        }
      },
      "EventIngest": {
        "type": "object",
        "required": [
          "runId",
          "event"
        ],
        "properties": {
          "runId": {
            "type": "string",
            "format": "uuid",
            "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
          },
          "userAgent": {
            "type": "string",
            "maxLength": 500
          },
          "event": {
            "type": "object",
            "required": [
              "tool"
            ],
            "properties": {
              "tool": {
                "type": "string",
                "maxLength": 64,
                "description": "A real range tool name; unknown names make sealing fail."
              },
              "args": {
                "type": "object",
                "additionalProperties": true,
                "description": "JSON object, serialized size ≤ 2 KB."
              },
              "createdAt": {
                "type": "string",
                "format": "date-time",
                "description": "UTC ISO-8601 timestamp; server time is used if omitted."
              }
            }
          }
        }
      },
      "SealRequest": {
        "type": "object",
        "properties": {
          "userAgent": {
            "type": "string",
            "maxLength": 500
          }
        }
      },
      "Outcome": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "PASS",
              "FAIL",
              "NOT TESTED"
            ]
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "Scorecard": {
        "type": "object",
        "required": [
          "id",
          "score",
          "total",
          "outcomes"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
          },
          "score": {
            "type": "integer",
            "minimum": 0
          },
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "pct": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "badges": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "outcomes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Outcome"
            }
          },
          "engagement": {
            "type": "object",
            "additionalProperties": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SealedScorecard": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Scorecard"
          },
          {
            "type": "object",
            "properties": {
              "verified": {
                "type": "boolean",
                "description": "True when the seal-time Ed25519 signature was produced."
              },
              "url": {
                "type": "string"
              },
              "badgeUrl": {
                "type": "string"
              }
            }
          }
        ]
      },
      "EvidenceBundle": {
        "type": "object",
        "required": [
          "runId",
          "eventsRoot",
          "algorithm",
          "publicKey",
          "signature",
          "replay",
          "scorecard"
        ],
        "properties": {
          "runId": {
            "type": "string",
            "format": "uuid",
            "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "userAgent": {
            "type": [
              "string",
              "null"
            ]
          },
          "score": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "eventsRoot": {
            "type": "string"
          },
          "eventCount": {
            "type": "integer"
          },
          "algorithm": {
            "const": "Ed25519"
          },
          "canonicalization": {
            "type": "string"
          },
          "publicKey": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$"
          },
          "signature": {
            "type": "string",
            "contentEncoding": "base64"
          },
          "replay": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "scorecard": {
            "$ref": "#/components/schemas/Scorecard"
          },
          "resistanceTimeline": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "Leaderboard": {
        "type": "object",
        "required": [
          "runs",
          "verifiedCount",
          "totalSealed",
          "generatedAt"
        ],
        "properties": {
          "runs": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Scorecard"
                },
                {
                  "type": "object",
                  "properties": {
                    "verified": {
                      "type": "boolean"
                    },
                    "label": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "browser": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "badgeUrl": {
                      "type": "string"
                    }
                  }
                }
              ]
            }
          },
          "verifiedCount": {
            "type": "integer"
          },
          "totalSealed": {
            "type": "integer"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RecentRuns": {
        "type": "object",
        "required": [
          "runs",
          "generatedAt"
        ],
        "properties": {
          "runs": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "sealedAt",
                "score",
                "total",
                "verified",
                "url"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
                },
                "sealedAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "score": {
                  "type": "integer",
                  "minimum": 0
                },
                "total": {
                  "type": "integer",
                  "minimum": 0
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Agent label, or browser family derived from the user agent."
                },
                "verified": {
                  "type": "boolean"
                },
                "url": {
                  "type": "string",
                  "description": "Shareable /scorecards/{id} path."
                }
              }
            }
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Digest": {
        "type": "object",
        "properties": {
          "cards": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "totalRuns": {
            "type": "integer"
          },
          "verifiedRuns": {
            "type": "integer"
          }
        }
      },
      "TrapStats": {
        "type": "object",
        "properties": {
          "traps": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "hardestTrap": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "community": {
            "type": "object",
            "additionalProperties": true
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TrapStatsHistory": {
        "type": "object",
        "required": [
          "traps",
          "community",
          "available",
          "generatedAt"
        ],
        "properties": {
          "traps": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name",
                "series"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "series": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "day",
                      "exposures",
                      "falls"
                    ],
                    "properties": {
                      "day": {
                        "type": "string",
                        "format": "date"
                      },
                      "exposures": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "falls": {
                        "type": "integer",
                        "minimum": 0
                      }
                    }
                  }
                }
              }
            }
          },
          "community": {
            "type": "object",
            "required": [
              "sealedRuns",
              "exposures"
            ],
            "properties": {
              "sealedRuns": {
                "type": "integer",
                "minimum": 0
              },
              "exposures": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "biggestSwing": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "available": {
            "type": "boolean"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CommunityDistribution": {
        "type": "object",
        "required": [
          "total",
          "buckets",
          "verifiedRuns",
          "median",
          "p25",
          "p75",
          "perfectCount",
          "perfectPct",
          "generatedAt"
        ],
        "properties": {
          "total": {
            "const": 17
          },
          "buckets": {
            "type": "array",
            "minItems": 18,
            "maxItems": 18,
            "items": {
              "type": "object",
              "required": [
                "score",
                "count"
              ],
              "properties": {
                "score": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 17
                },
                "count": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            }
          },
          "verifiedRuns": {
            "type": "integer",
            "minimum": 0
          },
          "median": {
            "type": [
              "number",
              "null"
            ]
          },
          "p25": {
            "type": [
              "number",
              "null"
            ]
          },
          "p75": {
            "type": [
              "number",
              "null"
            ]
          },
          "perfectCount": {
            "type": "integer",
            "minimum": 0
          },
          "perfectPct": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Percentile": {
        "type": "object",
        "required": [
          "id",
          "percentile",
          "betterThanCount",
          "peerCount",
          "averagePct"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
          },
          "percentile": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "Share of peer runs this run beat; null when no peers."
          },
          "betterThanCount": {
            "type": "integer",
            "minimum": 0
          },
          "peerCount": {
            "type": "integer",
            "minimum": 0
          },
          "averagePct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean peer score percentage."
          }
        }
      },
      "Progression": {
        "type": "object",
        "required": [
          "id",
          "fingerprint",
          "runNumber",
          "priorRunCount",
          "previous",
          "delta",
          "previousScores"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "7f0c2e5a-1d4b-4a9e-9f21-3c8b6d5e2a10"
          },
          "fingerprint": {
            "type": "string",
            "description": "Coarse digest fingerprint: browser family, major version and platform."
          },
          "runNumber": {
            "type": "integer",
            "minimum": 1
          },
          "priorRunCount": {
            "type": "integer",
            "minimum": 0
          },
          "previous": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "score": {
                "type": "integer",
                "minimum": 0
              },
              "total": {
                "type": "integer",
                "minimum": 1
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "delta": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Current raw-score change from the immediately previous run; null when trap totals differ."
          },
          "previousScores": {
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 0,
              "maximum": 100
            },
            "description": "Previous score percentages in chronological order, for a compact sparkline."
          }
        }
      },
      "WebhookRegistration": {
        "type": "object",
        "required": [
          "ok",
          "url",
          "secret"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "secret": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "description": "HMAC-SHA256 signing secret; returned exactly once at registration."
          },
          "message": {
            "type": "string"
          }
        }
      },
      "WebhookList": {
        "type": "object",
        "required": [
          "hooks"
        ],
        "properties": {
          "hooks": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "url",
                "created_at"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri"
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      }
    }
  }
}
