API reference
API reference
Section titled “API reference”Base URL: https://torstudio.ca. All endpoints speak JSON. Errors are returned as
{ "error": "human-readable message" } with an appropriate HTTP status.
Authentication
Section titled “Authentication”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.
POST /api/scan — run a scan
Section titled “POST /api/scan — run a scan”The same scan the homepage runs. No key required.
POST /api/scanContent-Type: application/json
{ "url": "example.com", "force": false }url(required): anyhttps://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/scanAuthorization: Bearer <api-key>Content-Type: application/json
{ "url": "example.com" }- Quotas: 30 scans/day on free, 500/day on Pro (per UTC day).
429with aRetry-Afterheader 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/licenseAuthorization: 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/lookupContent-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 — page preview
Section titled “GET /api/screenshot — page preview”GET /api/screenshot?url=example.comGET /api/screenshot?url=example.com&format=jsonReturns 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/emailContent-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.