{
  "openapi": "3.1.0",
  "info": {
    "title": "ThemeSniffer API",
    "version": "1.0.0",
    "summary": "Analyze any public website: WordPress detection, theme and plugins, hosting, CDN, fonts, and colors.",
    "description": "Four read-only GET endpoints that analyze a public website from the outside. Every endpoint takes a single `url` query parameter and returns JSON.\n\nNo API key is required and no account exists. Requests are rate limited per IP and CORS is open, so the API can be called from a browser, a script, or an agent.\n\nDetection reads the public front end only. Plugins that emit no CSS or JS stay invisible, and a site behind a strict bot filter may return an error. Private, local, and reserved addresses are refused.\n\nAn MCP server wrapping the same four analyses is documented at https://themesniffer.com/developers.",
    "termsOfService": "https://themesniffer.com/terms",
    "contact": {
      "name": "ThemeSniffer",
      "url": "https://themesniffer.com/contact"
    },
    "license": {
      "name": "Free to use, attribution appreciated",
      "url": "https://themesniffer.com/terms"
    }
  },
  "servers": [
    {
      "url": "https://themesniffer.com/api",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Developer portal",
    "url": "https://themesniffer.com/developers"
  },
  "tags": [
    { "name": "detection", "description": "WordPress detection and tech stack" },
    { "name": "design", "description": "Fonts and colors" }
  ],
  "paths": {
    "/check-wordpress": {
      "get": {
        "operationId": "checkWordPress",
        "summary": "Check whether a website runs WordPress",
        "description": "Returns a yes/no verdict with a confidence level, which of the eight detection signals matched, and the active theme when one is identifiable. The lightest of the four endpoints.",
        "tags": ["detection"],
        "parameters": [{ "$ref": "#/components/parameters/TargetUrl" }],
        "responses": {
          "200": {
            "description": "Analysis complete.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WordPressCheck" },
                "example": {
                  "success": true,
                  "isWordPress": true,
                  "confidence": "high",
                  "signals": {
                    "metaGenerator": "WordPress 6.7.1",
                    "wpContent": true,
                    "wpIncludes": true,
                    "wpBlocks": true,
                    "restApi": true,
                    "xPoweredBy": false,
                    "linkHeader": true,
                    "xPingback": false
                  },
                  "signalCount": 6,
                  "totalSignals": 8,
                  "poweredBy": "PHP/8.2.15",
                  "theme": {
                    "name": "Twenty Twenty-Four",
                    "slug": "twentytwentyfour",
                    "nameSource": "style.css",
                    "inRepo": true,
                    "wpOrgUrl": "https://wordpress.org/themes/twentytwentyfour/",
                    "version": "1.3",
                    "author": "the WordPress team"
                  },
                  "url": "https://example.com/",
                  "checkedAt": "2026-09-08T10:15:00.000Z"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/Unreachable" }
        }
      }
    },
    "/tech-stack": {
      "get": {
        "operationId": "getTechStack",
        "summary": "Full tech stack report for a website",
        "description": "The broadest endpoint: WordPress detection, theme, plugins, hosting provider, CDN, web server, security headers, page composition, and a lab speed snapshot measured from our edge to the target.",
        "tags": ["detection"],
        "parameters": [{ "$ref": "#/components/parameters/TargetUrl" }],
        "responses": {
          "200": {
            "description": "Analysis complete.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TechStack" },
                "example": {
                  "success": true,
                  "url": "https://example.com/",
                  "checkedAt": "2026-09-08T10:15:00.000Z",
                  "isWordPress": true,
                  "confidence": "high",
                  "theme": { "name": "Astra", "slug": "astra", "nameSource": "style.css", "version": "4.8.2", "versionSource": "style.css", "latestVersion": "4.9.0", "outdated": true, "inRepo": true, "activeInstalls": 1000000 },
                  "plugins": [{ "slug": "woocommerce", "name": "WooCommerce", "known": true }],
                  "hosting": { "name": "Kinsta", "type": "Managed WordPress", "via": "server" },
                  "cdn": { "name": "Cloudflare", "type": "CDN / WAF", "via": "server" },
                  "server": "nginx",
                  "poweredBy": "PHP/8.2.15",
                  "performance": {
                    "score": 82,
                    "grade": "B",
                    "serverResponseMs": 340,
                    "ttfbRating": "fast",
                    "htmlKb": 96,
                    "compression": null,
                    "compressed": null,
                    "compressionSource": null,
                    "compressionNote": "Undetermined, not \"off\": our edge decompresses the response before we can see it, so the site\u2019s Content-Encoding is not observable from here. Compression is left out of the score rather than counted as a failure.",
                    "https": true,
                    "requests": 41
                  },
                  "security": { "https": true, "hsts": true, "xContentType": true, "contentSecurityPolicy": false, "xFrameOptions": true }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/Unreachable" }
        }
      }
    },
    "/font-detector": {
      "get": {
        "operationId": "detectFonts",
        "summary": "Every font a website uses",
        "description": "Fetches the page and its stylesheets and reports font families with weights and styles, the provider each comes from (Google Fonts, Bunny, Fontshare, Adobe kits, self-hosted @font-face), system font stacks, icon fonts, WordPress theme.json font presets, and the CSS type scale. Works on non-WordPress sites.",
        "tags": ["design"],
        "parameters": [{ "$ref": "#/components/parameters/TargetUrl" }],
        "responses": {
          "200": {
            "description": "Analysis complete.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FontReport" },
                "example": {
                  "success": true,
                  "url": "https://example.com/",
                  "checkedAt": "2026-09-08T10:15:00.000Z",
                  "isWordPress": false,
                  "fonts": [
                    { "family": "Inter", "source": "google", "weights": ["400", "600", "700"], "styles": ["normal"], "variable": false, "isPrimary": true }
                  ],
                  "systemStacks": ["-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"],
                  "sources": {
                    "google": { "detected": true, "families": ["Inter"], "cssUrls": ["https://fonts.googleapis.com/css2?family=Inter"] },
                    "adobe": { "detected": false, "kitId": null },
                    "selfHosted": { "detected": false, "fileCount": 0 }
                  },
                  "typeScale": [{ "value": "0.875rem", "count": 12 }, { "value": "1rem", "count": 40 }],
                  "stylesheets": { "found": 9, "fetched": 6, "failed": 0, "inlineBlocks": 3, "cssBytes": 412000 }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/Unreachable" }
        }
      }
    },
    "/color-palette": {
      "get": {
        "operationId": "extractColorPalette",
        "summary": "A website's color palette, straight from its CSS",
        "description": "Parses the page's stylesheets and returns every color with hex, rgb and hsl, which color plays which role (background, text, primary, accent, border), the WordPress theme.json palette, gradients, and WCAG contrast pairs. Works on non-WordPress sites.",
        "tags": ["design"],
        "parameters": [{ "$ref": "#/components/parameters/TargetUrl" }],
        "responses": {
          "200": {
            "description": "Analysis complete.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ColorReport" },
                "example": {
                  "success": true,
                  "url": "https://example.com/",
                  "checkedAt": "2026-09-08T10:15:00.000Z",
                  "palette": [
                    { "hex": "#0b0f14", "rgb": "rgb(11, 15, 20)", "hsl": "hsl(213, 29%, 6%)", "topRole": "background", "roles": ["background"], "count": 84 }
                  ],
                  "roles": {
                    "background": { "hex": "#ffffff", "name": "white" },
                    "text": { "hex": "#111827", "name": null },
                    "primary": { "hex": "#059669", "name": null },
                    "accent": null,
                    "border": { "hex": "#e5e7eb", "name": null }
                  },
                  "wpPalette": [{ "slug": "primary", "name": "Primary", "hex": "#059669" }],
                  "contrast": [{ "label": "Body text on background", "ratio": 16.1, "rating": "AAA" }],
                  "stats": { "declarations": 1840, "uniqueColors": 96, "grouped": 24, "customProperties": 61 }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/Unreachable" }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcpJsonRpc",
        "summary": "MCP server (Streamable HTTP)",
        "description": "JSON-RPC 2.0 endpoint implementing the Model Context Protocol. Exposes the four analyses above as MCP tools, each with an MCP Apps `ui://` view. Supports `initialize`, `tools/list`, `tools/call`, `resources/list` and `resources/read`. No SSE stream is opened, so `GET` returns 405.",
        "tags": ["detection"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/JsonRpcRequest" },
              "example": { "jsonrpc": "2.0", "id": 1, "method": "tools/list" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JsonRpcResponse" }
              }
            }
          },
          "202": { "description": "Notification accepted; no response body." },
          "400": { "description": "Body was not valid JSON." },
          "429": { "description": "Rate limit exceeded." }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "TargetUrl": {
        "name": "url",
        "in": "query",
        "required": true,
        "description": "The website to analyze. A bare domain works: `example.com` and `https://example.com/` behave the same. Private, local, and reserved addresses are refused, and redirects are re-validated at every hop.",
        "schema": { "type": "string", "maxLength": 2000, "examples": ["wordpress.org", "https://stripe.com/"] }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The `url` parameter was missing, malformed, too long, or pointed at a blocked address.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "The request was rejected by the gate: an empty or implausibly short `User-Agent`, or an `Origin` header from a site other than themesniffer.com. Server-to-server callers send no `Origin` and are unaffected.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Too many requests from this IP in the current minute.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unreachable": {
        "description": "The target site could not be fetched at all: the connection failed or timed out. A site that answers and refuses the request is not this - it comes back as a 200 whose `blocked` field is true.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["success", "error"],
        "properties": {
          "success": { "type": "boolean", "const": false },
          "error": { "type": "string", "description": "A message written for a human to read." }
        }
      },
      "Theme": {
        "type": "object",
        "description": "The active theme. The display name is read from the theme's own style.css header where that file is reachable, then from the WordPress.org directory, and only as a last resort inferred from the folder name - `nameSource` says which. Enriched from the directory when the slug is published there.",
        "properties": {
          "name": { "type": "string" },
          "slug": { "type": "string" },
          "nameSource": { "type": "string", "enum": ["style.css", "wordpress.org", "slug"], "description": "Where `name` came from. `slug` means it was inferred from the folder name and is a guess." },
          "version": { "type": "string", "description": "The version this site has installed, read from its style.css. Falls back to the directory's current release when style.css is unreachable - `versionSource` says which." },
          "versionSource": { "type": "string", "enum": ["style.css", "wordpress.org"], "description": "Whether `version` is the installed version or the directory's current release." },
          "latestVersion": { "type": "string", "description": "The current release in the WordPress.org directory. Present only for repository themes." },
          "outdated": { "type": "boolean", "description": "True when the installed version is behind `latestVersion`. Present only when both are known and the installed one was read from style.css - a version taken from the directory cannot be compared against itself." },
          "wpOrgUrl": { "type": "string", "format": "uri", "description": "Present only when the theme is in the WordPress.org directory (`inRepo` true). Custom and premium themes have no page there." },
          "inRepo": { "type": "boolean" },
          "themeUri": { "type": "string", "format": "uri", "description": "Theme URI from style.css, when stated." },
          "parentTheme": { "type": "string", "description": "The Template header from style.css: the parent of a child theme." },
          "version": { "type": "string" },
          "author": { "type": "string" },
          "screenshotUrl": { "type": "string", "format": "uri" },
          "activeInstalls": { "type": "integer", "description": "Sites running this theme, as reported by WordPress.org. The directory buckets this figure, so read it as a floor: 500000 means \"500,000+\". Not the download count - see `downloads`." },
          "downloads": { "type": "integer", "description": "Lifetime downloads from the directory. Typically an order of magnitude larger than `activeInstalls` and not a measure of current usage." }
        }
      },
      "WordPressCheck": {
        "type": "object",
        "required": ["success", "isWordPress", "confidence", "signals", "url", "checkedAt"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "isWordPress": { "type": ["boolean", "null"], "description": "`null` when the page could not be read at all (a block, a challenge, a dead origin). Never report `null` as \"not WordPress\" - `note` says what stopped the check." },
          "confidence": { "type": "string", "enum": ["none", "low", "medium", "high", "unknown"] },
          "blocked": { "type": "boolean", "description": "True when the site refused the request or served a bot challenge instead of the page." },
          "blockedBy": { "type": ["string", "null"], "description": "The CDN or WAF that produced the refusal, when its response headers identify it (for example \"Cloudflare\")." },
          "status": { "type": "integer", "description": "The HTTP status the target returned, when the page could not be read." },
          "reason": { "type": "string", "enum": ["bot-protection", "challenge", "rate-limited", "unauthorized", "legal", "refused", "not-found", "origin-down", "server-error", "not-html"], "description": "Why the page could not be read. Absent on a completed analysis." },
          "note": { "type": ["string", "null"], "description": "A sentence for a human: why the check could not be completed." },
          "poweredBy": { "type": ["string", "null"], "description": "The raw X-Powered-By response header, whatever it names - usually a PHP version. Unlike the `xPoweredBy` signal it is not filtered for WordPress, so an absent header and a header naming something else are distinguishable." },
          "signals": {
            "type": "object",
            "description": "The eight independent signals. Each is `false` when absent; `metaGenerator` carries the generator string when present.",
            "properties": {
              "metaGenerator": { "oneOf": [{ "type": "string" }, { "type": "boolean" }] },
              "wpContent": { "type": "boolean" },
              "wpIncludes": { "type": "boolean" },
              "wpBlocks": { "type": "boolean" },
              "restApi": { "type": "boolean" },
              "xPoweredBy": { "oneOf": [{ "type": "string" }, { "type": "boolean" }], "description": "The X-Powered-By header when it names WordPress, `false` otherwise. A site sending `x-powered-by: PHP/8.1` reads `false` here - the header exists, but a PHP version is not WordPress evidence. `/api/tech-stack` reports the raw header as `poweredBy`." },
              "linkHeader": { "type": "boolean", "description": "The REST API endpoint is advertised, either in the Link header or as the `<link rel=\"https://api.w.org/\">` tag. A page cache serves the tag without setting the header." },
              "xPingback": { "type": "boolean", "description": "The pingback endpoint is advertised, either in the X-Pingback header or as the `<link rel=\"pingback\">` tag." }
            }
          },
          "signalCount": { "type": "integer" },
          "totalSignals": { "type": "integer" },
          "theme": { "oneOf": [{ "$ref": "#/components/schemas/Theme" }, { "type": "null" }] },
          "url": { "type": "string", "format": "uri" },
          "checkedAt": { "type": "string", "format": "date-time" }
        }
      },
      "Signature": {
        "description": "A provider matched from a response header, or null when nothing matched.",
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "name": { "type": "string" },
              "type": { "type": ["string", "null"] },
              "via": { "type": "string", "description": "The response header the match came from." }
            }
          },
          { "type": "null" }
        ]
      },
      "TechStack": {
        "type": "object",
        "required": ["success", "url", "checkedAt", "isWordPress"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "url": { "type": "string", "format": "uri", "description": "The final URL after redirects." },
          "checkedAt": { "type": "string", "format": "date-time" },
          "isWordPress": { "type": ["boolean", "null"], "description": "`null` when the page could not be read at all - see `blocked`, `reason` and `note`." },
          "confidence": { "type": "string", "enum": ["none", "low", "medium", "high"] },
          "blocked": { "type": "boolean", "description": "True when the site refused the request or served a bot challenge instead of the page." },
          "blockedBy": { "type": ["string", "null"], "description": "The CDN or WAF that produced the refusal, when identifiable from its headers." },
          "status": { "type": "integer" },
          "reason": { "type": "string", "description": "Why the page could not be read. Absent on a completed analysis." },
          "note": { "type": ["string", "null"] },
          "wpSignals": { "type": "object" },
          "generator": { "type": ["string", "null"] },
          "theme": { "oneOf": [{ "$ref": "#/components/schemas/Theme" }, { "type": "null" }] },
          "plugins": {
            "type": "array",
            "description": "Plugins visible from the front end. Known slugs are named from a bundled directory; unknown slugs are title-cased.",
            "items": {
              "type": "object",
              "properties": {
                "slug": { "type": "string" },
                "name": { "type": "string" },
                "known": { "type": "boolean" },
                "version": { "type": ["string", "null"] }
              }
            }
          },
          "hosting": { "$ref": "#/components/schemas/Signature" },
          "cdn": { "$ref": "#/components/schemas/Signature" },
          "server": { "type": ["string", "null"], "description": "Raw `Server` response header." },
          "poweredBy": { "type": ["string", "null"], "description": "Raw `X-Powered-By` response header, whatever it names - usually a PHP version. Unlike the `xPoweredBy` detection signal, this is not filtered for WordPress." },
          "performance": {
            "type": "object",
            "description": "A lab snapshot measured from our edge to the target, not the visitor's own Core Web Vitals.",
            "properties": {
              "score": { "type": "integer", "minimum": 0, "maximum": 100 },
              "grade": { "type": "string" },
              "serverResponseMs": { "type": "integer" },
              "ttfbRating": { "type": "string", "enum": ["fast", "moderate", "slow"] },
              "downloadMs": { "type": "integer" },
              "htmlKb": { "type": "number", "description": "Size of the uncompressed HTML document, in KB. Not the compressed bytes a visitor transfers." },
              "compression": { "type": ["string", "null"], "description": "Wire encoding of the HTML response - the coding the server named, commonly `br` or `gzip`, or `none` when it was observed to be uncompressed. `null` means undetermined: our edge runtime decompresses the response before we see it, so a site's `Content-Encoding` is usually not observable. Never read `null` as \"compression is off\"." },
              "compressed": { "type": ["boolean", "null"], "description": "Whether the HTML was compressed on the wire. `null` when undetermined, in which case compression is excluded from `score` rather than counted against it." },
              "compressionSource": { "type": ["string", "null"], "description": "Which header the answer came from: `content-encoding`, or `content-length` when a surviving length matching the bytes we decoded proved nothing was decompressed. `null` when undetermined." },
              "compressionNote": { "type": ["string", "null"], "description": "Why `compression` is undetermined. `null` when it was observed." },
              "https": { "type": "boolean" },
              "requests": { "type": "integer" },
              "note": { "type": "string" }
            }
          },
          "security": {
            "type": "object",
            "properties": {
              "https": { "type": "boolean" },
              "hsts": { "type": "boolean" },
              "xContentType": { "type": "boolean" },
              "contentSecurityPolicy": { "type": "boolean" },
              "xFrameOptions": { "type": "boolean" }
            }
          },
          "composition": { "type": "object", "description": "Counts of scripts, stylesheets, images, and inline blocks in the HTML." }
        }
      },
      "FontReport": {
        "type": "object",
        "required": ["success", "url", "checkedAt", "fonts"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "url": { "type": "string", "format": "uri" },
          "checkedAt": { "type": "string", "format": "date-time" },
          "isWordPress": { "type": "boolean" },
          "blocked": { "type": "boolean", "description": "True when the target refused the request or served a bot challenge instead of the page." },
          "blockedBy": { "type": ["string", "null"], "description": "The CDN or WAF that produced the refusal, when identifiable from its headers." },
          "reason": { "type": "string", "description": "Why the page could not be read. Absent on a completed analysis." },
          "note": { "type": ["string", "null"] },
          "fonts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "family": { "type": "string" },
                "source": { "type": "string", "enum": ["google", "bunny", "fontshare", "adobe", "self-hosted", "declared"] },
                "weights": { "type": "array", "items": { "type": "string" } },
                "styles": { "type": "array", "items": { "type": "string" } },
                "variable": { "type": "boolean" },
                "isPrimary": { "type": "boolean" },
                "icon": { "type": "boolean" },
                "fallbacks": { "type": "array", "items": { "type": "string" } },
                "files": { "type": "array", "items": { "type": "string" } }
              }
            }
          },
          "systemStacks": { "type": "array", "items": { "type": "string" } },
          "iconFonts": { "type": "array", "items": { "type": "object" } },
          "sources": { "type": "object", "description": "Per-provider detection, each with a `detected` boolean." },
          "wpFontPresets": { "type": "array", "items": { "type": "object" } },
          "typeScale": {
            "type": "array",
            "description": "Distinct font-size values, ascending by px equivalent.",
            "items": {
              "type": "object",
              "properties": { "value": { "type": "string" }, "count": { "type": "integer" } }
            }
          },
          "stylesheets": { "$ref": "#/components/schemas/StylesheetStats" }
        }
      },
      "ColorReport": {
        "type": "object",
        "required": ["success", "url", "checkedAt", "palette"],
        "properties": {
          "success": { "type": "boolean", "const": true },
          "url": { "type": "string", "format": "uri" },
          "checkedAt": { "type": "string", "format": "date-time" },
          "isWordPress": { "type": "boolean" },
          "blocked": { "type": "boolean" },
          "blockedBy": { "type": ["string", "null"] },
          "reason": { "type": "string" },
          "note": { "type": ["string", "null"] },
          "palette": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "hex": { "type": "string" },
                "rgb": { "type": "string" },
                "hsl": { "type": "string" },
                "name": { "type": ["string", "null"] },
                "count": { "type": "integer" },
                "roles": { "type": "array", "items": { "type": "string" } },
                "topRole": { "type": ["string", "null"] }
              }
            }
          },
          "wpPalette": { "type": "array", "items": { "type": "object" }, "description": "Colors from the theme's theme.json presets." },
          "themeGlobals": { "type": "array", "items": { "type": "object" } },
          "gradients": { "type": "array", "items": { "type": "object" } },
          "themeColorMeta": { "type": ["string", "null"] },
          "roles": {
            "type": "object",
            "description": "The one color inferred for each role, or null.",
            "properties": {
              "background": { "type": ["object", "null"] },
              "text": { "type": ["object", "null"] },
              "primary": { "type": ["object", "null"] },
              "accent": { "type": ["object", "null"] },
              "border": { "type": ["object", "null"] }
            }
          },
          "contrast": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": { "type": "string" },
                "ratio": { "type": "number" },
                "rating": { "type": "string" },
                "note": { "type": ["string", "null"] }
              }
            }
          },
          "stats": { "type": "object" },
          "stylesheets": { "$ref": "#/components/schemas/StylesheetStats" }
        }
      },
      "StylesheetStats": {
        "type": "object",
        "description": "How much CSS was reachable within the fetch budget.",
        "properties": {
          "found": { "type": "integer" },
          "fetched": { "type": "integer" },
          "failed": { "type": "integer" },
          "inlineBlocks": { "type": "integer" },
          "cssBytes": { "type": "integer" }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": ["jsonrpc", "method"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": { "type": ["string", "number", "null"] },
          "method": { "type": "string" },
          "params": { "type": "object" }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": ["jsonrpc"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": { "type": ["string", "number", "null"] },
          "result": { "type": "object" },
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "integer" },
              "message": { "type": "string" }
            }
          }
        }
      }
    }
  }
}
