API Documentation

Use the API to read your sites, scans, and findings. Trigger scans and get alerts when things change. Works on Pro and Studio plans.

All calls use JSON. Each response has a request ID you can use for support. Set up takes less than a minute.

Quickstart

Get your first scan result in 60 seconds. Create an API key in Settings, then run these three commands:

1. List your sites

curl -H "Authorization: Bearer sc_live_YOUR_KEY" \
  https://sitecurl.com/api/v1/sites

2. Trigger a scan

curl -X POST \
  -H "Authorization: Bearer sc_live_YOUR_KEY" \
  https://sitecurl.com/api/v1/sites/SITE_ID/scans

3. Get findings

curl -H "Authorization: Bearer sc_live_YOUR_KEY" \
  https://sitecurl.com/api/v1/scans/SCAN_ID/findings

Every response has X-Request-ID and X-RateLimit-* headers so you can track usage.

Authentication

Every request needs a Bearer token in the Authorization header:

Authorization: Bearer sc_live_...

Create your API key in Settings. Open the API tab to get started.

Short-lived access tokens

For scripts and agents, trade your API key for a token that expires in one hour. Keep the key in a secret manager and send only the token on each request:

curl -X POST https://sitecurl.com/api/v1/tokens \
  -H "Authorization: Bearer sc_live_YOUR_KEY"

{ "access_token": "sc_tok_...", "token_type": "Bearer", "expires_in": 3600 }

Use sc_tok_ exactly like a key on any endpoint. It carries the same account access and rate limit as the key that created it.

Two things to know. An access token cannot create another access token, so a leaked one expires within the hour. Revoking the key immediately invalidates every token it created, and that is the only way to revoke one, since tokens cannot be revoked individually.

Rate limits

Plan Requests per hour
Pro 100
Studio 1,000

Each response includes these headers:

  • X-Request-ID: a unique ID for each request (send your own or we make one)
  • X-RateLimit-Limit: max requests per hour
  • X-RateLimit-Remaining: how many you have left
  • X-RateLimit-Reset: when the window resets (Unix time)
  • Retry-After: seconds to wait (only on 429 responses)

Sites

List sites

GET /api/v1/sites

Returns all sites on your account.

Pass page (default 1) and per_page (default 25, max 100) to paginate.

{
  "data": [
    {
      "id": "uuid",
      "name": "example.com",
      "url": "https://example.com/",
      "status": "active",
      "scan_frequency": "weekly",
      "last_scanned_at": "2026-03-18T12:00:00Z",
      "created_at": "2026-01-15T10:30:00Z"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "has_more": false }
}

Get site

GET /api/v1/sites/:id

Returns one site and its latest scan.

Scans

List scans

GET /api/v1/sites/:site_id/scans

Get scan

GET /api/v1/scans/:id

Response fields:

  • id, site_id, status, score
  • checks_total, passed_count, critical_count, warning_count
  • pages_scanned, delta_fixed_count, delta_regressed_count
  • started_at, completed_at

Trigger scan

POST /api/v1/sites/:site_id/scans

Returns 202. The scan runs in the background. Use webhooks or poll the scan endpoint to know when it is done.

Returns 409 if a scan is still running. Returns 429 if you hit the daily cap.

Findings

List findings for a scan

GET /api/v1/scans/:scan_id/findings

Optional filters:

  • category: one of the API filter keys listed below
API key Category
seoSEO
performancespeed
securitysecurity
accessibilityaccessibility
technicaltechnical health
availabilityuptime
ai_search_readinessAI readiness
  • severity: critical, warning, info
  • passed: true or false
  • check_key: filter by specific check key
  • page_url: filter by page URL
  • page (default 1), per_page (default 50, max 200)

Each finding has its category, severity, message, fix, page URL, and proof.

Reports

Get report

GET /api/v1/reports/:token

Returns the report token, site, scan, and expiry date.

Intelligence

Trends and changes you can only see over time.

Score trends

GET /api/v1/sites/:id/intelligence/trends?limit=20

Returns past scores, oldest first.

Regressions

GET /api/v1/sites/:id/intelligence/regressions

Checks that broke since the last scan, plus new issues.

Improvements

GET /api/v1/sites/:id/intelligence/improvements

Checks that were fixed since the last scan.

Priority inbox

GET /api/v1/intelligence/priority

Your top 5 action items across all sites. Covers score drops, new issues, quick wins, and stale scans.

Lead-capture scans

Studio plan only. Scan an arbitrary URL and email a whitelabeled "top 5 issues" partial report to a recipient, built for a lead-capture form on your own site, not the SiteCurl dashboard. No site is added to your account. Any form builder, plugin or automation tool that can send an authenticated POST request with your API key can call it.

POST /api/v1/lead_scans
{ "url": "https://...", "recipient_email": "[email protected]" }

Set the report's call-to-action link under Settings › Report branding › "Lead-capture report link": it points at your own site, not a SiteCurl-hosted report, since the point of a partial report is that the full findings stay yours to deliver. A duplicate (url, recipient_email) submission within 10 minutes returns the original scan instead of running again.

Example request

curl -X POST \
  -H "Authorization: Bearer sc_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","recipient_email":"[email protected]"}' \
  https://sitecurl.com/api/v1/lead_scans

The API answers 202 with a scan id right away and emails the report when the scan finishes. It accepts a JSON body or a form-encoded one, and a bare domain like example.com works as well as a full URL. If the site can't be scanned (unreachable, blocked, or a private address), the visitor gets no email. Instead you get an email with their address and the reason, so you can follow up yourself. Turn it off under Settings, Notifications.

Connecting a form

Any tool that can send an authenticated POST works. In the WPForms Webhooks add-on, choose POST and the JSON format, add a request header Authorization with the value Bearer sc_live_YOUR_KEY, and map url and recipient_email in the request body to your form's website and email fields. In Webhooks by Zapier, use a POST action with the JSON payload type and the same header and keys.

Webhooks

Endpoints

Method Path
GET /api/v1/webhook_endpoints
POST /api/v1/webhook_endpoints
PATCH /api/v1/webhook_endpoints/:id
DELETE /api/v1/webhook_endpoints/:id

Create request body

{ "url": "https://...", "events": ["scan_completed"] }

Available events: scan_completed, score_dropped, critical_regression, site_unreachable.

Payload headers

  • X-SiteCurl-Event: event type
  • X-SiteCurl-Signature: HMAC-SHA256 signature (v1=<hex>)
  • X-SiteCurl-Timestamp: Unix timestamp

To check it: sign "#{timestamp}.#{body}" with your secret key.

Signature verification examples

Ruby

timestamp = request.headers["X-SiteCurl-Timestamp"]
signature = request.headers["X-SiteCurl-Signature"]
body = request.body.read

expected = "v1=" + OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{body}")
unless ActiveSupport::SecurityUtils.secure_compare(expected, signature)
  head :unauthorized and return
end

Node.js

const crypto = require("crypto");

function verify(secret, timestamp, body, signature) {
  const expected = "v1=" + crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${body}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(signature)
  );
}

Python

import hmac, hashlib

def verify(secret, timestamp, body, signature):
    expected = "v1=" + hmac.new(
        secret.encode(), f"{timestamp}.{body}".encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Example: scan_completed

{
  "event": "scan_completed",
  "site_id": "uuid",
  "scan_id": "uuid",
  "score": 85,
  "checks_total": 80,
  "passed_count": 68,
  "critical_count": 2,
  "warning_count": 10,
  "report_token": "abc123..."
}

Example: score_dropped

{
  "event": "score_dropped",
  "site_id": "uuid",
  "scan_id": "uuid",
  "current_score": 72,
  "previous_score": 85,
  "drop": 13
}

Example: critical_regression

Fires on a new critical OR warning-severity regression. critical_checks stays critical-only for backward compatibility; severities carries every flagged check with its actual severity.

{
  "event": "critical_regression",
  "site_id": "uuid",
  "scan_id": "uuid",
  "critical_checks": ["https_redirect"],
  "severities": [
    { "check_key": "https_redirect", "severity": "critical" },
    { "check_key": "meta_description", "severity": "warning" }
  ]
}

Example: site_unreachable

Sent once per outage, when a scheduled scan could not reach the site and a second check 15 minutes later could not reach it either.

{
  "event": "site_unreachable",
  "site_id": "uuid",
  "scan_id": "uuid",
  "url": "https://example.com",
  "cause": "dns"
}

cause is one of dns, connection, timeout or tls.

MCP (AI tools)

Connect AI coding tools like Claude Code, Cursor, or Windsurf to SiteCurl. Uses the same API key.

Endpoint

POST /api/v1/mcp

Speaks JSON-RPC 2.0. Three methods: initialize, tools/list, tools/call. Supports Streamable HTTP transport: send Accept: text/event-stream to receive SSE-formatted responses.

Setup

Add to your Claude Code or Cursor settings:

{
  "mcpServers": {
    "sitecurl": {
      "url": "https://sitecurl.com/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer sc_live_..."
      }
    }
  }
}

Available tools

Tool Description
sitecurl_list_sites List all monitored websites
sitecurl_get_site Site details with latest scan
sitecurl_scan Trigger a new scan
sitecurl_scan_status Check status of latest scan
sitecurl_findings Scan findings (filterable)
sitecurl_score Current health score
sitecurl_trends Score history over time
sitecurl_priority Top action items across all sites
sitecurl_regressions Checks that broke since last scan
sitecurl_improvements Checks fixed since last scan
sitecurl_add_site Add a website. Scheduled scans default to weekly on an active Pro, Studio or trial plan and to manual otherwise; pass scan_frequency to choose
sitecurl_search_findings Search open findings across every site at once, grouped by site

CI/CD integration

Block deploys when your site has critical audit findings. Install the @sitecurl/mcp npm package and run sitecurl-check in your pipeline.

Install

npm install -g @sitecurl/mcp

Usage

sitecurl-check https://example.com --fail-on=critical

Triggers a scan, waits for it to finish, then exits with code 0 (pass) or 1 (findings above your threshold). Set the API key via the SITECURL_API_KEY environment variable.

Exit codes

Code Meaning
0No findings at or above threshold
1Findings found, check failed
2Scan failed or timed out
3Setup error (bad key, missing site)

GitHub Actions

- name: SiteCurl Audit
  run: npx @sitecurl/mcp check https://example.com --fail-on=critical
  env:
    SITECURL_API_KEY: ${{ secrets.SITECURL_API_KEY }}

GitLab CI

sitecurl-audit:
  script:
    - npx @sitecurl/mcp check https://example.com --fail-on=critical
  variables:
    SITECURL_API_KEY: $SITECURL_API_KEY

Error responses

Errors come back as JSON with a message, code, and request ID:

{
  "error": "Rate limit exceeded",
  "error_code": "RATE_LIMIT_EXCEEDED",
  "request_id": "a1b2c3d4-..."
}
Status Error code
401 INVALID_API_KEY
403 PLAN_REQUIRED
403 LONG_LIVED_KEY_REQUIRED
404 NOT_FOUND
409 SCAN_IN_PROGRESS
422 VALIDATION_ERROR
429 RATE_LIMIT_EXCEEDED
429 QUOTA_EXCEEDED

Machine-readable spec

Download the full spec at /openapi.yaml (OpenAPI 3.1.0).

Questions?

Get in touch if anything is unclear.