stole.si Scan a site

developers

Scan API.

Everything stole.si shows on a result page is available as JSON. It’s free and needs no key or sign-up. Every endpoint takes a website address, scans it if there’s no recent result, and answers with plain JSON.

Base address: https://www.stole.si/api/v1. There’s also an OpenAPI 3.1 spec and an MCP server for AI apps.

Quick start

curl 'https://www.stole.si/api/v1/summary?url=stripe.com'

If the site was scanned in the last 24 hours you get the answer right away. If not, stole.si scans it now and waits up to 20 seconds for it to finish. When a scan takes longer, you get 202 with a Retry-After header: call again and the result will be there.

Endpoints

EndpointWhat you get
GET /scanStarts or reuses a scan and returns a short summary when it’s ready.
GET /summaryHeadline facts, the four scores and every failing check with advice.
GET /resultEverything measured, section by section. Add fields=tech,speed to get only some sections.
GET /export/{format}The raw DESIGN.md, tokens.json, tokens.css or tailwind.config.js file.
GET /compareTwo sites side by side: ?a=stripe.com&b=github.com.
GET /statusIs the service healthy? Queue, success rate and scan times.
GET /healthA quick up-or-down check.

Every scan endpoint also accepts POST with a JSON body such as {"url": "stripe.com", "fresh": true}.

Parameters

NameMeaning
urlRequired. A public address like stripe.com or https://example.com/pricing. Only ports 80 and 443, no IP addresses or private networks.
freshtrue to scan again instead of using a result from the last 24 hours. A result less than 10 minutes old is reused anyway.
waitSeconds to wait for a scan before answering 202, from 0 to 25. Default 20.
fields/result only. Comma-separated sections: page, tech, speed, network, security, seo, accessibility, content, colors, typography, spacing, radius, shadows, breakpoints, container, components, variables, notes.

Example

curl 'https://www.stole.si/api/v1/result?url=github.com&fields=speed,security'
{
  "ok": true,
  "site": "github.com",
  "scanned_at": "2026-10-11T09:12:44Z",
  "age_seconds": 312,
  "refreshing": false,
  "schema": 2,
  "result": {
    "speed": {
      "ttfb_ms": 212,
      "fcp_ms": 604,
      "bytes": 1840221,
      "requests": 96,
      "checks": [
        {"id": "ttfb", "label": "The server answers quickly", "ok": true, "value": "212 ms", "why": "..."}
      ],
      "score": {"passed": 6, "failed": 2, "total": 8}
    },
    "security": { ... }
  }
}

Every check has the same shape: ok is true when it passes, false when it fails and null when it couldn’t be measured or doesn’t apply. value is what was measured and why explains it in plain words.

While a scan runs

HTTP/1.1 202 Accepted
Retry-After: 3

{"ok": true, "pending": true, "site": "example.com", "state": "scanning", "position": 0, "seconds": 21.4, "retry_after": 3}

Errors

Errors come back as JSON with a stable error code and a sentence you can show to people:

{"ok": false, "error": "dns", "title": "We couldn’t find that site", "message": "No website answers at that address. Check the spelling and try again."}
CodeStatusMeaning
invalid_url, scheme, ip, port, private400The address can’t be scanned.
optout403The site’s owner asked not to be scanned.
dns, not_html, empty422No site there, not a web page, or nothing visible.
rate_limited429Too many new scans or requests. See limits below.
connect, blocked, http_error502The site didn’t answer, blocked the scan or returned an error.
busy503Too many scans are queued. Try again in a minute.
timeout504The page took too long to load.
failed500Something broke on our side.

Limits

Need more? Get in touch.

How results are measured

Each scan opens the page once in a real Chrome browser from stole.si’s server, with an honest user agent. Images and video aren’t downloaded, so page weight and timings leave them out. Speed numbers are a single measurement from one place, not an average of real visitors. The accessibility checks are quick automated checks, not a full audit.