Developer Docs·API v1

Build on the click-fraud platform.

REST API and webhooks for sites, blocked IPs, and click intelligence. Bearer-token auth, HMAC-signed payloads, cursor pagination. Available on Pro and above.

Endpoints
7
Rate limit
60/min
Webhook events
7
Signature
HMAC

Quickstart#

  1. 1Open Settings → API & Webhooks in your dashboard.
  2. 2Create a token, pick the abilities you need, and copy it once — it will not be shown again.
  3. 3Send requests with Authorization: Bearer YOUR_TOKEN.
curl
curl https://api.myclickshield.com/api/v1/sites \
  -H 'Authorization: Bearer YOUR_TOKEN'

Authentication#

Every request must include a bearer token in the Authorization header.

header
Authorization: Bearer mcs_live_...

Tokens are scoped to one account, can be granted a subset of abilities, and can be set to expire after 30, 90, 180, 365 or 730 days. Lost tokens cannot be recovered — revoke and create a new one. Maximum 20 tokens per account.

Token abilities#

Each token carries a set of abilities. A request fails with 403 Forbidden if the token doesn't carry the ability the endpoint requires.

AbilityDescription
sites:readList and read sites the token owner can access
stats:readRead aggregate fraud statistics for a site
clicks:readRead individual click events
blocked-ips:readList blocked IPs on a site
blocked-ips:writeAdd and remove blocked IPs

Rate limits#

60 requests per minute per token. The standard rate-limit headers are returned on every response; over-limit requests get a 429 Too Many Requests.

  • X-RateLimit-Limit

    Window cap

  • X-RateLimit-Remaining

    Requests left

  • Retry-After

    Seconds to wait

Errors#

All errors return a JSON body of the shape { "message": "..." }.

StatusMeaning
401Missing or invalid token
402Plan does not include API access — upgrade to Pro+
403Token lacks the required ability
404Resource not found (or not owned by the caller)
422Validation failed — see errors field
429Rate limit exceeded

Endpoints

7 endpoints
GET/api/v1/sites

List sites

Returns all sites the token owner can access (their own + team sites).

Required abilitysites:read

Example
curl https://api.myclickshield.com/api/v1/sites \
  -H 'Authorization: Bearer YOUR_TOKEN'

Response · 200 OK

{
  "data": [
    {
      "id": 17,
      "name": "Acme Coffee",
      "domain": "acme-coffee.com",
      "is_active": true,
      "grace_mode": false,
      "block_threshold": 70,
      "flag_threshold": 40,
      "created_at": "2026-04-01T12:00:00+00:00"
    }
  ]
}
GET/api/v1/sites/{id}

Retrieve a site

Returns one site by ID. 404 if the token owner cannot access it.

Required abilitysites:read

Parameters

NameTypeDescription
id*integerSite ID
GET/api/v1/sites/{id}/stats

Aggregate stats

Click counts and fraud rate for a window of recent days.

Required abilitystats:read

Parameters

NameTypeDescription
daysinteger (1-90)Window size in days (default 30)

Response · 200 OK

{
  "data": {
    "site_id": 17,
    "window_days": 30,
    "total_clicks": 4128,
    "blocked_clicks": 312,
    "flagged_clicks": 87,
    "fraud_rate": 7.56
  }
}
GET/api/v1/sites/{id}/clicks

List clicks

Cursor-paginated click events. Use the returned next_cursor for the next page.

Required abilityclicks:read

Parameters

NameTypeDescription
statusenumvalid | flagged | blocked
classificationstringInternal classification label
ipstringFilter to clicks from one IP
sincedateISO-8601 lower bound on created_at
untildateISO-8601 upper bound on created_at
per_pageinteger (1-200)Page size (default 50)
cursorstringCursor returned by the previous page
GET/api/v1/sites/{id}/blocked-ips

List blocked IPs

Cursor-paginated list of IPs currently blocked on the site.

Required abilityblocked-ips:read

POST/api/v1/sites/{id}/blocked-ips

Block an IP

Idempotent — re-blocking an existing IP updates its metadata.

Required abilityblocked-ips:write

Parameters

NameTypeDescription
ip_address*IPv4/v6IP to block
reasonstringFree-text reason
typeenumpermanent | temporary
expires_atdatetimeFor temporary blocks
Example
curl -X POST https://api.myclickshield.com/api/v1/sites/17/blocked-ips \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"ip_address":"203.0.113.42","reason":"Manual review"}'
DELETE/api/v1/sites/{id}/blocked-ips/{blockedIp}

Unblock an IP

Removes the block record. Subsequent clicks from that IP will be evaluated normally.

Required abilityblocked-ips:write

Webhooks

7 events

Overview#

Subscribe to events and receive a signed POST whenever they happen — no polling required. Configure endpoints under Settings → API & Webhooks. You can have up to 10 endpoints per account.

Each delivery is a JSON POST with these headers:

  • X-MyClickShield-EventEvent name (e.g. ip.blocked)
  • X-MyClickShield-TimestampUnix timestamp in seconds
  • X-MyClickShield-SignatureHMAC-SHA256 hex digest
  • X-MyClickShield-DeliveryUnique delivery ID for idempotency
Example payload
POST /your-endpoint HTTP/1.1
Content-Type: application/json
X-MyClickShield-Event: ip.blocked
X-MyClickShield-Timestamp: 1746367200
X-MyClickShield-Signature: 9f7c4...
X-MyClickShield-Delivery: 5821

{
  "event": "ip.blocked",
  "data": {
    "site_id": 17,
    "site_domain": "acme-coffee.com",
    "ip_address": "203.0.113.42",
    "reason": "Auto-blocked due to high fraud score",
    "fraud_score": 88,
    "source": "auto"
  },
  "sent_at": "2026-05-04T12:00:00+00:00"
}

Verifying signatures#

Compute HMAC_SHA256(secret, timestamp + "." + raw_body) and compare in constant time. Reject if the signature mismatches or the timestamp is more than 5 minutes old (replay protection).

import crypto from 'crypto'

app.post('/webhooks/myclickshield', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.headers['x-myclickshield-timestamp']
  const sig = req.headers['x-myclickshield-signature']
  const body = req.body.toString()

  const expected = crypto
    .createHmac('sha256', process.env.MCS_WEBHOOK_SECRET)
    .update(ts + '.' + body)
    .digest('hex')

  if (
    sig.length !== expected.length ||
    !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig)) ||
    Math.abs(Date.now() / 1000 - Number(ts)) > 300
  ) {
    return res.status(400).send('invalid signature')
  }

  const payload = JSON.parse(body)
  // ... handle payload.event ...
  res.sendStatus(200)
})

Event catalog#

EventWhen
ip.blockedAn IP was added to a site's block list (manual, API, or auto-block)
ip.unblockedAn IP was removed from a site's block list
fraud.detectedA click was classified as fraudulent
site.createdA new site was added to the account
site.pausedAuto-pause kicked in and ad spend was paused for a site
site.resumedAuto-pause expired and ads resumed
subscription.updatedSubscription plan, status, or click limit changed

Retries & auto-disable#

Non-2xx responses and transport errors trigger up to 5 attempts with exponential back-off:

  1. immediate
  2. 30s
  3. 2m
  4. 5m
  5. 30m

After 20 consecutive failures the endpoint is auto-disabled — re-enable it from the dashboard once you've fixed your receiver. A 2xx reply confirms delivery; we don't expect any specific body.

Ready to integrate?

Generate a token in your dashboard and start with the quickstart above.

Open dashboard