{
  "openapi": "3.1.0",
  "info": {
    "title": "Stuff on Screen Content Effort API",
    "version": "2.1.0",
    "description": "Independent heuristic feedback on public English articles, not Google scores or ranking predictions. No API key required. Reports are visible to anyone with the link. Send one analysis at a time. Shared cache lasts six hours and is keyed by URL and rubric version. Approximate per-connection rate limit: 20 analysis requests per minute per edge location. Global daily allowance: 1,000 uncached/fresh fetch attempts, including failures, reset at midnight UTC. Respect Retry-After on HTTP 429. Saved-result reads and shared-cache hits do not consume the daily allowance. Compare scores only within the same rubricVersion."
  },
  "servers": [
    {
      "url": "https://tools.stuffonscreen.com"
    }
  ],
  "paths": {
    "/api/analyze": {
      "post": {
        "operationId": "analyzePage",
        "summary": "Check a public webpage and save a shareable report",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "description": "Public HTTP(S) article URL. No credentials or private addresses."
                  },
                  "fresh": {
                    "type": "boolean",
                    "default": false,
                    "description": "Fetch and score again, preserving any older report."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved analysis; use the id at /contentEffort/{id} to share.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalysisResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL or private address",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Cross-origin browser request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Page cannot be scored",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Busy, anonymous rate limit or daily allowance reached. Wait the number of seconds in Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before a retry. The daily allowance resets at midnight UTC.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/results/{id}": {
      "get": {
        "operationId": "getSavedResult",
        "summary": "Read a stored result without fetching the page again",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Saved analysis; use the id at /contentEffort/{id} to share.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalysisResult"
                }
              }
            }
          },
          "404": {
            "description": "Result not found"
          },
          "503": {
            "description": "Storage unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AnalysisResult": {
        "type": "object",
        "required": [
          "id",
          "score",
          "categories",
          "improvements",
          "analyzedAt",
          "rubricVersion",
          "confidence",
          "page",
          "warnings"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "label": {
            "type": "string"
          },
          "engine": {
            "type": "string",
            "enum": [
              "rules-v2"
            ]
          },
          "rubricVersion": {
            "type": "string"
          },
          "analyzedAt": {
            "type": "string",
            "format": "date-time"
          },
          "confidence": {
            "type": "string",
            "enum": [
              "low",
              "moderate"
            ]
          },
          "rationale": {
            "type": "string"
          },
          "page": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              },
              "title": {
                "type": "string"
              },
              "wordCount": {
                "type": "integer"
              },
              "truncated": {
                "type": "boolean"
              }
            }
          },
          "categories": {
            "type": "array",
            "minItems": 7,
            "maxItems": 7,
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "weight": {
                  "type": "integer"
                },
                "score": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100
                },
                "rationale": {
                  "type": "string"
                },
                "signals": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "label": {
                        "type": "string"
                      },
                      "points": {
                        "type": "integer"
                      }
                    }
                  }
                },
                "evidence": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "kind": {
                        "type": "string"
                      },
                      "text": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "improvements": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "category": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "action": {
                  "type": "string"
                },
                "impact": {
                  "type": "string",
                  "enum": [
                    "high",
                    "medium"
                  ],
                  "description": "Priority within this rubric, not a forecast of search rankings or traffic."
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "confidenceReason": {
            "type": "string",
            "description": "Reading and rubric-fit limits in plain language; optional on older saved reports. Confidence is not a probability of accuracy."
          },
          "cached": {
            "type": "boolean",
            "description": "True when a request reuses a shared cached report; omitted on saved snapshots."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          }
        }
      }
    }
  }
}
