Documentation

SCAND.Ai is an independent AI industry watchdog. This guide covers everything you need to integrate with our API.

Getting Started

  1. Create a free account to get your API key
  2. Add the X-API-Key header to your requests
  3. Explore the interactive API reference (Swagger UI)
curl -H "X-API-Key: sk_your_key_here" https://scand.ai/api/topics

Authentication

All API requests accept an optional X-API-Key header. Without a key, you're limited to 30 requests per day. With a free key, 100/day. See pricing for higher limits.

X-API-Key: sk_your_key_here

API Endpoints

Base URL: https://scand.ai/api

Topics

MethodPathDescription
GET/topicsList active controversies (up to 50)
GET/topics/:slugTopic detail with parties and noise breakdown

Stars

MethodPathDescription
GET/starsList tracked AI figures
GET/stars/:slugStar detail with identities and controversies

Activity & Stats

MethodPathDescription
GET/activityStar activity feed. Filter: ?star_id=N
GET/pulseLive platform stats: active, breaking, hottest

Export (Pro/Enterprise)

MethodPathDescription
GET/export/topicsFull dataset (NDJSON)
GET/export/statsAggregate statistics

Real-time (WebSocket)

Connect via WebSocket at wss://scand.ai/api/ws to receive live events.

Event format: Each message is a JSON object with type and data fields:

// noise_update — noise level changed for a topic
{ "type": "noise_update", "data": { "slug": "openai-safety-team", "noise_level": 72.5, "previous": 65.1, "state": "controversy" } }

// state_change — topic transitioned to a new state
{ "type": "state_change", "data": { "slug": "openai-safety-team", "from": "spike", "to": "controversy", "noise_level": 72.5 } }

// new_topic — a new controversy was detected
{ "type": "new_topic", "data": { "slug": "meta-ai-labor", "title": "Meta AI Team Labor Dispute", "state": "trending", "noise_level": 35.0 } }

Connection example:

const ws = new WebSocket('wss://scand.ai/api/ws');
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  console.log(msg.type, msg.data);
};

Alerts

MethodPathDescription
GET/alertsList your alert preferences
POST/alertsCreate an alert: { "email": "...", "frequency": "daily", "min_noise": 50, "categories": ["safety"] }
DELETE/alerts/:idDelete an alert preference

Error Responses

All errors return a JSON object with an error field. Common HTTP status codes:

StatusMeaningExample
400Bad Request{ "error": "missing url or events" }
401Unauthorized{ "error": "invalid_api_key" }
403Forbidden{ "error": "pro_or_enterprise_required", "hint": "Upgrade at scand.ai/pricing" }
404Not Found{ "error": "not_found" }
429Rate Limited{ "error": "rate_limit_exceeded", "remaining_day": 0, "remaining_month": 150 }

Rate-limited responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix timestamp) headers.

Webhooks

Pro and Enterprise users can register webhook endpoints to receive push notifications.

Events

  • topic.created — new controversy detected
  • topic.state_change — state transition (trending → spike → controversy → scandal)
  • noise.spike — noise level jumped significantly
  • content.breaking — breaking content from S-tier source

Security

Each webhook gets a unique signing secret. Verify payloads using the X-Scandal-Signature header (HMAC-SHA256).

POST /api/webhooks
{
  "url": "https://your-app.com/webhook",
  "events": ["topic.state_change", "noise.spike"]
}
// Response: { "id": 1, "secret": "your-signing-secret" }

Signature Verification

Verify the X-Scandal-Signature header to ensure payloads are authentic.

Node.js:

const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express example
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
  const sig = req.headers['x-scandal-signature'];
  if (!verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  console.log('Event:', event.type, event.data);
  res.sendStatus(200);
});

Python:

import hmac, hashlib

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

# Flask example
@app.route('/webhook', methods=['POST'])
def webhook():
    sig = request.headers.get('X-Scandal-Signature', '')
    if not verify_webhook(request.data, sig, WEBHOOK_SECRET):
        return 'Invalid signature', 401
    event = request.get_json()
    print(f"Event: {event['type']}", event['data'])
    return '', 200

Embeddable Widgets

Add controversy data to your site:

Badge (no API key required)

AI Controversies

Ticker (scrolling headlines)

Topic Card

Data Model

Topic States

Topics progress through states based on activity: trendingspikecontroversyscandalresolved

Noise Level (0-100)

Composite score from 7 factors: reach, engagement, star power, duration, cross-platform presence, polarity, and industry impact. Decays over time.

Categories

safety | ethics | ip_copyright | labor | regulation | corporate | military | other

Rate Limits

TierPriceDailyMonthlyWebhooksExport
AnonymousFree30
Free$01003,000
Pro$29/mo10,000300,0005Yes
Enterprise$99/moUnlimitedUnlimited50Yes

Changelog

  • v1.0.0 (March 2026) — Public API launch: topics, stars, activity, pulse, WebSocket, webhooks, exports, embeds