HTTP endpoint
Call the AI Site Scanner from your own code with GET or POST /api/ai-scan: parameters, the JSON response and ScanResult type, error codes and rate limits.
The scanner page, the command line and the MCP server all call one endpoint, https://toolz.dev/api/ai-scan, and you can call it too. Send a URL with GET or POST, and it returns { "success": true, "data": <ScanResult>, "reportId": "<id>" }: the full scan result as JSON and the id of the stored report.
Unversioned. This endpoint has no version number and no API key. It is the scanner's own endpoint, documented so you can use it, and its parameters and response may change until a versioned API ships. If you build on it, read fields defensively and expect to adjust your code when it changes. For a CI check, the command line already handles the response and the exit codes for you.
How do I call it?
With GET and query parameters:
curl "https://toolz.dev/api/ai-scan?url=example.com"
curl "https://toolz.dev/api/ai-scan?url=example.com&mode=site&siteType=content"Or with POST and a JSON body:
curl -X POST https://toolz.dev/api/ai-scan \
-H "Content-Type: application/json" \
-d '{"url": "example.com", "mode": "site", "siteType": "content", "checks": ["llms-txt", "json-ld"]}'Both run the same scan. Each request fetches the target site live; results are never cached.
What parameters does it take?
| Parameter | GET | POST body | Values | Default |
|---|---|---|---|---|
url |
Query | String | A domain or page URL. https:// is added when there is no scheme. At most 2000 characters |
Required |
mode |
Query | String | site grades up to 5 pages from the sitemap as well. Anything else is a page scan |
Page scan |
siteType |
Query | String | content or api. Anything else means all checks |
all |
checks |
Query, repeated (checks=a&checks=b) or comma-separated (checks=a,b) |
Array of strings | Check ids from What the scan checks. Unknown ids are dropped. If none are left, every check runs | Every check for the siteType |
visibility |
Query | String | public or unlisted. unlisted is a private scan and needs a Toolz Pro sign-in token |
public |
checks narrows the checks chosen by siteType; it does not add checks from another type. For a private scan, send the Pro account's token as Authorization: Bearer <token>. Public and private scans, limits and Pro explains how that is checked.
What does a successful response look like?
HTTP 200 with:
{
"success": true,
"data": {
"url": "https://example.com/",
"domain": "example.com",
"scannedAt": "2026-09-23T10:00:00.000Z",
"fetchOk": true,
"ttfbMs": 212,
"platform": { "name": "Next.js", "confidence": 0.9, "evidence": ["..."] },
"overallScore": 84,
"grade": "B",
"categories": [
{ "key": "content", "label": "Content Accessibility", "score": 69, "weight": 48, "passed": ["..."], "issues": ["..."] }
],
"topIssues": ["..."],
"checks": ["..."],
"siteType": "content",
"mode": "page"
},
"reportId": "a1B2c3D4e5F6"
}The values above are illustrative. reportId is null when the result could not be stored; the scan result is complete either way. The report page for an id is https://toolz.dev/ai-scan/r/<reportId>.
A site that could not be loaded still returns HTTP 200 with success: true, but with fetchOk: false, a fetchError message, a score of 0, grade F, empty arrays and reportId: null. Check data.fetchOk before reading the score. Troubleshooting lists the fetchError messages.
What is in ScanResult?
These are the TypeScript types the endpoint returns, from the scanner's source:
interface ScanResult {
url: string // final URL after redirects
domain: string
scannedAt: string // ISO 8601
fetchOk: boolean
fetchError?: string // only when fetchOk is false
ttfbMs: number
platform: Platform
overallScore: number // 0-100
grade: string // "A+" .. "F"
categories: CategoryResult[]
topIssues: Issue[] // up to 6, most severe first
checks: CheckResult[] // every check that ran
siteType: 'content' | 'api' | 'all'
mode?: 'page' | 'site'
pages?: PageResult[] // whole-site scans only, worst first
}
interface CheckResult {
id: string // e.g. "llms-txt"
category: 'discoverability' | 'content' | 'botAccess' | 'protocol' | 'commerce'
label: string
description: string
weight: number
status: 'pass' | 'fail' | 'na'
detail: string // what was found
fixPrompt: string
docs?: string // reference URL
}
interface CategoryResult {
key: string
label: string
score: number // 0-100
weight: number // total weight of the evaluated checks
passed: string[] // labels of passing checks
issues: Issue[] // failing checks
}
interface Issue {
id: string
title: string
severity: 'critical' | 'warning' | 'info'
detail: string
fixPrompt: string
docs?: string
}
interface Platform {
name: string
confidence: number // 0-1
evidence: string[]
}
interface PageResult {
url: string
score: number
grade: string
failed: number // failing checks on that page
}checks is the complete list and the one to use in code: every check that ran, passed or failed, with its fix prompt. topIssues is the short list the scanner page shows. Scores, grades and scan filters explains how score, weight and severity are derived.
How do I authenticate?
The endpoint is free and needs no key for a single public page. The paid modes do need one: mode=site grades up to five pages from the sitemap and visibility=unlisted keeps the report out of the public directory, and both are part of Toolz Pro. Issue a key from the dashboard - key creation is part of Advanced, the tier that carries API access - and send it as a bearer token:
curl -H "Authorization: Bearer tz_live_..." \
"https://toolz.dev/api/ai-scan?url=example.com&mode=site"A key is shown once, when it is created, and stored only as a hash - a lost key is revoked and replaced, not recovered. Revoking takes effect on the next call, and so does a plan change: the tier is read from the account at call time, not copied onto the key when it was issued.
Without a key, or with one whose plan does not cover the mode, the endpoint answers 403 with code: "PRO_REQUIRED" and a message naming what is missing.
What errors can it return?
Errors have success: false, an error message meant for people, and sometimes a code:
| Status | error |
When |
|---|---|---|
| 400 | Missing url parameter |
No url was sent |
| 400 | Invalid JSON body |
A POST body that is not JSON |
| 400 | A URL message, for example That address is not publicly reachable. |
The URL is invalid, not http or https, contains a username or password, or points at a private or local address |
| 403 | Private scans are part of Toolz Pro. Run it as a public scan, or upgrade at toolz.dev/pricing. with code: "PRO_REQUIRED" |
visibility=unlisted without a Pro sign-in |
| 429 | Too many scans. Wait a minute and try again. |
More than 6 scans from your IP address in 60 seconds |
| 502 | The scan could not be completed. The site may be unreachable. |
The scan failed unexpectedly on the server |
Branch on the status and code, not on the message text, which may be reworded. The full list of URL messages is in Troubleshooting.
Are there limits?
The endpoint allows 6 scans per IP address in any 60-second window, counting refused requests too. There is no API key and no separate quota. Scans made through it are public unless a Pro token is sent with visibility=unlisted, and each one that loads the site is stored as a report. What the scanner fetches from the target is in Privacy and what gets fetched.