Developers

Public API v1

Authenticate with an API key, read and update tournaments you manage, and receive signed webhooks when the bracket moves.

Authentication

Create a key in Settings → Developer. The full token is shown once and starts with brk_live_. Send it on every /v1 request — either header works:

X-Api-Key: brk_live_…
Authorization: Bearer brk_live_…
  • read — list and fetch tournaments, participants and matches.
  • write — add participants, report scores, start or finalize. GET routes still work on a write-only key.
  • The key acts as its owner. Public tournaments are readable; private ones only if you can manage them.

Interactive try-it-out lives on Swagger.

Endpoints

Base URL https://bracket.arrobin.com/backend. JSON in and out. :idOrSlug accepts a tournament cuid or its public slug.

GET/v1/meread

Confirm the key works. Returns the owner’s user id and the key’s scopes.

curl https://bracket.arrobin.com/backend/v1/me \
  -H "X-Api-Key: brk_live_…"
GET/v1/tournamentsread

Tournaments you created or administer (not the public discovery catalog).

curl https://bracket.arrobin.com/backend/v1/tournaments \
  -H "X-Api-Key: brk_live_…"
GET/v1/tournaments/:idOrSlugread

One tournament by cuid or slug. Private events 404 unless the key owner can manage them.

Query include=participants,matches,standings — comma-separated extras.

curl "https://bracket.arrobin.com/backend/v1/tournaments/summer-open?include=participants,matches" \
  -H "X-Api-Key: brk_live_…"
GET/v1/tournaments/:idOrSlug/participantsread

Teams / players in seed order, including roster rows.

curl https://bracket.arrobin.com/backend/v1/tournaments/summer-open/participants \
  -H "X-Api-Key: brk_live_…"
POST/v1/tournaments/:idOrSlug/participantswrite

Add a participant before the bracket is generated.

Body { "name": "Team Alpha", "seed": 1, "players": ["Ada"] }

Fails after generate (bracket_generated), on duplicate names, or when the event is full.

curl https://bracket.arrobin.com/backend/v1/tournaments/summer-open/participants \
  -H "X-Api-Key: brk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"name":"Team Alpha","seed":1}'
GET/v1/tournaments/:idOrSlug/matchesread

Non-bye matches. Filter with state=open (ready to play), complete, or all (default).

Query state=open | complete | all

curl "https://bracket.arrobin.com/backend/v1/tournaments/summer-open/matches?state=open" \
  -H "X-Api-Key: brk_live_…"
PUT/v1/matches/:idwrite

Report a result. You must manage the parent tournament.

Body { "homeScore": 2, "awayScore": 1, "winnerId": "optional_team_id", "sets": [{"home":11,"away":7}] }

If sets[] is sent, scores become set-wins. winnerId must be home or away. Omit it to infer from scores (equal scores are a draw).

curl https://bracket.arrobin.com/backend/v1/matches/MATCH_ID -X PUT \
  -H "X-Api-Key: brk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"homeScore":2,"awayScore":1}'
POST/v1/tournaments/:idOrSlug/startwrite

Generate the bracket from saved settings. Owner only — admins get owner_only.

curl https://bracket.arrobin.com/backend/v1/tournaments/summer-open/start -X POST \
  -H "X-Api-Key: brk_live_…"
POST/v1/tournaments/:idOrSlug/finalizewrite

Mark the tournament completed and fire tournament.completed. Idempotent if already done.

curl https://bracket.arrobin.com/backend/v1/tournaments/summer-open/finalize -X POST \
  -H "X-Api-Key: brk_live_…"

Errors

Failed /v1 responses use a single envelope. Validation failures may add details.

{
  "error": {
    "code": "not_found",
    "message": "Tournament not found"
  }
}
CodeWhen
unauthorizedMissing API key
invalid_api_keyUnknown or revoked key
insufficient_scopeWrite endpoint called with a read-only key
forbidden / owner_onlyYou do not manage this tournament (start is owner-only)
not_foundUnknown id — or a private event you cannot see
rate_limited120 requests per minute per key
validation_errorBody failed schema checks

Webhooks

Register an HTTPS URL in Developer settings (or a tournament’s Integrations tab). We POST JSON within 5 seconds and sign the raw body. After 20 consecutive failures the hook is paused. Test deliveries add X-Bracket-Test: 1.

Headers

  • X-Bracket-Event — event name, e.g. match.completed
  • X-Bracket-Signature — sha256= plus hex HMAC-SHA256 of the raw body
  • X-Bracket-Delivery — unique delivery id
  • User-Agent — Bracket-Webhooks/1.0

Events

  • tournament.started

    Tournament started (bracket generated)

  • tournament.completed

    Tournament completed (final results)

  • match.ready

    Match ready to play

  • match.completed

    Match result reported

  • participant.registered

    Participant registered

  • registration.approved

    Registration approved

  • schedule.updated

    Schedule updated

  • announcement.posted

    Announcement posted

Payload

Every delivery is { id, event, createdAt, tournamentId, data }. When we know the tournament we attach a short summary on data.tournament; match events also include data.match. A test ping sends { test: true, message: "Test delivery from Bracket" } as data.

{
  "id": "8f3c1e2a-…",
  "event": "match.completed",
  "createdAt": "2026-09-05T12:00:00.000Z",
  "tournamentId": "clxxxxxxxxxxxxxxxxxxxxxxxx",
  "data": {
    "tournament": { "id": "…", "slug": "summer-open", "name": "Summer Open", "status": "ACTIVE", "format": "SINGLE_ELIM" },
    "match": { "id": "…", "round": 2, "status": "COMPLETED", "homeScore": 2, "awayScore": 1 }
  }
}

Verify the signature (Node)

Hash the exact bytes you received. Re-serializing JSON will fail the check.

const crypto = require('crypto');

function verifySignature(rawBody, secret, header) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  const a = Buffer.from(header ?? '', 'utf8');
  const b = Buffer.from(expected, 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express: app.use(express.raw({ type: 'application/json' }))
// then verifySignature(req.body, secret, req.get('X-Bracket-Signature'))

Rate limits

120 requests per minute per API key. Responses include X-RateLimit-Limit and X-RateLimit-Remaining. Over the limit you get 429 with Retry-After.

Ready to call it?

Mint a key, then try the live schema explorer.