{
  "openapi": "3.0.3",
  "info": {
    "title": "AgentRank API",
    "version": "1.4.0",
    "description": "Scores how ready a website is for AI agents. The same JSON endpoints the AgentRank web app uses. All operations require a signed-in AgentRank account. Free accounts get 1 saved scan, then 402 limit_reached. Pro limits: 50 calls per rolling 24 hours and 200 per rolling 30 days, counting every call (saved or not; admins exempt). Agency: 100 client scans per billing period and max 50 per day; quota exhaustion returns 402 agency_quota.",
    "contact": { "name": "AgentRank", "email": "leon@jannick.io", "url": "https://jannick.io/contact" },
    "termsOfService": "https://jannick.io/terms"
  },
  "externalDocs": { "description": "Human-readable API guide", "url": "https://jannick.io/api" },
  "servers": [{ "url": "https://jannick.io/api/apps/6a9d111004ca350a0146906d/functions" }],
  "security": [{ "sessionAuth": [] }],
  "components": {
    "securitySchemes": {
      "sessionAuth": { "type": "http", "scheme": "bearer", "description": "The signed-in user's AgentRank session token, as sent by the AgentRank web app. Agents without a session should hand a human https://jannick.io/?url={target}." }
    },
    "schemas": {
      "Category": { "type": "object", "properties": { "score": { "type": "integer", "minimum": 0, "maximum": 100 }, "explanation": { "type": "string" }, "findings": { "type": "array", "items": { "type": "object", "properties": { "text": { "type": "string" }, "steps": { "type": "array", "items": { "type": "string" } } } } } } },
      "Fix": { "type": "object", "properties": { "title": { "type": "string" }, "why": { "type": "string" }, "impact": { "type": "string", "enum": ["high", "medium", "low"] }, "effort": { "type": "string", "enum": ["low", "medium", "high"] }, "steps": { "type": "array", "items": { "type": "string" } } } },
      "Result": {
        "type": "object",
        "properties": {
          "site_name": { "type": "string" }, "detected_cms": { "type": "string" }, "overall_score": { "type": "integer", "minimum": 0, "maximum": 100 }, "grade": { "type": "string", "example": "A-" }, "summary": { "type": "string" },
          "categories": { "type": "object", "properties": { "discoverability": { "$ref": "#/components/schemas/Category" }, "clarity": { "$ref": "#/components/schemas/Category" }, "pricing_transparency": { "$ref": "#/components/schemas/Category" }, "actionability": { "$ref": "#/components/schemas/Category" }, "trust": { "$ref": "#/components/schemas/Category" }, "local_visibility": { "$ref": "#/components/schemas/Category" } } },
          "top_fixes": { "type": "array", "items": { "$ref": "#/components/schemas/Fix" } }, "generated_llms_txt": { "type": "string" }, "generated_schema": { "type": "string" }, "live_crawled": { "type": "boolean" }, "scanned_at": { "type": "string", "format": "date-time" }
        }
      },
      "SharedReport": {
        "type": "object", "description": "Selected shared audit fields; generated implementation assets are never included.",
        "properties": { "site_name": { "type": "string" }, "url": { "type": "string" }, "overall_score": { "type": "number" }, "grade": { "type": "string" }, "summary": { "type": "string" }, "categories": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/Category" } }, "top_fixes": { "type": "string", "description": "JSON-encoded array of fix objects." }, "created_date": { "type": "string", "format": "date-time" }, "badge_url": { "type": "string", "nullable": true }, "og_image_url": { "type": "string", "nullable": true }, "agent_view": { "type": "string", "description": "JSON-encoded array of simulated buyer answers." }, "detected_cms": { "type": "string", "nullable": true } }
      },
      "BenchmarkRow": { "type": "object", "properties": { "url": { "type": "string" }, "host": { "type": "string" }, "available": { "type": "boolean" }, "auditId": { "type": "string", "nullable": true }, "site_name": { "type": "string" }, "overall_score": { "type": "integer", "minimum": 0, "maximum": 100 }, "grade": { "type": "string" }, "categories": { "type": "object", "additionalProperties": { "type": "integer" } }, "scanned_at": { "type": "string", "format": "date-time" }, "public": { "type": "boolean" } } },
      "BenchmarkInsight": { "type": "object", "properties": { "host": { "type": "string" }, "signal": { "type": "string", "enum": ["discoverability", "clarity", "pricing_transparency", "actionability", "trust", "local_visibility", "overall"] }, "delta": { "type": "integer" } } },
      "Error": { "type": "object", "properties": { "error": { "type": "string", "enum": ["invalid_url", "invalid_input", "auth_required", "limit_reached", "agency_quota", "fair_use", "rate_limited", "scan_failed", "server_error", "not_found", "invalid_baseline", "invalid_progress"] }, "message": { "type": "string" }, "scope": { "type": "string", "enum": ["day", "month"], "description": "Present for runAudit fair_use responses." } } }
    }
  },
  "paths": {
    "/runAudit": {
      "post": {
        "operationId": "runAudit",
        "summary": "Scan a website for agent readiness",
        "description": "Live-fetches the homepage, robots.txt, sitemap.xml, llms.txt and agent manifests, grades six categories and returns the full report. Takes about a minute. Pro accounts are limited to 50 calls per rolling 24 hours and 200 per rolling 30 days; every call counts, saved or not; admins are exempt. Agency accounts: 100 client scans per billing period and max 50 per day, then 402 agency_quota. On 429 fair_use, wait at least Retry-After seconds before retrying. This is the earliest release for the indicated scope, not a reservation: the other window and additional scans can still prevent a retry. Free exhaustion remains 402, missing authentication remains 401.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["url"], "properties": { "url": { "type": "string", "example": "example.com", "description": "Public HTTP(S) website. Scheme optional." }, "force": { "type": "boolean", "default": false, "description": "Paid-plan request marker; interactive scans already use a live crawl." }, "save": { "type": "boolean", "default": true }, "previousAuditId": { "type": "string", "pattern": "^[a-fA-F0-9]{24}$", "description": "Owned saved report for the same normalized URL; reviews tracked fixes." }, "source": { "type": "string", "enum": ["manual", "compare"], "default": "manual" } } } } } },
        "responses": {
          "200": { "description": "The report", "content": { "application/json": { "schema": { "type": "object", "properties": { "result": { "$ref": "#/components/schemas/Result" }, "saved": { "type": "boolean" }, "auditId": { "type": "string", "nullable": true }, "cached": { "type": "boolean", "enum": [false] } } } } } },
          "400": { "description": "invalid_url, invalid_baseline or invalid_progress", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "auth_required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "402": { "description": "limit_reached — free scans used; upgrade to Pro, or agency_quota — Agency billing-period quota reached", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "not_found — inaccessible baseline report", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": {
            "description": "fair_use — paid-plan cap reached: 50 per rolling 24 hours (scope day) or 200 per rolling 30 days (scope month); every call counts, saved or not.",
            "headers": { "Retry-After": { "description": "Positive integer seconds until enough scans leave the indicated rolling window to permit another scan. Wait at least this long; the other window may still apply.", "schema": { "type": "string", "pattern": "^[1-9][0-9]*$", "example": "3600" } } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "fair_use", "scope": "day", "message": "Daily fair-use limit reached." } } }
          },
          "500": { "description": "server_error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "502": { "description": "scan_failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/getPublicReport": {
      "post": {
        "operationId": "getPublicReport", "summary": "Read a report its owner shared publicly",
        "description": "Sign-in required, except white-label reports from active Agency subscriptions, which open without a login. Only explicitly shared reports are returned. Best-effort per-IP limit: 60 lookups per rolling hour. This endpoint's 429 response does not currently include Retry-After.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["id"], "properties": { "id": { "type": "string", "pattern": "^[a-fA-F0-9]{24}$" } } } } } },
        "responses": {
          "200": { "description": "The shared report (without paste-ready llms.txt/JSON-LD)", "content": { "application/json": { "schema": { "type": "object", "properties": { "result": { "$ref": "#/components/schemas/SharedReport" } } } } } },
          "401": { "description": "auth_required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "not_found — private, unknown or malformed id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "rate_limited — too many lookups from this IP", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "500": { "description": "server_error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/getBenchmark": {
      "post": {
        "operationId": "getBenchmark", "summary": "Compare 2–3 sites from existing audit records (no new scans)",
        "description": "Reads the most recent audit per requested host that the caller can see (owned audits, plus shared audits as a fallback) and returns side-by-side scores and gaps where a competitor beats the first site by at least five points. Read-only; no new scans. Sign-in required.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["urls"], "properties": { "urls": { "type": "array", "items": { "type": "string", "description": "Website URL; scheme optional." }, "minItems": 2, "maxItems": 3, "description": "First entry is your site; others are competitors." } } } } } },
        "responses": {
          "200": { "description": "Per-site scores and gap insights", "content": { "application/json": { "schema": { "type": "object", "properties": { "rows": { "type": "array", "items": { "$ref": "#/components/schemas/BenchmarkRow" } }, "insights": { "type": "array", "items": { "$ref": "#/components/schemas/BenchmarkInsight" } }, "generated_at": { "type": "string", "format": "date-time" } } } } } },
          "400": { "description": "invalid_input — fewer than 2 URLs", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "auth_required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "500": { "description": "server_error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  }
}
