Syndicate Tier

Syndicate API

Integrate Chalk signals, coverage data, and paper bets into your own tools. Webhooks push halftime alerts to your endpoint the moment a filter matches.

Authentication

Every request must include your API key in the X-API-Key header. Generate a key in Settings. One key per account — revoke or regenerate anytime.

curl https://chalk.is/api/v1/syndicate/signals \
  -H 'X-API-Key: chalk_sk_...'

All endpoints require a Syndicate subscription. Pro-tier keys receive a 403 Forbidden response.

Rate Limits

60/min

Burst limit

5,000/day

Daily quota

10 retries

Webhook delivery

Every response includes rate-limit headers:

X-RateLimit-Limit-Minute: 60
X-RateLimit-Remaining-Minute: 58
X-RateLimit-Limit-Day: 5000
X-RateLimit-Remaining-Day: 4847
X-RateLimit-Reset: 1711843200

When you hit a limit, the response is 429 Too Many Requests with a Retry-After header in seconds.

Endpoints

All paths are relative to https://chalk.is/api/v1/syndicate

GET/me

API key owner info and rate limit status

GET/signals

Today's halftime signals

GET/signals/{game_id}

Single game signal detail

GET/games

Today's games with status

GET/games/{game_id}

Game detail

GET/games/{game_id}/coverage

Cover dynamics and betting signal data

GET/games/{game_id}/lines

2H line history for a game

GET/paper-bets

List your paper bets

POST/paper-bets

Place a new paper bet

POST/paper-bets/resolve

Resolve pending bets for finished games

DELETE/paper-bets/{bet_id}

Cancel a pending paper bet

PATCH/paper-bets/{bet_id}

Update line, amount, or odds on a pending bet

GET/webhooks

Get webhook endpoint status

POST/webhooks

Register a webhook endpoint

PUT/webhooks

Update webhook URL (new signing secret)

DELETE/webhooks

Remove webhook endpoint

POST/webhooks/test

Send a test payload to your webhook

POST/webhooks/rotate-secret

Rotate signing secret without changing URL

GET/filters

List your filters and published edges

Parameters

GET/signals

date    string  # YYYY-MM-DD, default today
league  string  # "cbb" or "nba", default "cbb"

GET/games

date    string  # YYYY-MM-DD, default today
league  string  # "cbb" or "nba", default "cbb"

POST/paper-bets

game_id   string   # Game identifier (from /games)
side      string   # "HOME", "AWAY", "OVER", or "UNDER"
line      number   # Spread or total line (e.g. -3.5)
bet_type  string   # "2h_spread", "fg_spread", "total_over", "total_under", "1h_spread"
amount    number   # Wager amount in units
odds      integer  # American odds (e.g. -110)

POST/paper-bets/resolve

game_id  string  # Optional — resolve only bets for this game.
                # Omit to resolve all pending bets with finished games.

PATCH/paper-bets/{bet_id}

line    number   # Optional — new spread/total line
amount  number   # Optional — new wager amount
odds    integer  # Optional — new American odds

POST/webhooks

url  string  # HTTPS endpoint to receive webhook payloads

PUT/webhooks

url  string  # New HTTPS endpoint (generates new signing secret)

Webhooks

Register an HTTPS endpoint via POST /webhooks or in Settings to receive a signed POST when a filter matches at halftime. Each delivery includes two verification headers:

  • X-Chalk-Signature — HMAC-SHA256 hex digest of {timestamp}.{payload}
  • X-Chalk-Timestamp — Unix epoch seconds when the payload was generated

Example payload

{
  "event": "signal.matched",
  "timestamp": "2026-03-31T01:15:00Z",
  "data": {
    "game_id": "805081125660",
    "home_team": "Kansas",
    "away_team": "Duke",
    "league": "cbb",
    "halftime_score": { "home": 28, "away": 37 },
    "halftime_margin": 9,
    "regime": "trailing",
    "signal": "BEARISH",
    "model_implied_2h": -1.2,
    "market_2h_line": -5.5,
    "edge_pts": 4.3,
    "filter": {
      "id": "a1b2c3d4-...",
      "name": "Power4 Trailing Bearish"
    },
    "game_url": "https://chalk.is/game/805081125660?src=webhook"
  }
}

Signature verification

Reject any request where the signature does not match or the timestamp is more than 5 minutes old.

Python

import hmac, hashlib

def verify_signature(payload: bytes, signature: str, timestamp: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.".encode() + payload,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

TypeScript

import { createHmac, timingSafeEqual } from "crypto";

function verifySignature(
  payload: string,
  signature: string,
  timestamp: string,
  secret: string,
): boolean {
  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${payload}`)
    .digest("hex");
  return timingSafeEqual(
    Buffer.from(`sha256=${expected}`),
    Buffer.from(signature),
  );
}

After 10 consecutive delivery failures, the webhook is automatically disabled. Re-enable it in Settings after fixing your endpoint.

Error Codes

CodeNameDescription
401UnauthorizedMissing or invalid API key. Check the X-API-Key header.
403ForbiddenValid key but insufficient tier. Syndicate required for API access.
404Not FoundResource does not exist. Verify the game ID or endpoint path.
422Validation ErrorRequest parameters failed validation. Check the error detail.
429Rate LimitedToo many requests. Back off and check X-RateLimit-Reset header.

Start building

Syndicate includes 5,000 API calls per day, outbound webhooks with HMAC signing, and paper bet tracking.