{
  "openapi": "3.1.0",
  "info": {
    "title": "Siteline API",
    "version": "1.0.0",
    "description": "AI agent readiness scanner for public websites. Evaluates Signal, Navigate, Absorb, and Perform (SNAP) across any URL. Rate limiting conforms to the Graceful Boundaries specification at Level 4.",
    "contact": {
      "name": "PAICE",
      "url": "https://paice.work/"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://siteline.to/terms/"
    }
  },
  "servers": [
    {
      "url": "https://siteline.to",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Full LLM context — everything an agent needs to use this API",
    "url": "https://siteline.to/llms-full.txt"
  },
  "paths": {
    "/api/scan": {
      "get": {
        "operationId": "scanUrl",
        "summary": "Scan a public URL for agent readiness",
        "description": "Evaluates a public website across the SNAP rubric and returns a grade (A-F), score (0-100), per-pillar breakdown, findings, and remediation tier. Results are cached for 24 hours per domain.",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public URL or domain to scan.",
            "example": "https://example.com"
          },
          {
            "name": "siteType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "business",
                "publishing",
                "docs",
                "software",
                "data-reference",
                "portfolio",
                "research-education",
                "transactional",
                "community",
                "authenticated-app",
                "other"
              ]
            },
            "description": "Optional site-family hint that focuses route discovery and result metadata. Stored as a hint, not score authority: scoring stays evidence-gated and an unrecognized value is ignored (the default business profile applies). Additional internal profile aliases exist but are not part of the stable public contract.",
            "example": "docs"
          },
          {
            "name": "debug",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            },
            "description": "Include fetch timing and debug details in the response."
          }
        ],
        "responses": {
          "200": {
            "description": "Scan result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (10 per IP per hour)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/api/result": {
      "get": {
        "operationId": "getResult",
        "summary": "Look up a stored scan result",
        "description": "Retrieves a previously stored scan result by result ID or domain. Homepage result IDs follow domain-slug-YYYYMMDD; path/query-scoped result IDs follow domain-slug-path-scope-hash-YYYYMMDD.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Result ID (e.g. example-com-20260322 or example-com-pricing-a1b2c3d4-20260322) or domain (e.g. example.com)."
          }
        ],
        "responses": {
          "200": {
            "description": "Stored scan result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanResult"
                }
              }
            }
          },
          "404": {
            "description": "No result found"
          },
          "429": {
            "description": "Rate limit exceeded (60 per IP per minute)"
          }
        }
      }
    },
    "/api/limits": {
      "get": {
        "operationId": "getLimits",
        "summary": "Rate limit and policy discovery",
        "description": "Returns all enforced rate limits, SSRF protection policy, DNS preflight behavior, response body byte caps, and endpoint links. Conforms to Graceful Boundaries Level 4."
      }
    },
    "/api/og": {
      "get": {
        "operationId": "getOgImage",
        "summary": "Generate OG grade card image",
        "description": "Generates a 1200x630 branded grade card image for social sharing.",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "grade",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "A",
                "B",
                "C",
                "D",
                "F"
              ]
            }
          },
          {
            "name": "score",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100
            }
          },
          {
            "name": "resultId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Alternative: auto-lookup from stored result."
          }
        ]
      }
    },
    "/api/email-capture": {
      "post": {
        "operationId": "captureEmail",
        "summary": "Submit email for scan report delivery",
        "description": "Captures an email address and optional scan result snapshot for report delivery."
      }
    }
  },
  "components": {
    "schemas": {
      "ScanResult": {
        "type": "object",
        "required": [
          "ok",
          "normalizedURL",
          "domain",
          "scannedAt",
          "grade",
          "score",
          "label"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "normalizedURL": {
            "type": "string",
            "format": "uri"
          },
          "domain": {
            "type": "string"
          },
          "scannedAt": {
            "type": "string",
            "format": "date-time"
          },
          "resultId": {
            "type": "string",
            "description": "Stable identifier. Homepage scans use domain-slug-YYYYMMDD; path/query-scoped scans use domain-slug-path-scope-hash-YYYYMMDD."
          },
          "grade": {
            "type": "string",
            "enum": [
              "A",
              "B",
              "C",
              "D",
              "F"
            ]
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "label": {
            "type": "string",
            "enum": [
              "Agent-Usable",
              "Nearly Agent-Ready",
              "Mostly Usable",
              "Needs Clearer Paths",
              "Needs More Agentic Resources",
              "Needs Agentic Foundations",
              "Hard for Agents",
              "Agent-Blocked or Unusable"
            ]
          },
          "likelyFailureMode": {
            "type": "string"
          },
          "pillars": {
            "type": "object",
            "properties": {
              "access": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              "navigability": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              "readability": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              "actionHandoff": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              }
            }
          },
          "findings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "pass",
                    "warn",
                    "fail",
                    "blocked",
                    "not_applicable"
                  ]
                },
                "title": {
                  "type": "string"
                },
                "reason": {
                  "type": "string"
                },
                "impact": {
                  "type": "string"
                },
                "remediation": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "group": {
                  "type": "string",
                  "enum": [
                    "blockers",
                    "coreIssues",
                    "opportunities"
                  ]
                },
                "confidence": {
                  "type": "string",
                  "enum": [
                    "high",
                    "medium",
                    "low"
                  ]
                }
              }
            }
          },
          "agenticEnablement": {
            "type": "object",
            "description": "Layer 2: qualitative assessment of 8 agentic purposes. Related resources are grouped by purpose (best score counts). Level caps the maximum grade.",
            "properties": {
              "level": {
                "type": "integer",
                "minimum": 0,
                "maximum": 4,
                "description": "0-1=cap D, 2=cap C, 3=cap B, 4=cap A"
              },
              "totalQuality": {
                "type": "integer",
                "description": "Sum of per-purpose quality scores (0/1/2 each, max 16)"
              },
              "maxQuality": {
                "type": "integer",
                "description": "Maximum possible quality score (16)"
              },
              "purposes": {
                "type": "object",
                "description": "Per-purpose quality scores (best resource per purpose)",
                "additionalProperties": {
                  "type": "integer"
                }
              },
              "details": {
                "type": "object",
                "description": "Per-resource quality scores (individual breakdown)",
                "additionalProperties": {
                  "type": "integer"
                }
              }
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "headline": {
                "type": "string"
              },
              "detail": {
                "type": "string"
              }
            }
          },
          "external": {
            "type": "object",
            "description": "Attributed third-party baseline panels (e.g. external.cloudflare from isitagentready.com). Informational only: never feeds score, grade, or level. See schemas/scan-result.schema.json for the full shape.",
            "properties": {
              "cloudflare": {
                "type": "object",
                "properties": {
                  "available": {
                    "type": "boolean"
                  },
                  "informational": {
                    "type": "boolean"
                  },
                  "attribution": {
                    "type": "object"
                  },
                  "level": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "levelLabel": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "categories": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "standards": {
            "type": "object",
            "description": "PAICE standards conformance panel (Graceful Boundaries, GuideCheck). Informational only: never feeds score, grade, or level. See schemas/scan-result.schema.json for the full shape.",
            "properties": {
              "version": {
                "type": "string"
              },
              "informational": {
                "type": "boolean"
              },
              "entries": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "RateLimitError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "limit": {
            "type": "string"
          },
          "retryAfterSeconds": {
            "type": "integer"
          },
          "why": {
            "type": "string"
          },
          "humanUrl": {
            "type": "string",
            "format": "uri"
          },
          "requestId": {
            "type": "string"
          }
        }
      }
    }
  }
}
