{
  "openapi": "3.1.0",
  "info": {
    "title": "AgentFit",
    "version": "v2.34.0",
    "description": "Audit public API documentation for AI/agent-readiness against the 30-criteria rubric\ndefined at https://www.gumeniuk.com/en/tech/agentfit-metodologiya/.\n\nOutput JSON is byte-compatible with the LLM-audit prompt from the original article (linked there),\nso a human can compare an LLM-driven audit and this service's code-driven audit\nside by side.\n\n## Authentication\nThis public API is **unauthenticated**. A separate authenticated admin API\n(JWT bearer) backs the first-party `/admin` console; it is intentionally not\ndocumented here and is not for third-party use.\n\n## Recommended flow\nAudits can take up to ~30s. `POST /audit` is **synchronous by default** (blocks\nand returns the report as `200`, up to the server's 120s ceiling) on every\ndeployment. For interactive/UI clients, opt into **async** with\n`?async=true`: it returns `202` with a `poll_url`; poll\n`GET /api/public/audit/{id}` every 2–3s until `status` is `done` **or**\n`error`. (Synchronous callers should set a client timeout of at least 120s.)\n\n```bash\n# async (recommended for UI / non-blocking clients)\nRUN=$(curl -s -X POST 'https://agentfit.dev/audit?async=true' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"base_url\":\"https://docs.stripe.com\"}' | jq -r .run_id)\n# poll until done|error\ncurl -s \"https://agentfit.dev/api/public/audit/$RUN\" | jq '.status, .total_score'\n# diff vs the previous run for this site\ncurl -s \"https://agentfit.dev/api/public/audit/$RUN/diff\" | jq '.total_delta'\n\n# sync (convenience; blocks up to ~30s, set timeout \u003e=120s)\ncurl -s --max-time 120 -X POST 'https://agentfit.dev/audit' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"base_url\":\"https://docs.stripe.com\"}' | jq '.total_score'\n```\n\n## Rate limits \u0026 quota\n- `POST /audit`: ~1 request / 30s per IP (burst 2).\n- A site may be audited at most **once per 24h per network** — re-submitting a\n  recently-audited site returns `429`; do NOT retry-loop, read the existing run\n  (its share page) or wait for `Retry-After`.\n- Polling/read GETs: ~1 req/s per IP (burst 30). Browse JSON: ~5 req/s (burst 20).\n- Every `429` carries a `Retry-After` header (seconds).\n\n## Not documented here (fetch directly from the service)\nHuman HTML pages (`/`, `/browse*`, `/r/{id}`, `/rubric`, ...), the share-image\n`/r/{id}/og.png`, operational `/healthz` and `/metrics`, static assets, and\nstandardized discovery files (`/llms.txt`, `/llms-full.txt`, `/robots.txt`,\n`/sitemap.xml`, `/index.md`).\n\n## Tooling note\nThis is an OpenAPI **3.1** document. Use Redoc or a recent Swagger UI; older\nSwagger UI versions do not render 3.1.\n",
    "contact": {
      "name": "Stanislav Gumeniuk",
      "email": "i@gumeniuk.com",
      "url": "https://agentfit.dev"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://agentfit.dev/terms"
    }
  },
  "servers": [
    {
      "url": "https://agentfit.dev",
      "description": "production"
    },
    {
      "url": "http://localhost:8080",
      "description": "local development"
    }
  ],
  "paths": {
    "/openapi.yaml": {
      "get": {
        "summary": "This OpenAPI specification (YAML)",
        "operationId": "openapi",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/yaml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "summary": "This OpenAPI specification (JSON)",
        "operationId": "openapiJSON",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/audit": {
      "post": {
        "summary": "Run an AI-readiness audit",
        "description": "Submit a documentation base URL for auditing. Synchronous by default\n(blocks until the report is ready, up to the server's 120s ceiling).\nWith `?async=true`, returns `202` immediately with a `poll_url`.\n",
        "operationId": "audit",
        "parameters": [
          {
            "name": "async",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Opt-in. When true, enqueue the audit and return 202 + poll_url instead\nof blocking. When false/absent, the audit runs synchronously and the\nreport is returned at 200. (The first-party web UI sets this to true.)\n"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AuditRequest"
              },
              "example": {
                "base_url": "https://docs.stripe.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Audit completed (synchronous). The body is the AuditReport at the top\nlevel. `X-Audit-Cache: HIT|MISS` indicates a cached replay;\n`X-Audit-Diagnostics` (base64) carries the non-scoring diagnostics\nenvelope when present.\n",
            "headers": {
              "X-Audit-Cache": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "HIT",
                    "MISS"
                  ]
                }
              },
              "X-Audit-Diagnostics": {
                "schema": {
                  "type": "string"
                },
                "description": "Base64-encoded diagnostics envelope (when present)."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditReport"
                }
              }
            }
          },
          "202": {
            "description": "Audit accepted (async). Poll `poll_url` until `status=done`. A browser\nform POST (`application/x-www-form-urlencoded`) instead receives a\n303 redirect to the share page `share_url`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AsyncAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (malformed body, non-public or non-HTTPS base_url)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Target URL unreachable / audit could not run",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or the 24h-per-site quota was hit. Honour `Retry-After`;\non a quota hit, read the existing run rather than resubmitting.\n",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "Audit exceeded timeout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/audit/{id}": {
      "get": {
        "summary": "Poll / read a guest audit run",
        "operationId": "getPublicAudit",
        "description": "Returns the run's current status; the full report is embedded under `report` when `status=done`. Poll every 2-3s until status is `done` or `error`. Always Cache-Control no-store.",
        "parameters": [
          {
            "$ref": "#/components/parameters/RunId"
          }
        ],
        "responses": {
          "200": {
            "description": "Run status (pending/running, error, or done with embedded report)",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/AuditRunPending"
                    },
                    {
                      "$ref": "#/components/schemas/AuditRunError"
                    },
                    {
                      "$ref": "#/components/schemas/AuditRunDone"
                    }
                  ],
                  "discriminator": {
                    "propertyName": "status",
                    "mapping": {
                      "pending": "#/components/schemas/AuditRunPending",
                      "running": "#/components/schemas/AuditRunPending",
                      "error": "#/components/schemas/AuditRunError",
                      "done": "#/components/schemas/AuditRunDone"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "id is not a UUID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Audit not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Polling rate limit exceeded",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/audit/{id}/history": {
      "get": {
        "summary": "List prior runs for the same site",
        "operationId": "getPublicAuditHistory",
        "parameters": [
          {
            "$ref": "#/components/parameters/RunId"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC3339 timestamp; returns runs strictly older than this. Defaults to the anchor run's start time. Feed the previous page's next_cursor back here; stop when next_cursor is empty."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 100,
              "minimum": 1,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of run summaries (newest first)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoryPage"
                }
              }
            }
          },
          "400": {
            "description": "id not a UUID / cursor not RFC3339",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Audit not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/audit/{id}/diff": {
      "get": {
        "summary": "Diff a run against a baseline",
        "operationId": "getPublicAuditDiff",
        "description": "Compares the target run (`{id}`) against a baseline. Without `baseline`, auto-selects the most recent prior done run for the same site (`baseline_auto=true`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/RunId"
          },
          {
            "name": "baseline",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Run id to diff against. Omit to auto-select the previous done run."
          }
        ],
        "responses": {
          "200": {
            "description": "Per-category and per-criterion deltas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiffResponse"
                }
              }
            }
          },
          "400": {
            "description": "id / baseline not a UUID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Run not found / no prior done run for auto-baseline",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Target not done, or baseline invalid (same run / different site / not done)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/browse/summary": {
      "get": {
        "summary": "Corpus summary (all audited sites)",
        "operationId": "browseSummary",
        "description": "ETag-cached; supports `If-None-Match` → 304.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrowseSummary"
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          }
        }
      }
    },
    "/api/public/browse/top": {
      "get": {
        "summary": "Top sites by score in a window",
        "operationId": "browseTop",
        "parameters": [
          {
            "name": "window",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "30d",
                "all"
              ],
              "default": "7d"
            },
            "description": "Look-back window for ranking."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 50,
              "minimum": 1,
              "default": 10
            }
          },
          {
            "name": "order",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "best",
                "worst"
              ],
              "default": "best"
            },
            "description": "Rank by highest (best) or lowest (worst) score."
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrowseTop"
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          }
        }
      }
    },
    "/api/public/browse/stats": {
      "get": {
        "summary": "Corpus-wide score / TLD / org distributions",
        "operationId": "browseStats",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrowseStats"
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          }
        }
      }
    },
    "/api/public/browse/search": {
      "get": {
        "summary": "Search audited sites by substring",
        "operationId": "browseSearch",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring of the site URL."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 50,
              "minimum": 1,
              "default": 20
            }
          },
          {
            "$ref": "#/components/parameters/IfNoneMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrowseSearch"
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          }
        }
      }
    },
    "/api/public/dashboard": {
      "get": {
        "summary": "Public dashboard data by slug",
        "operationId": "publicDashboard",
        "description": "Returns a dashboard an operator has marked public. Slugs are operator-provisioned (there is no public listing endpoint).",
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Operator-provisioned public dashboard slug."
          },
          {
            "name": "bucket",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "30d",
                "90d",
                "12m"
              ],
              "default": "90d"
            },
            "description": "Aggregation/look-back bucket for dashboard series."
          }
        ],
        "responses": {
          "200": {
            "description": "Resolved dashboard widgets",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Dashboard"
                }
              }
            }
          },
          "202": {
            "description": "Cache warming; retry after the Retry-After delay",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "warming"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "slug required / invalid bucket",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Dashboard is not public",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Dashboard not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "RunId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Run id (UUID) returned by POST /audit?async=true."
      },
      "IfNoneMatch": {
        "name": "If-None-Match",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Conditional request — server returns 304 when the ETag is unchanged."
      }
    },
    "headers": {
      "ETag": {
        "schema": {
          "type": "string"
        },
        "description": "Content-hash ETag; send it back as If-None-Match to get a 304."
      }
    },
    "schemas": {
      "AuditRequest": {
        "type": "object",
        "required": [
          "base_url"
        ],
        "properties": {
          "base_url": {
            "type": "string",
            "format": "uri",
            "description": "Root URL of the documentation site, no trailing slash. Must be public and (for guest submissions) HTTPS.",
            "example": "https://docs.stripe.com"
          }
        }
      },
      "AsyncAccepted": {
        "type": "object",
        "required": [
          "run_id",
          "status",
          "poll_url",
          "share_url"
        ],
        "description": "202 body for an async audit submission (production DB-backed path).",
        "properties": {
          "run_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending"
            ]
          },
          "poll_url": {
            "type": "string",
            "description": "Relative path to GET for status (same as /api/public/audit/{run_id}).",
            "example": "/api/public/audit/019ee9a6-6323-7356-b7b2-ce3ed9694290"
          },
          "share_url": {
            "type": "string",
            "description": "Relative path to the human-readable HTML share page (not JSON).",
            "example": "/r/019ee9a6-6323-7356-b7b2-ce3ed9694290"
          }
        },
        "example": {
          "run_id": "019ee9a6-6323-7356-b7b2-ce3ed9694290",
          "status": "pending",
          "poll_url": "/api/public/audit/019ee9a6-6323-7356-b7b2-ce3ed9694290",
          "share_url": "/r/019ee9a6-6323-7356-b7b2-ce3ed9694290"
        }
      },
      "AuditRunPending": {
        "type": "object",
        "required": [
          "run_id",
          "status",
          "base_url",
          "started_at",
          "poll_url"
        ],
        "properties": {
          "run_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running"
            ]
          },
          "base_url": {
            "type": "string",
            "format": "uri"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "poll_url": {
            "type": "string"
          }
        },
        "example": {
          "run_id": "019ee9a6-6323-7356-b7b2-ce3ed9694290",
          "status": "running",
          "base_url": "https://docs.stripe.com",
          "started_at": "2026-05-15T10:02:11Z",
          "poll_url": "/api/public/audit/019ee9a6-6323-7356-b7b2-ce3ed9694290"
        }
      },
      "AuditRunError": {
        "type": "object",
        "required": [
          "run_id",
          "status",
          "base_url",
          "error"
        ],
        "properties": {
          "run_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "base_url": {
            "type": "string",
            "format": "uri"
          },
          "error": {
            "type": "string"
          }
        },
        "example": {
          "run_id": "019ee9a6-6323-7356-b7b2-ce3ed9694290",
          "status": "error",
          "base_url": "https://docs.stripe.com",
          "error": "target unreachable: dial tcp: i/o timeout"
        }
      },
      "AuditRunDone": {
        "type": "object",
        "required": [
          "run_id",
          "status",
          "base_url",
          "started_at",
          "finished_at",
          "total_score",
          "report",
          "share_url"
        ],
        "description": "Done run. The full report is nested under `report` (note: the synchronous\nPOST /audit returns the report at the TOP level instead).\n",
        "properties": {
          "run_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "done"
            ]
          },
          "base_url": {
            "type": "string",
            "format": "uri"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": "string",
            "format": "date-time"
          },
          "total_score": {
            "type": "integer",
            "maximum": 100,
            "minimum": 0
          },
          "report": {
            "$ref": "#/components/schemas/AuditReport"
          },
          "share_url": {
            "type": "string"
          }
        },
        "example": {
          "run_id": "019ee9a6-6323-7356-b7b2-ce3ed9694290",
          "status": "done",
          "base_url": "https://docs.stripe.com",
          "started_at": "2026-05-15T10:02:11Z",
          "finished_at": "2026-05-15T10:02:39Z",
          "total_score": 78,
          "share_url": "/r/019ee9a6-6323-7356-b7b2-ce3ed9694290",
          "report": {
            "base_url": "https://docs.stripe.com",
            "audit_date": "2026-05-15",
            "auditor": "agentfit/v2.31.6",
            "total_score": 78,
            "max_score": 100
          }
        }
      },
      "HistoryPage": {
        "type": "object",
        "required": [
          "runs",
          "next_cursor",
          "base_url",
          "base_url_norm"
        ],
        "properties": {
          "runs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GuestRunSummary"
            }
          },
          "next_cursor": {
            "type": "string",
            "description": "RFC3339Nano cursor for the next page; empty when no more rows."
          },
          "base_url": {
            "type": "string",
            "format": "uri"
          },
          "base_url_norm": {
            "type": "string"
          }
        },
        "example": {
          "runs": [
            {
              "id": "019ee9a6-6323-7356-b7b2-ce3ed9694290",
              "started_at": "2026-05-15T10:02:11Z",
              "total_score": 78,
              "status": "done"
            }
          ],
          "next_cursor": "2026-05-15T10:02:11.123456Z",
          "base_url": "https://docs.stripe.com",
          "base_url_norm": "docs.stripe.com"
        }
      },
      "GuestRunSummary": {
        "type": "object",
        "required": [
          "id",
          "started_at",
          "total_score",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "total_score": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "done"
            ]
          }
        }
      },
      "DiffResponse": {
        "type": "object",
        "required": [
          "from_run_id",
          "to_run_id",
          "from_started_at",
          "to_started_at",
          "total_from",
          "total_to",
          "total_delta",
          "categories",
          "criteria",
          "baseline_auto"
        ],
        "properties": {
          "from_run_id": {
            "type": "string",
            "format": "uuid"
          },
          "to_run_id": {
            "type": "string",
            "format": "uuid"
          },
          "from_started_at": {
            "type": "string",
            "format": "date-time"
          },
          "to_started_at": {
            "type": "string",
            "format": "date-time"
          },
          "total_from": {
            "type": "integer"
          },
          "total_to": {
            "type": "integer"
          },
          "total_delta": {
            "type": "integer"
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DiffEntry"
            }
          },
          "criteria": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DiffEntry"
            }
          },
          "baseline_auto": {
            "type": "boolean",
            "description": "True when the baseline was auto-selected (no explicit ?baseline)."
          }
        },
        "example": {
          "from_run_id": "019ee990-0000-7000-8000-000000000001",
          "to_run_id": "019ee9a6-6323-7356-b7b2-ce3ed9694290",
          "from_started_at": "2026-05-14T09:00:00Z",
          "to_started_at": "2026-05-15T10:02:11Z",
          "total_from": 74,
          "total_to": 78,
          "total_delta": 4,
          "baseline_auto": true,
          "categories": [
            {
              "key": "C",
              "from": 18,
              "to": 20,
              "delta": 2,
              "from_max": 24,
              "to_max": 24
            }
          ],
          "criteria": [
            {
              "key": "C1b",
              "from": 5,
              "to": 7,
              "delta": 2,
              "from_max": 7,
              "to_max": 7,
              "from_status": "partial",
              "to_status": "present"
            }
          ]
        }
      },
      "DiffEntry": {
        "type": "object",
        "required": [
          "key",
          "from",
          "to",
          "delta"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "Category key (A..F) or criterion id (e.g. A1)."
          },
          "from": {
            "type": "integer"
          },
          "to": {
            "type": "integer"
          },
          "delta": {
            "type": "integer"
          },
          "from_max": {
            "type": "integer"
          },
          "to_max": {
            "type": "integer"
          },
          "from_status": {
            "type": "string",
            "description": "Criteria entries only."
          },
          "to_status": {
            "type": "string",
            "description": "Criteria entries only."
          }
        }
      },
      "BrowseSummary": {
        "type": "object",
        "required": [
          "sites",
          "site_count",
          "refreshed_at"
        ],
        "properties": {
          "sites": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SiteSummary"
            }
          },
          "site_count": {
            "type": "integer"
          },
          "refreshed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SiteSummary": {
        "type": "object",
        "required": [
          "base_url",
          "canonical_url",
          "run_count",
          "latest_run_at",
          "first_run_at",
          "best_score",
          "worst_score",
          "avg_score"
        ],
        "properties": {
          "base_url": {
            "type": "string"
          },
          "canonical_url": {
            "type": "string",
            "format": "uri"
          },
          "run_count": {
            "type": "integer"
          },
          "latest_run_at": {
            "type": "string",
            "format": "date-time"
          },
          "first_run_at": {
            "type": "string",
            "format": "date-time"
          },
          "best_score": {
            "type": "integer"
          },
          "worst_score": {
            "type": "integer"
          },
          "avg_score": {
            "type": "integer"
          }
        }
      },
      "BrowseTop": {
        "type": "object",
        "required": [
          "window",
          "order",
          "limit",
          "rows"
        ],
        "properties": {
          "window": {
            "type": "string",
            "enum": [
              "7d",
              "30d",
              "all"
            ]
          },
          "order": {
            "type": "string",
            "enum": [
              "best",
              "worst"
            ]
          },
          "limit": {
            "type": "integer"
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TopAudit"
            }
          }
        }
      },
      "TopAudit": {
        "type": "object",
        "required": [
          "id",
          "base_url",
          "started_at",
          "total_score"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "base_url": {
            "type": "string"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "total_score": {
            "type": "integer"
          }
        }
      },
      "BrowseStats": {
        "type": "object",
        "required": [
          "score_bands",
          "tld_distribution",
          "etld_top",
          "site_count",
          "refreshed_at"
        ],
        "properties": {
          "score_bands": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScoreBand"
            }
          },
          "tld_distribution": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TLDBucket"
            }
          },
          "etld_top": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrgBucket"
            }
          },
          "site_count": {
            "type": "integer"
          },
          "refreshed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ScoreBand": {
        "type": "object",
        "required": [
          "band",
          "count",
          "avg_band_score"
        ],
        "properties": {
          "band": {
            "type": "string",
            "enum": [
              "good",
              "partial",
              "below_avg",
              "low"
            ]
          },
          "count": {
            "type": "integer"
          },
          "avg_band_score": {
            "type": "number"
          }
        }
      },
      "TLDBucket": {
        "type": "object",
        "required": [
          "tld",
          "count"
        ],
        "properties": {
          "tld": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          }
        }
      },
      "OrgBucket": {
        "type": "object",
        "required": [
          "etld_plus_one",
          "site_count",
          "avg_best",
          "max_best"
        ],
        "properties": {
          "etld_plus_one": {
            "type": "string"
          },
          "site_count": {
            "type": "integer"
          },
          "avg_best": {
            "type": "number"
          },
          "max_best": {
            "type": "integer"
          }
        }
      },
      "BrowseSearch": {
        "type": "object",
        "required": [
          "query",
          "hits"
        ],
        "properties": {
          "query": {
            "type": "string"
          },
          "hits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SiteSummary"
            }
          }
        }
      },
      "Dashboard": {
        "type": "object",
        "description": "Resolved public dashboard. `widgets` is a JSON object keyed by widget id.",
        "required": [
          "name",
          "widgets"
        ],
        "properties": {
          "dashboard_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "resolved_at": {
            "type": "string",
            "format": "date-time"
          },
          "from": {
            "type": "string",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "format": "date-time"
          },
          "widgets": {
            "type": "object",
            "description": "Map of widget id → resolved widget. Each value carries a `type` and either `data` or `error`.",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "description": "One of: scorecard, trend, comparison, criterion_heatmap, diff_table, site_grid."
                },
                "title": {
                  "type": "string"
                },
                "data": {
                  "description": "Widget payload; shape varies by type."
                },
                "error": {
                  "type": "string",
                  "description": "Set instead of data when the widget failed to resolve."
                }
              }
            }
          }
        }
      },
      "AuditReport": {
        "type": "object",
        "required": [
          "base_url",
          "audit_date",
          "auditor",
          "e1_gating_passed",
          "unreliable_marker",
          "total_score",
          "max_score",
          "categories",
          "criteria"
        ],
        "properties": {
          "base_url": {
            "type": "string",
            "format": "uri"
          },
          "audit_date": {
            "type": "string",
            "format": "date",
            "example": "2026-05-15"
          },
          "auditor": {
            "type": "string",
            "example": "agentfit/v2.31.6"
          },
          "e1_gating_passed": {
            "type": "boolean",
            "description": "True when E1 (no-JS content visibility) scored \u003e 0."
          },
          "unreliable_marker": {
            "type": "boolean",
            "description": "True when E1 failed OR when the uniform-shell trap (duplicate bodies across URLs) was detected."
          },
          "total_score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "max_score": {
            "type": "integer",
            "enum": [
              100
            ]
          },
          "categories": {
            "type": "object",
            "required": [
              "A_discovery",
              "B_page_artifacts",
              "C_api_spec",
              "D_content",
              "E_hygiene",
              "F_agent_surface"
            ],
            "properties": {
              "A_discovery": {
                "$ref": "#/components/schemas/CategoryScore"
              },
              "B_page_artifacts": {
                "$ref": "#/components/schemas/CategoryScore"
              },
              "C_api_spec": {
                "$ref": "#/components/schemas/CategoryScore"
              },
              "D_content": {
                "$ref": "#/components/schemas/CategoryScore"
              },
              "E_hygiene": {
                "$ref": "#/components/schemas/CategoryScore"
              },
              "F_agent_surface": {
                "$ref": "#/components/schemas/CategoryScore"
              }
            }
          },
          "criteria": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CriterionResult"
            },
            "minItems": 30,
            "maxItems": 30,
            "description": "30 results in canonical order A1, A2, A3, A4, A5, B1…B6, C1a, C1b, C2, C3, D1…D6, E1…E5, F1, F2, F3, F4."
          }
        }
      },
      "CategoryScore": {
        "type": "object",
        "required": [
          "score",
          "max"
        ],
        "properties": {
          "score": {
            "type": "integer",
            "minimum": 0
          },
          "max": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "CriterionResult": {
        "type": "object",
        "required": [
          "id",
          "status",
          "score",
          "max",
          "evidence_url",
          "evidence_snippet",
          "http_status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "A1",
              "A2",
              "A3",
              "A4",
              "A5",
              "B1",
              "B2",
              "B3",
              "B4",
              "B5",
              "B6",
              "C1a",
              "C1b",
              "C2",
              "C3",
              "D1",
              "D2",
              "D3",
              "D4",
              "D5",
              "D6",
              "E1",
              "E2",
              "E3",
              "E4",
              "E5",
              "F1",
              "F2",
              "F3",
              "F4"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "present",
              "partial",
              "absent",
              "error",
              "not_applicable"
            ]
          },
          "score": {
            "type": "integer",
            "minimum": 0
          },
          "max": {
            "type": "integer",
            "minimum": 0
          },
          "evidence_url": {
            "type": "string",
            "description": "URL of the resource that informed this result (may be empty if no useful URL)."
          },
          "evidence_snippet": {
            "type": "string",
            "maxLength": 50,
            "description": "Up to 50 runes of representative evidence (snippet, header value, or summary)."
          },
          "http_status": {
            "type": "integer",
            "description": "HTTP status of the most relevant fetch (0 if no fetch was performed)."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code (added incrementally; may be absent on older paths).",
            "enum": [
              "invalid_base_url",
              "not_public_address",
              "http_not_supported",
              "rate_limited",
              "site_quota",
              "audit_failed",
              "not_found",
              "not_done",
              "method_not_allowed",
              "internal"
            ]
          },
          "retry_after": {
            "type": "integer",
            "description": "Seconds to wait before retrying (present on rate-limit/quota errors)."
          }
        },
        "example": {
          "error": "this site was already audited from your network within the last 24h; try again later",
          "code": "site_quota",
          "retry_after": 43200
        }
      }
    }
  }
}