{
  "openapi": "3.1.0",
  "info": {
    "title": "IsSiteLegit API",
    "version": "1.0.0",
    "description": "Evidence-based \"is this website legit?\" results. Results are automated assessments based on public signals — not a guarantee. Never describe a site as a scam unless its verdict is \"known_malicious\"."
  },
  "servers": [
    {
      "url": "https://issitelegit.com"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key created by an administrator"
      }
    },
    "schemas": {
      "Evidence": {
        "type": "object",
        "required": [
          "key",
          "label",
          "value",
          "impact"
        ],
        "properties": {
          "key": {
            "type": "string",
            "example": "domain.age_days"
          },
          "label": {
            "type": "string",
            "example": "Domain age"
          },
          "value": {
            "type": "string",
            "example": "Registered 12 days ago (2026-09-22)"
          },
          "impact": {
            "type": "string",
            "enum": [
              "positive",
              "negative",
              "neutral"
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "LinkCheck": {
        "type": [
          "object",
          "null"
        ],
        "description": "Exact-link check. Present (non-null) only when a full link with a path or query was given. The link is never stored. \"not_listed\" means no blacklist we check lists it — not that it is safe.",
        "required": [
          "url",
          "status",
          "listed",
          "matches",
          "checkedSources",
          "unavailableSources",
          "message"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "The link that was checked (credentials and #fragment removed, host lowercased)"
          },
          "status": {
            "type": "string",
            "enum": [
              "listed",
              "not_listed",
              "unknown"
            ]
          },
          "listed": {
            "type": "boolean"
          },
          "matches": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "source": {
                  "type": "string",
                  "enum": [
                    "urlhaus",
                    "openphish",
                    "phishtank",
                    "google_safe_browsing",
                    "google_web_risk"
                  ]
                },
                "sourceLabel": {
                  "type": "string",
                  "example": "URLhaus"
                },
                "threat": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "example": "malware distribution"
                }
              }
            }
          },
          "checkedSources": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "URLhaus",
              "OpenPhish",
              "Google Safe Browsing"
            ]
          },
          "unavailableSources": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Configured sources that failed this time"
          },
          "message": {
            "type": "string",
            "example": "This exact link IS listed as malware distribution by URLhaus — do not open it."
          }
        }
      },
      "CheckResult": {
        "type": "object",
        "required": [
          "found",
          "domain",
          "url",
          "verdict",
          "score",
          "summary",
          "lastCheckedAt",
          "evidence"
        ],
        "properties": {
          "found": {
            "type": "boolean",
            "const": true
          },
          "domain": {
            "type": "string",
            "example": "example.com",
            "description": "Canonical registrable domain (punycode)"
          },
          "displayDomain": {
            "type": "string",
            "example": "example.com"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Permanent result page"
          },
          "verdict": {
            "type": "string",
            "enum": [
              "likely_legit",
              "caution",
              "high_risk",
              "known_malicious"
            ]
          },
          "verdictLabel": {
            "type": "string",
            "example": "Likely legit"
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "summary": {
            "type": "string"
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "lastCheckedAt": {
            "type": "string",
            "format": "date-time"
          },
          "stale": {
            "type": "boolean",
            "description": "True when older than the staleness threshold (30 days by default)"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "text": {
                  "type": "string"
                },
                "weight": {
                  "type": "string",
                  "enum": [
                    "positive",
                    "negative"
                  ]
                },
                "evidenceKeys": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "evidence": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Evidence"
            }
          },
          "history": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "checkedAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "score": {
                  "type": "integer"
                },
                "verdict": {
                  "type": "string"
                }
              }
            }
          },
          "recheckAvailableAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "aiModel": {
            "type": [
              "string",
              "null"
            ]
          },
          "disclaimer": {
            "type": "string"
          },
          "linkCheck": {
            "$ref": "#/components/schemas/LinkCheck"
          }
        }
      },
      "Pending": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "pending"
          },
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "domain": {
            "type": "string"
          },
          "step": {
            "type": [
              "string",
              "null"
            ]
          },
          "completedSteps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "pollUrl": {
            "type": "string",
            "format": "uri"
          },
          "resultUrl": {
            "type": "string",
            "format": "uri"
          },
          "linkCheck": {
            "$ref": "#/components/schemas/LinkCheck"
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "domain": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "completed",
              "failed"
            ]
          },
          "step": {
            "type": [
              "string",
              "null"
            ]
          },
          "completedSteps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "checkId": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/check": {
      "get": {
        "operationId": "getCheck",
        "summary": "Get the latest result for a domain (and optionally check an exact link)",
        "description": "Returns the latest stored result. Does not start a new check unless `fresh=1` is given with an API key; then a check is started if there is no result or it is stale, and the request waits up to ~25 s before answering 202 with a job to poll. Pass either `domain` or `url`. When the input is a full link with a path or query (e.g. a file on raw.githubusercontent.com), the response also contains `linkCheck`: that exact link looked up in the local blacklist feeds and Google Safe Browsing / Web Risk (when configured). The link itself is never stored.",
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Domain or URL, e.g. example.com (required unless url is given)"
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 2048
            },
            "description": "A full http(s) link to check exactly, e.g. https://raw.githubusercontent.com/user/repo/main/install.sh (takes precedence over domain)"
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Start a check if missing or stale (API key required)"
          }
        ],
        "responses": {
          "200": {
            "description": "Result",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix time when the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckResult"
                }
              }
            }
          },
          "202": {
            "description": "Check started but not finished yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pending"
                }
              }
            }
          },
          "400": {
            "description": "Invalid domain or url",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Domain not checked yet (linkCheck is still included when a full link was given)",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "linkCheck": {
                          "$ref": "#/components/schemas/LinkCheck"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/checks/{jobId}": {
      "get": {
        "operationId": "getJob",
        "summary": "Poll a running check",
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "404": {
            "description": "Unknown job",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Overview for AI agents, including the MCP endpoint",
    "url": "https://issitelegit.com/llms.txt"
  }
}