Skip to content

API reference

Base URL: https://torstudio.ca. All endpoints speak JSON. Errors are returned as { "error": "human-readable message" } with an appropriate HTTP status.

The /api/v1/* endpoints require an API key, issued in the admin console:

Authorization: Bearer <api-key>

The public /api/scan endpoint needs no key but is rate-limited per IP.

The same scan the homepage runs. No key required.

POST /api/scan
Content-Type: application/json
{ "url": "example.com", "force": false }
  • url (required): any https:// URL. Private hosts (localhost, RFC-1918) are rejected.
  • force (optional): skip the result cache and re-scan fresh.

Rate limit: 10 scans per IP per 10 minutes (429 when exceeded).

Response: the full ScanResult:

{
"url": "https://example.com/",
"host": "example.com",
"scannedAt": "2026-09-29T16:00:00.000Z",
"durationMs": 18432,
"score": 73,
"level": { "name": "Advancing", "blurb": "..." },
"groups": [
{
"id": "discovery", "title": "AI Discovery", "weight": 30,
"blurb": "Can AI systems find and understand the site?",
"score": 21.5, "passed": 9, "failed": 2, "warned": 1, "skipped": 0,
"checks": [
{
"id": "llms-txt", "group": "discovery", "title": "llms.txt exists",
"weight": 5, "critical": true,
"status": "fail", "detail": "404 Not Found",
"fix": "Add /llms.txt: a markdown summary of the site…"
}
]
}
],
"telemetry": { "ip": "...", "asn": "...", "dns": {}, "certificate": {}, "technologies": [] },
"pagespeed": { "...": "Core Web Vitals, FCP/LCP/TBT/CLS (mobile)" },
"verdict": "One-paragraph human-readable verdict.",
"fixPrompt": "Copy-paste prompt with every failure and its fix.",
"aiVerdict": { "...": "AI-written summary + fix plan, or null" },
"stackEvidence": { "...": "what the site is built on (see Stack lookup)" },
"globalAverage": 58.4
}

POST /api/v1/scan — keyed scan (integrations)

Section titled “POST /api/v1/scan — keyed scan (integrations)”

The endpoint the WordPress plugin uses. Same engine as /api/scan, with per-key daily quotas and a guaranteed verdict artifact.

POST /api/v1/scan
Authorization: Bearer <api-key>
Content-Type: application/json
{ "url": "example.com" }
  • Quotas: 30 scans/day on free, 500/day on Pro (per UTC day). 429 with a Retry-After header when the daily limit is reached.
  • The AI verdict’s missing artifacts are backfilled from deterministic templates, and a fully deterministic template verdict is used when the AI pipeline is unavailable — consumers never get a null artifact for a known check.

GET /api/v1/license — check a key’s plan

Section titled “GET /api/v1/license — check a key’s plan”

What the WordPress plugin calls on connect and daily to confirm the key’s plan.

GET /api/v1/license
Authorization: Bearer <api-key>
{ "plan": "pro", "site_url": "https://example.com", "expires": "2026-10-29T00:00:00.000Z" }

expires carries the subscription’s current-period end for Stripe-bought keys, null otherwise.

POST /api/v1/stack/lookup — “what is it built on”

Section titled “POST /api/v1/stack/lookup — “what is it built on””

Fingerprint what a site is built on — deterministic layers plus deep-scan layers (browser render + AI inference) when the fingerprint pass is thin.

POST /api/v1/stack/lookup
Content-Type: application/json
{ "url": "example.com" }

Rate limit: 20 lookups per IP per 10 minutes. Returns the stack evidence JSON (the same stackEvidence object embedded in scan results). 400 for bad input, 502 when the target can’t be fetched.

GET /api/screenshot?url=example.com
GET /api/screenshot?url=example.com&format=json

Returns a PNG screenshot of the landing page (or JSON metadata with ?format=json). 501 when screenshots are unavailable, 503 when the monthly browser-rendering budget is exhausted.

POST /api/report/email — email the report

Section titled “POST /api/report/email — email the report”

Sends the visitor’s visible scan results as an email summary (score, readiness level, group breakdown, shareable link). The fix plan and exports stay Pro-only — the email is lead capture, not a second paywall.

POST /api/report/email
Content-Type: application/json
{ "email": "owner@example.com", "result": { "...": "ScanResult from /api/scan" } }

The address is validated (format + MX/A + disposable block) and subscribed to the studio’s newsletter; the summary is only ever delivered to the inbox.