{
  "openapi": "3.1.0",
  "info": {
    "title": "vibeship quick check API",
    "version": "1.0.0",
    "summary": "Run vibeship's free quick check of a website and read the result.",
    "description": "The free quick check looks at a public website from the outside: sixteen checks of safety, speed, findability and trust, graded A to F, in about one to two minutes. No sign-in and no key.\n\nFor assistants and agents, use the /v1/free-checks endpoints: they return a plain-language report shaped for a model (grade, score and scope, what blocks production, what to do next, what is in place, one line per check, the unlock offer and the disclaimer). /v1/audits is the lower-level API the vibeship.eu report page itself uses.\n\nThe free result is locked: it carries the grade, the counts per area and one line per check. What exactly was found, where it sits and how to fix it are on the report page, behind a one-off unlock or a subscription. Every result carries a disclaimer that must be shown to the user: scans are best effort and do not replace a security firm.\n\nChecks started here are passive: fifteen of the sixteen checks, everything but the break-in test on the site's forms and links, which only the site's owner starts on vibeship.eu. Only check a site you own or are allowed to have checked. Requests are rate limited per address, per client and per site; a 429 answer carries Retry-After.\n\nThe same tools are available over MCP at https://vibeship.eu/mcp — see https://vibeship.eu/developers.html.",
    "termsOfService": "https://vibeship.eu/terms.html",
    "contact": {
      "name": "vibeship",
      "url": "https://vibeship.eu/contact.html",
      "email": "support@vibeship.eu"
    }
  },
  "externalDocs": {
    "description": "Developer guide: MCP set-up for Claude, Cursor, VS Code and ChatGPT",
    "url": "https://vibeship.eu/developers.html"
  },
  "servers": [
    {
      "url": "https://vibeship.eu"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "Quick check for agents",
      "description": "Start a quick check and read a model-friendly report."
    },
    {
      "name": "Audit API",
      "description": "The raw audit endpoints behind the vibeship.eu report page."
    }
  ],
  "paths": {
    "/v1/free-checks": {
      "post": {
        "tags": [
          "Quick check for agents"
        ],
        "operationId": "startFreeCheck",
        "summary": "Start a free quick check of a website",
        "description": "Starts the passive free quick check (no break-in test) of a public website and waits up to wait_seconds for the result. 200 with the report when finished; 202 with the audit_id while it runs: then call getFreeCheckReport. The same address asked for again within ten minutes returns the same run.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "The website, e.g. https://example.com or example.com",
                    "examples": [
                      "https://example.com"
                    ]
                  },
                  "wait_seconds": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100,
                    "default": 25,
                    "description": "How long to wait for the result before answering 202."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The finished report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FreeCheckReport"
                }
              }
            }
          },
          "202": {
            "description": "Still running. Call getFreeCheckReport with audit_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FreeCheckReport"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/free-checks/{audit_id}": {
      "get": {
        "tags": [
          "Quick check for agents"
        ],
        "operationId": "getFreeCheckReport",
        "summary": "Get the report of a quick check",
        "description": "The report of a quick check: status and progress while it runs, then grade, score, scope, counts per area, one plain-language line per check, the report link, the unlock offer and the disclaimer. Optionally waits up to wait_seconds for a running check to finish.",
        "parameters": [
          {
            "$ref": "#/components/parameters/AuditID"
          },
          {
            "name": "wait_seconds",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100,
              "default": 0
            },
            "description": "Wait up to this long for a running check to finish."
          }
        ],
        "responses": {
          "200": {
            "description": "Finished: graded, or explained when the site could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FreeCheckReport"
                }
              }
            }
          },
          "202": {
            "description": "Still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FreeCheckReport"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/free-checks/{audit_id}/checks/{check}": {
      "get": {
        "tags": [
          "Quick check for agents"
        ],
        "operationId": "explainCheck",
        "summary": "Explain one check and how it is usually fixed",
        "description": "What one check means, how this run came out on it, and how that kind of problem is usually fixed, with concrete steps for the stack named. On a locked run the site-specific details stay locked and the answer carries the unlock link.",
        "parameters": [
          {
            "$ref": "#/components/parameters/AuditID"
          },
          {
            "name": "check",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/CheckName"
            }
          },
          {
            "name": "stack",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/Stack"
            },
            "description": "The stack the site is built on. Omit for every stack that has steps."
          }
        ],
        "responses": {
          "200": {
            "description": "The explanation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckExplanation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/audits": {
      "post": {
        "tags": [
          "Audit API"
        ],
        "operationId": "createAudit",
        "summary": "Create a quick check (raw)",
        "description": "Starts a quick check and answers at once with its id. Read it with getAudit or follow it with auditEvents. Agents should prefer startFreeCheck, which returns a ready-to-read report.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "An http or https address with a public host."
                  },
                  "passive": {
                    "type": "boolean",
                    "default": false,
                    "description": "Leave out the break-in test (the one check that sends attack payloads). The agent endpoints always do."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "locked"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "locked": {
                      "type": "boolean",
                      "description": "Whether the caller will see the locked view."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/v1/audits/{id}": {
      "get": {
        "tags": [
          "Audit API"
        ],
        "operationId": "getAudit",
        "summary": "Get a quick check (raw)",
        "description": "The run as stored: status and one result per check. Unless the run was bought, check data is reduced to counts and severities (locked).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Audit"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/audits/{id}/events": {
      "get": {
        "tags": [
          "Audit API"
        ],
        "operationId": "auditEvents",
        "summary": "Follow a quick check as it runs (server-sent events)",
        "description": "A text/event-stream: {\"event\":\"ready\"} first, then one CheckResult per check as it lands (locked view, as getAudit), then {\"event\":\"end\"} when the run is finished. Not usable from ChatGPT actions; poll getFreeCheckReport there instead.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The event stream.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "AuditID": {
        "name": "audit_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{6,64}$"
        },
        "description": "The audit_id startFreeCheck returned."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request was refused, e.g. an address that is private, carries a password or uses an unusual port.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No quick check with this id.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests or runs. Wait for Retry-After seconds.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "CheckName": {
        "type": "string",
        "enum": [
          "https",
          "php2shell",
          "lighthouse-perf",
          "lighthouse-extras",
          "security-headers",
          "seo-meta",
          "favicons",
          "mobile-viewport",
          "exposed-surfaces",
          "browser-secrets",
          "prod-cleanliness",
          "dns-basics",
          "robots-sitemap",
          "owasp-passive",
          "business-info",
          "owasp-active"
        ]
      },
      "Stack": {
        "type": "string",
        "enum": [
          "nextjs-vercel",
          "supabase",
          "netlify",
          "cloudflare",
          "firebase",
          "wordpress"
        ]
      },
      "Severity": {
        "type": "string",
        "enum": [
          "bad",
          "warn",
          "ok",
          "failed",
          "not_run",
          "pending"
        ],
        "description": "bad blocks production, warn is what to do next, ok is in place, not_run is the break-in test a passive check leaves out."
      },
      "Disclaimer": {
        "type": "object",
        "required": [
          "text",
          "terms_url"
        ],
        "description": "Must be shown to the user with every result.",
        "properties": {
          "text": {
            "type": "string"
          },
          "terms_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Unlock": {
        "type": "object",
        "description": "How the locked details are opened. Always on vibeship.eu, never through the API.",
        "properties": {
          "how": {
            "type": "string"
          },
          "report_url": {
            "type": "string",
            "format": "uri"
          },
          "one_off": {
            "$ref": "#/components/schemas/Offer"
          },
          "subscription": {
            "$ref": "#/components/schemas/Offer"
          },
          "difference": {
            "type": "string"
          },
          "pricing_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Offer": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "price": {
            "type": "string"
          },
          "includes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "refund": {
            "type": "string"
          }
        }
      },
      "Counts": {
        "type": "object",
        "properties": {
          "blocks_production": {
            "type": "integer"
          },
          "do_next": {
            "type": "integer"
          },
          "in_place": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          }
        }
      },
      "CheckLine": {
        "type": "object",
        "required": [
          "check",
          "label",
          "area",
          "severity",
          "summary"
        ],
        "properties": {
          "check": {
            "$ref": "#/components/schemas/CheckName"
          },
          "label": {
            "type": "string"
          },
          "area": {
            "type": "string",
            "enum": [
              "Safety",
              "Speed",
              "Getting found",
              "Trust"
            ]
          },
          "severity": {
            "$ref": "#/components/schemas/Severity"
          },
          "readiness": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "blocks_production",
              "do_next",
              "in_place",
              null
            ]
          },
          "summary": {
            "type": "string",
            "description": "One plain-language line on how this area came out."
          },
          "details": {
            "$ref": "#/components/schemas/CheckDetails"
          }
        }
      },
      "CheckDetails": {
        "type": "object",
        "description": "Present only when the details are not locked: a bought run, or a check shown to everyone.",
        "properties": {
          "title": {
            "type": "string"
          },
          "explanation": {
            "type": "string"
          },
          "why_it_matters": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "severity": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "where": {
                  "type": "string"
                },
                "fix": {
                  "type": "string"
                },
                "detail": {
                  "type": "string"
                }
              }
            }
          },
          "fix_steps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "FreeCheckReport": {
        "type": "object",
        "required": [
          "audit_id",
          "url",
          "status",
          "report_url",
          "locked",
          "disclaimer"
        ],
        "properties": {
          "audit_id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "host": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "const": "quick_check"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "completed",
              "unreachable",
              "timed_out"
            ]
          },
          "progress": {
            "type": "object",
            "properties": {
              "checks_done": {
                "type": "integer"
              },
              "checks_total": {
                "type": "integer"
              }
            }
          },
          "report_url": {
            "type": "string",
            "format": "uri",
            "description": "The full report on vibeship.eu, where the lock is lifted."
          },
          "scope": {
            "type": "string",
            "description": "What the grade covers. Show it with the grade."
          },
          "locked": {
            "type": "boolean"
          },
          "grade": {
            "type": "object",
            "properties": {
              "letter": {
                "type": "string"
              },
              "score": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              "headline": {
                "type": "string"
              }
            }
          },
          "verdict": {
            "type": "string"
          },
          "headline": {
            "type": "string"
          },
          "summary": {
            "$ref": "#/components/schemas/Counts"
          },
          "areas": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Counts"
                },
                {
                  "type": "object",
                  "properties": {
                    "area": {
                      "type": "string"
                    }
                  }
                }
              ]
            }
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckLine"
            }
          },
          "locked_note": {
            "type": "string"
          },
          "unlock": {
            "$ref": "#/components/schemas/Unlock"
          },
          "next_step": {
            "type": "object",
            "properties": {
              "heading": {
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "not_run": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "unreachable": {
            "type": "object",
            "description": "Present when the site did not answer our scanner. There is no grade then.",
            "properties": {
              "heading": {
                "type": "string"
              },
              "explanation": {
                "type": "string"
              },
              "steps": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "timed_out": {
            "type": "object",
            "properties": {
              "heading": {
                "type": "string"
              },
              "explanation": {
                "type": "string"
              }
            }
          },
          "next": {
            "type": "string",
            "description": "What to do next, for the assistant."
          },
          "note": {
            "type": "string"
          },
          "disclaimer": {
            "$ref": "#/components/schemas/Disclaimer"
          },
          "passive": {
            "type": "boolean",
            "description": "Always true here: the break-in test was not run."
          },
          "passive_note": {
            "type": "string"
          }
        }
      },
      "CheckExplanation": {
        "type": "object",
        "required": [
          "audit_id",
          "check",
          "severity",
          "result",
          "locked",
          "disclaimer"
        ],
        "properties": {
          "audit_id": {
            "type": "string"
          },
          "check": {
            "$ref": "#/components/schemas/CheckName"
          },
          "label": {
            "type": "string"
          },
          "area": {
            "type": "string"
          },
          "means": {
            "type": "string",
            "description": "What this check is about, in plain words."
          },
          "severity": {
            "type": "string"
          },
          "readiness": {
            "type": [
              "string",
              "null"
            ]
          },
          "result": {
            "type": "string"
          },
          "locked": {
            "type": "boolean"
          },
          "details": {
            "$ref": "#/components/schemas/CheckDetails"
          },
          "note": {
            "type": "string"
          },
          "how_to_fix": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "general": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "stacks": {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "steps": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "what_exactly": {
                "type": "string"
              }
            }
          },
          "unlock": {
            "$ref": "#/components/schemas/Unlock"
          },
          "report_url": {
            "type": "string",
            "format": "uri"
          },
          "disclaimer": {
            "$ref": "#/components/schemas/Disclaimer"
          }
        }
      },
      "Audit": {
        "type": "object",
        "required": [
          "id",
          "url",
          "status",
          "created_at",
          "checks",
          "locked"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "completed",
              "failed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time"
          },
          "failure_reason": {
            "type": "string",
            "enum": [
              "timed_out",
              "unreachable"
            ]
          },
          "reachability": {
            "type": "object",
            "properties": {
              "reachable": {
                "type": "boolean"
              },
              "cause": {
                "type": "string"
              },
              "status": {
                "type": "integer"
              },
              "midway": {
                "type": "boolean"
              },
              "scanner_ip": {
                "type": "string"
              }
            }
          },
          "locked": {
            "type": "boolean"
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckResult"
            }
          }
        }
      },
      "CheckResult": {
        "type": "object",
        "required": [
          "name",
          "status"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "completed",
              "failed"
            ]
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "duration_ms": {
            "type": "integer"
          },
          "data": {
            "type": "object",
            "description": "Check-specific. Reduced to counts and severities on a locked run."
          },
          "error": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "limit": {
            "type": "string"
          },
          "retry_after_seconds": {
            "type": "integer"
          }
        }
      }
    }
  }
}
