Skip to main content

Developer Reference

BetEdge API v1

REST API for sports-betting data, Pinnacle-anchored model picks, track-record analytics, and real-time webhooks. Base URL: https://betedge.tips/api/v1

Authentication

All API requests require a Bearer token in the Authorization header. Keys have the prefix betedge_ followed by 64 lowercase hex characters. Generate and manage keys at /dashboard/api-keys.

curl
curl -X GET "https://betedge.tips/api/v1/picks" \
  -H "Authorization: Bearer betedge_<your_api_key>"
JavaScript
const response = await fetch("https://betedge.tips/api/v1/picks", {
  headers: { Authorization: "Bearer betedge_<your_api_key>" },
});
Python
import requests
resp = requests.get(
    "https://betedge.tips/api/v1/picks",
    headers={"Authorization": "Bearer betedge_<your_api_key>"},
)

Error responses

  • 401 invalid_api_key — Missing, invalid, or revoked key
  • 403 tier_upgrade_required — Endpoint not available on your tier

Rate limits and quotas

Rate limits use a sliding-window algorithm per API key. Monthly quotas reset at the start of each calendar month UTC.

TierRequests/hourMonthly cap
Starter10010,000
Growth1,000100,000
Enterprise10,0001,000,000

Response headers on every request

  • X-RateLimit-Limit — requests allowed per hour
  • X-RateLimit-Remaining — requests remaining in current window
  • X-RateLimit-Reset — Unix timestamp when the window resets

429 rate limit exceeded

{
  "error": "rate_limit_exceeded",
  "retry_after_seconds": 42
}

// Or for monthly cap:
{
  "error": "monthly_quota_exceeded",
  "resets_at": "2026-06-01T00:00:00.000Z"
}

GET /api/v1/picks

GET/api/v1/picks
StarterGrowthEnterprise

Returns a paginated list of published and settled picks. Picks in admin_review status are never returned.

Query parameters

ParameterTypeDescription
sportstring?Filter by sport slug (e.g. soccer, tennis)
leaguestring?Filter by league name (e.g. Premier League)
sinceISO 8601?Return picks with kickoff_at after this timestamp
limitinteger?Results per page (default 50, max 200)
cursorstring?Pagination cursor from previous response

Response shape

{
  "data": [
    {
      "id": "uuid",
      "sport": "soccer",
      "league": "Premier League",
      "home_team": "Arsenal",
      "away_team": "Chelsea",
      "kickoff_at": "2026-05-25T15:00:00Z",
      "pick": "Arsenal -0.5 Asian Handicap",
      "odds": 1.91,
      "edge_pct": 4.2,
      "confidence": 3,
      "status": "published",
      "settled_at": null,
      "result": null
    }
  ],
  "next_cursor": "2026-05-25T14:00:00Z",
  "count": 47
}
curl
curl "https://betedge.tips/api/v1/picks?sport=soccer&limit=20" \
  -H "Authorization: Bearer betedge_<your_api_key>"
JavaScript
const resp = await fetch(
  "https://betedge.tips/api/v1/picks?sport=soccer&limit=20",
  { headers: { Authorization: "Bearer betedge_<key>" } }
);
const { data, next_cursor, count } = await resp.json();
Python
resp = requests.get(
    "https://betedge.tips/api/v1/picks",
    params={"sport": "soccer", "limit": 20},
    headers={"Authorization": "Bearer betedge_<key>"},
)
data = resp.json()["data"]

Error responses

  • 400 invalid_cursor Cursor format is invalid
  • 401 invalid_api_key Missing or invalid Bearer token

GET /api/v1/track-record

GET/api/v1/track-record
GrowthEnterprise

Returns aggregated performance statistics. Starter-tier requests return 403.

Query parameters

ParameterTypeDescription
sportstring?Scope to a single sport
days_backinteger?Lookback window in days (default 30, max 365)

Response shape

{
  "overall": {
    "roi_pct": 8.3,
    "win_rate": 54.1,
    "total_settled": 420,
    "clv_avg_pct": 2.1,
    "edge_avg_pct": 3.8
  },
  "per_sport": [
    { "sport": "soccer", "roi_pct": 9.1, "total_settled": 180 }
  ],
  "series": [
    { "date": "2026-05-01", "cumulative_pnl_pct": 4.2 }
  ]
}
curl
curl "https://betedge.tips/api/v1/track-record?days_back=30" \
  -H "Authorization: Bearer betedge_<growth_or_enterprise_key>"
JavaScript
const resp = await fetch(
  "https://betedge.tips/api/v1/track-record?days_back=30",
  { headers: { Authorization: "Bearer betedge_<key>" } }
);
const { overall, per_sport, series } = await resp.json();
Python
resp = requests.get(
    "https://betedge.tips/api/v1/track-record",
    params={"days_back": 30},
    headers={"Authorization": "Bearer betedge_<key>"},
)
overall = resp.json()["overall"]

Error responses

  • 403 tier_upgrade_required Starter tier; minimum_tier: growth
  • 401 invalid_api_key Missing or invalid Bearer token

GET /api/v1/match-analyses/:matchId

GET/api/v1/match-analyses/:matchId
GrowthEnterprise

Returns the AI-generated analysis text for a specific match. The matchId corresponds to the id field in pick objects.

Response shape

{
  "match_id": "uuid",
  "analysis": "Arsenal vs Chelsea — Pinnacle fair value is 1.87 on Arsenal ...",
  "created_at": "2026-05-25T08:00:00Z"
}
curl
curl "https://betedge.tips/api/v1/match-analyses/<matchId>" \
  -H "Authorization: Bearer betedge_<growth_or_enterprise_key>"
JavaScript
const resp = await fetch(
  `https://betedge.tips/api/v1/match-analyses/${matchId}`,
  { headers: { Authorization: "Bearer betedge_<key>" } }
);
const { analysis } = await resp.json();
Python
resp = requests.get(
    f"https://betedge.tips/api/v1/match-analyses/{match_id}",
    headers={"Authorization": "Bearer betedge_<key>"},
)
analysis = resp.json()["analysis"]

Error responses

  • 404 not_found No analysis found for this matchId
  • 403 tier_upgrade_required Starter tier; minimum_tier: growth
  • 401 invalid_api_key Missing or invalid Bearer token

POST /api/v1/webhooks

POST/api/v1/webhooks
GrowthEnterprise

Creates a webhook subscription. The hmac_secret is returned once — store it immediately for signature verification. Maximum 5 subscriptions per API key.

Request body (JSON)

ParameterTypeDescription
urlstringHTTPS endpoint to deliver events to (public host required)
eventsstring[]Array of event types: "pick.published" and/or "pick.settled"

Response shape

// 201 Created
{
  "subscription_id": "uuid",
  "url": "https://yourserver.com/webhook",
  "events": ["pick.published"],
  "hmac_secret": "abc123..."   // Store immediately — not retrievable after this response
}
curl
curl -X POST "https://betedge.tips/api/v1/webhooks" \
  -H "Authorization: Bearer betedge_<growth_or_enterprise_key>" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://yourserver.com/webhook","events":["pick.published"]}'
JavaScript
const resp = await fetch("https://betedge.tips/api/v1/webhooks", {
  method: "POST",
  headers: {
    Authorization: "Bearer betedge_<key>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://yourserver.com/webhook",
    events: ["pick.published", "pick.settled"],
  }),
});
const { subscription_id, hmac_secret } = await resp.json();
Python
resp = requests.post(
    "https://betedge.tips/api/v1/webhooks",
    headers={"Authorization": "Bearer betedge_<key>"},
    json={
        "url": "https://yourserver.com/webhook",
        "events": ["pick.published", "pick.settled"],
    },
)
secret = resp.json()["hmac_secret"]  # store this immediately

Error responses

  • 409 duplicate_url A subscription already exists for this URL
  • 429 subscription_limit Maximum 5 subscriptions per API key
  • 400 invalid_url URL must be HTTPS and publicly reachable
  • 403 tier_upgrade_required Starter tier; minimum_tier: growth

GET /api/v1/picks/stream

GET/api/v1/picks/stream
Enterprise

Server-Sent Events (SSE) stream. Delivers new published picks as they happen. Sends a heartbeat comment every 30 seconds to keep the connection alive. Starter and Growth tiers return 403.

Event format

event: pick.published
data: {"id":"uuid","sport":"soccer","odds":1.91,"edge_pct":4.2,...}

: heartbeat
curl
curl -N "https://betedge.tips/api/v1/picks/stream" \
  -H "Authorization: Bearer betedge_<enterprise_key>" \
  -H "Accept: text/event-stream"
JavaScript
const evtSource = new EventSource(
  "https://betedge.tips/api/v1/picks/stream",
  { withCredentials: false }  // use a proxy that injects the Bearer header
);
evtSource.addEventListener("pick.published", (e) => {
  const pick = JSON.parse(e.data);
  console.log("New pick:", pick.id);
});
Python
import sseclient, requests

resp = requests.get(
    "https://betedge.tips/api/v1/picks/stream",
    headers={"Authorization": "Bearer betedge_<enterprise_key>"},
    stream=True,
)
client = sseclient.SSEClient(resp)
for event in client.events():
    if event.event == "pick.published":
        import json; pick = json.loads(event.data)

Error responses

  • 403 tier_upgrade_required Starter/Growth tier; minimum_tier: enterprise
  • 401 invalid_api_key Missing or invalid Bearer token

Webhook signature verification

Every webhook delivery includes an X-BetEdge-Signature header containing an HMAC-SHA256 signature of the raw request body, formatted as sha256=<hex>. Verify the signature using the hmac_secret returned when you created the subscription.

Delivery headers

  • X-BetEdge-Signature: sha256=<hex>
  • X-BetEdge-Event: pick.published
  • Content-Type: application/json

Verification examples

Node.js / TypeScript
import { createHmac } from "node:crypto";

function verifyBetEdgeSignature(
  rawBody: string,
  signature: string,
  secret: string
): boolean {
  const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
  // Constant-time compare to prevent timing attacks
  const buf1 = Buffer.from(signature);
  const buf2 = Buffer.from(expected);
  if (buf1.length !== buf2.length) return false;
  return require("node:crypto").timingSafeEqual(buf1, buf2);
}
Python
import hashlib, hmac

def verify_betedge_signature(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Retry schedule

Failed deliveries are retried with exponential backoff: 1 min, 5 min, 30 min, 2 hr, 12 hr. After 5 failed attempts the subscription is marked exhausted and deliveries stop.

Error codes

HTTP statuserror codeDescription
400invalid_cursorPagination cursor is malformed
400invalid_urlWebhook URL must be HTTPS and publicly reachable
401invalid_api_keyBearer token missing, invalid, or revoked
403tier_upgrade_requiredEndpoint not available on your current tier
404not_foundRequested resource does not exist
409duplicate_urlWebhook subscription for this URL already exists
429rate_limit_exceededHourly rate limit reached; see Retry-After header
429monthly_quota_exceededMonthly request cap reached; resets at start of next month
429subscription_limitMaximum 5 webhook subscriptions per API key
500internal_errorUnexpected server error; retry with exponential backoff
18+ only. Gambling can be addictive — help at BeGambleAware.
API Reference — BetEdge Developer Docs | BetEdge