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
| Endpoint | What you get |
|---|---|
GET /scan | Starts or reuses a scan and returns a short summary when it’s ready. |
GET /summary | Headline facts, the four scores and every failing check with advice. |
GET /result | Everything 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 /compare | Two sites side by side: ?a=stripe.com&b=github.com. |
GET /status | Is the service healthy? Queue, success rate and scan times. |
GET /health | A quick up-or-down check. |
Every scan endpoint also accepts POST with a JSON body such as {"url": "stripe.com", "fresh": true}.
Parameters
| Name | Meaning |
|---|---|
url | Required. A public address like stripe.com or https://example.com/pricing. Only ports 80 and 443, no IP addresses or private networks. |
fresh | true to scan again instead of using a result from the last 24 hours. A result less than 10 minutes old is reused anyway. |
wait | Seconds 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."}
| Code | Status | Meaning |
|---|---|---|
invalid_url, scheme, ip, port, private | 400 | The address can’t be scanned. |
optout | 403 | The site’s owner asked not to be scanned. |
dns, not_html, empty | 422 | No site there, not a web page, or nothing visible. |
rate_limited | 429 | Too many new scans or requests. See limits below. |
connect, blocked, http_error | 502 | The site didn’t answer, blocked the scan or returned an error. |
busy | 503 | Too many scans are queued. Try again in a minute. |
timeout | 504 | The page took too long to load. |
failed | 500 | Something broke on our side. |
Limits
- Each IP address can start 20 new scans an hour. Results that are already stored don’t count and are unlimited.
- About 30 API requests a minute per IP address, with short bursts allowed. Going over returns
429. - Every response has
X-RateLimit-LimitandX-RateLimit-Remainingheaders for new scans, andX-RateLimit-Resetonce you hit the limit. - CORS is open, so you can call the API straight from a web page.
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.