← DominoLeaders

DominoLeaders Tournament API

A read-only REST API for tournament organizers to fetch their tournament data programmatically. Common use cases include Discord bots that announce results, OBS overlays that render live standings, and stats aggregators that snapshot performance over time. To get started, issue an API key from your tournament's manage page → API keys tab (visit /tournaments to find the tournament you organize).

Base URL: https://dominoleaders.com/api

Authentication

All authenticated requests require a bearer token in the Authorization header. API keys are scoped to a single tournament and look like dlk_....

curl https://dominoleaders.com/api/v1/tournaments \
  -H "Authorization: Bearer dlk_yourkey..."

Heads up: the full key is shown only once at issue time. If you lose it, revoke the old key from the manage page and issue a new one.

Endpoints

GET/v1/health

Public — no authentication required. Useful for liveness probes.

curl https://dominoleaders.com/api/v1/health

Response:

{ "status": "ok", "time": 1717449600000 }

GET/v1/tournaments

Lists tournaments owned by the organizer who issued the API key.

curl https://dominoleaders.com/api/v1/tournaments \
  -H "Authorization: Bearer dlk_yourkey..."

Response:

{
  "data": [
    {
      "id": "k973abc1...",
      "name": "Spring Championship",
      "status": "active",
      "startsAt": 1717449600000,
      "endsAt": null,
      "registrantCount": 32
    },
    {
      "id": "k973abc2...",
      "name": "Saturday Showdown",
      "status": "completed",
      "startsAt": 1714857600000,
      "endsAt": 1715116800000,
      "registrantCount": 16
    }
  ]
}

GET/v1/tournaments/:id

Returns the full tournament resource: metadata, current standings, and recent matches.

curl https://dominoleaders.com/api/v1/tournaments/k973abc1... \
  -H "Authorization: Bearer dlk_yourkey..."

Response:

{
  "data": {
    "id": "k973abc1...",
    "name": "Spring Championship",
    "description": "Weekly bracket-style tournament for verified players.",
    "status": "active",
    "startsAt": 1717449600000,
    "endsAt": null,
    "registrantCount": 32,
    "ownerUserId": "us123...",
    "rules": "First to 150. No fives. Standard Big-6.",
    "standings": [
      {
        "rank": 1,
        "userId": "us999...",
        "username": "domino_king",
        "displayName": "Domino King",
        "profileImageUrl": "https://...",
        "wins": 8,
        "losses": 1,
        "winRate": 0.888,
        "eloRating": 1612
      }
    ],
    "recentMatches": [
      {
        "id": "mt001...",
        "submitterUserId": "us999...",
        "opponentUserId": "us888...",
        "winnerUserId": "us999...",
        "submitterScore": 150,
        "opponentScore": 120,
        "submitterEloAfter": 1612,
        "opponentEloAfter": 1488,
        "screenshotUrls": ["https://..."],
        "reviewedAt": 1717445000000
      }
    ]
  }
}

GET/v1/tournaments/:id/standings

Just the standings array — a lighter payload optimized for OBS overlays and other high-frequency pollers.

curl https://dominoleaders.com/api/v1/tournaments/k973abc1.../standings \
  -H "Authorization: Bearer dlk_yourkey..."

Response:

{
  "data": [
    {
      "rank": 1,
      "userId": "us999...",
      "username": "domino_king",
      "displayName": "Domino King",
      "profileImageUrl": "https://cdn.../avatar.png",
      "wins": 8,
      "losses": 1,
      "winRate": 0.888,
      "eloRating": 1612
    },
    {
      "rank": 2,
      "userId": "us888...",
      "username": "bone_yard",
      "displayName": "Bone Yard",
      "profileImageUrl": "https://cdn.../avatar2.png",
      "wins": 7,
      "losses": 2,
      "winRate": 0.777,
      "eloRating": 1574
    }
  ]
}

GET/v1/tournaments/:id/matches?status=approved|pending|rejected

Matches for the tournament. Defaults to status=approved; pass pending or rejected to fetch the other queues.

curl "https://dominoleaders.com/api/v1/tournaments/k973abc1.../matches?status=approved" \
  -H "Authorization: Bearer dlk_yourkey..."

Response:

{
  "data": [
    {
      "id": "mt001...",
      "submitterUserId": "us999...",
      "opponentUserId": "us888...",
      "winnerUserId": "us999...",
      "submitterScore": 150,
      "opponentScore": 120,
      "submitterEloAfter": 1612,
      "opponentEloAfter": 1488,
      "screenshotUrls": ["https://cdn.../proof.png"],
      "reviewedAt": 1717445000000
    }
  ]
}

Webhooks

Subscribing

Create a webhook from your tournament's manage page → Webhooks tab. Provide a URL, pick the events you care about, and save. Copy the signing secret immediately — like API keys, it's shown only once.

Supported events

The following 8 events are supported. Any other event name will be rejected at subscription time.

  • match.approved
  • match.rejected
  • match.submitted
  • tournament.activated
  • tournament.completed
  • tournament.cancelled
  • participant.joined
  • participant.left

Payload format

All deliveries are HTTP POSTs with a JSON body and the following headers:

  • Content-Type: application/json
  • X-DL-Event: <event-name>
  • X-DL-Signature: sha256=<hex> — HMAC SHA-256 of the raw body using your webhook secret
  • X-DL-Delivery: <uuid> — stable per delivery; use as an idempotency key
  • User-Agent: DominoLeaders-Webhook/1.0

Example body for match.approved:

{
  "event": "match.approved",
  "deliveredAt": 1717449600000,
  "tournamentId": "k973abc1...",
  "match": {
    "id": "mt001...",
    "submitterUserId": "us999...",
    "opponentUserId": "us888...",
    "winnerUserId": "us999...",
    "submitterScore": 150,
    "opponentScore": 120,
    "submitterEloAfter": 1612,
    "opponentEloAfter": 1488,
    "screenshotUrls": ["https://cdn.../proof.png"],
    "reviewedAt": 1717445000000
  }
}

Verifying signatures

Always verify the signature before trusting the payload. Compute an HMAC SHA-256 of the raw request body using your webhook secret and compare to the X-DL-Signature header.

import crypto from "crypto";
import express from "express";

const app = express();

function verify(rawBody: string, signatureHeader: string, secret: string) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return signatureHeader === `sha256=${expected}`;
}

app.post(
  "/dl-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sig = req.header("X-DL-Signature") || "";
    if (!verify(req.body.toString("utf8"), sig, process.env.DL_SECRET!)) {
      return res.status(401).send("bad signature");
    }
    const body = JSON.parse(req.body.toString("utf8"));
    console.log(body.event, body.tournamentId);
    res.sendStatus(200);
  }
);

Retries and delivery audit

Every delivery attempt is logged to the webhookDeliveries table and surfaced on the manage page, including non-2xx responses and timeouts (>10s). For v1 there are no automatic retries — implement idempotency on your side using the X-DL-Delivery header so manual re-deliveries from the dashboard are safe.

Rate limits and abuse

There are no formal rate limits today, but please be reasonable — cache standings on your end and avoid sub-second polling. For high-volume integrations (large public leaderboards, multi-stream overlays), reach out at support@dominoleaders.com so we can whitelist you and discuss your needs.

API keys can be revoked at any time from the manage page; revocation is immediate and existing requests in flight will be rejected on their next call.

Roadmap

  • Write mutations — creating participants and approving matches via the API. Coming later.
  • Bracket data — exposed once bracket-style tournaments ship. Coming later.