for organizers, federations, and sport software
Plug your event into the public.
One JSON POST per leaderboard update. tournament.guru renders the public spectator pages — branded with your logo, your sponsors, your scoring rules — and threads competitors across events for the Trophy Room.
You own the scoring
Producers compute rank locally and POST a pre-ranked leaderboard. No platform-side rules engine to fight.
Sport-agnostic shape
Bag weights, golf strokes, chess ratings, race times — same columns + rows JSON. Add a new sport without touching the platform.
Branded per event
Each tournament carries its own logo, colors, hero image, and sponsor strip. Your producer attribution links to your homepage.
One POST, sport-shaped
The SDK handles auth, retries, idempotency. You describe your columns and stamp ranks; we paint.
import { TournamentGuruClient, assignRanksAscending } from "@tournament-guru/sdk";
const guru = new TournamentGuruClient({ apiKey: process.env.GURU_API_KEY! });
const rows = entries.map((e) => ({
competitorId: e.userId,
rank: 0, // overwritten below
displayName: e.golferName,
values: { thru: e.holes, today: e.today, total: e.total },
}));
const ranked = assignRanksAscending(rows, (r) => r.values.total);
await guru.upsertTournament({
schemaVersion: "v1",
id: "highland-invitational-r3",
producer: { id: "highland-tour", displayName: "Highland Tour" },
name: "Highland Invitational — Round 3",
startDate: "2026-05-04",
location: "Pinehurst No. 2, NC",
state: "in-progress",
leaderboard: {
updatedAt: new Date().toISOString(),
rankingExplanation: "Lowest total stroke score wins.",
columns: [
{ id: "thru", label: "Thru", format: "integer", align: "right" },
{ id: "today", label: "Today", format: "integer", align: "right" },
{ id: "total", label: "Total", format: "integer", align: "right" },
],
rows: ranked,
},
});Fishing
// Fishing — heaviest aggregate, ties broken by big fish.
assignRanksDescending(rows, [
(r) => r.values.weight ?? 0,
(r) => r.values.big ?? 0,
]);Running
// Running — fastest chip time wins.
assignRanksAscending(rows, (r) => r.values.timeSec ?? 0);v1 endpoints
| POST | /api/v1/tournaments | Create or replace a tournament |
| PATCH | /api/v1/tournaments/{id} | Partial update |
| POST | /api/v1/tournaments/{id}/leaderboard | Atomic leaderboard replace |
| POST | /api/v1/tournaments/{id}/events | Append to event log |
| GET | /api/v1/tournaments/{producerId}:{tournamentId} | Public read |
| GET | /api/v1/producers/me | Auth smoke test |
Auth: Authorization: Bearer tg_<producerId>_<secret>. Body capped at 200 KB. Default rate limit 60 req/min per producer.
Want a key?
We're issuing producer keys by hand for now. Email hello@tournament.guru with your event, your sport, and where the data lives.