Skip to content
← Home

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.

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.

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.

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

{
  "event": "critical_regression",
  "site_id": "uuid",
  "scan_id": "uuid",
  "critical_checks": ["https_redirect", "mixed_content"]
}

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 new website to monitor

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
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.