Mini 4WD Manager

Mini 4WD Manager

API Reference

Overview

Base URL

us-central1-mini4wdtournament.cloudfunctions.net

Protocol

HTTPS only

Format

application/json

Authentication

All requests require a permanent API key passed as a Bearer token.

Request Header
Authorization: Bearer m4wd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Generating a Key

  1. Log in to the app and click your profile icon in the top-right corner
  2. Select Generate API Key
  3. Copy the key immediately — it is shown only once and never stored in plain text
  4. Store it securely in an environment variable, never in source control
Security: Keys are stored as SHA-256 hashes. If compromised, select Revoke API Key from the user menu — the old key is immediately invalidated.

Plan Limits

LimitFreePro
Max Tournaments10Unlimited
API Key1 active key1 active key
Real-time dashboard updates

Create Tournament

POST /apiCreateTournament
Creates a new tournament and optionally pre-registers players and their cars/tickets.

Required Headers

HeaderValue
AuthorizationBearer <your_api_key>
Content-Typeapplication/json

Parameters

You must pass the tournament configuration as a JSON body (POST).

{
  "name": "My Tournament",
  "mode": "STANDARD",
  "dryRun": false,
  "settings": { ... },
  "entries": [ ... ]
}

Request Body

FieldTypeRequiredDescription
namestring Yes Display name of the tournament
modestring Yes STANDARD, TICKETED, or POINTS
externalIdstring No Your app's own ID for this tournament. Stored for cross-reference; not validated for uniqueness.
settingsobject No Tournament configuration. Available fields depend on mode. See settings fields sections below.
entriesarray No List of player registrations to pre-populate. See Entries Array.

Entries Array

Each object in entries registers a player and their cars or tickets.

FieldTypeRequiredDescription
playerNamestring Yes Player display name. Same player can enter multiple categories — matched case-insensitively.
categorystring Yes Category key. Must be in enabledCategories (unless dynamic categories enabled).
countnumber Yes STANDARD: Number of cars. TICKETED: Number of tickets. Must be ≥ 1 and ≤ configured max. POINTS: Not needed, pass 0.
externalIdstring No Your app's own ID for this player. Stored for cross-reference.

Known Category Keys

box-stock

Box Stock

stock

Stock

bmax

BMax

open

Open Class

Dynamic Categories: Set "dynamicCategoriesEnabled": true to allow custom category names in entries. Unknown names are automatically slugified and created in the tournament.
Standard

Standard Mode

Round-robin style tournament where players register cars. Cars race across rounds and accumulate wins and losses. Supports second-chance (double elimination) brackets.

Settings Fields

FieldTypeDefaultDescription
lanesnumber3Number of racing lanes (3 or 5)
enabledCategoriesstring[]["box-stock","stock","bmax","open"]Active racing categories
maxCarsPerCategoryobject{"box-stock":3,...}Max cars a single player can enter per category. Key = category slug, value = max count.
secondChanceModestring"everyone""everyone", "none", or "cutoff"
secondChanceThresholdnumber3Losses before elimination (when secondChanceMode is "cutoff")
timerEnabledbooleanfalseShow lap timer during races
confirmWinnerSelectionbooleantrueConfirmation dialog before saving race results
dynamicCategoriesEnabledbooleanfalseAuto-create unknown categories from entries
reEntryEnabledbooleanfalseAllow eliminated players to re-enter. Only valid when secondChanceMode is "none".
reEntryMaxCarsnumber2Max re-entries per player. Only applies when reEntryEnabled is true.
Invalid fields: rebuy_enabled, rebuy_ticket_amount, boss_battle_enabled, pointsHeats, pointsDistribution, topCutSize, maxTicketsPerCategory

Sample Payload

Standard Mode — POST body
{
  "name": "Summer Cup 2025",
  "mode": "STANDARD",
  "dryRun": false,
  "externalId": "my-app-event-001",
  "settings": {
    "lanes": 3,
    "enabledCategories": ["box-stock", "bmax"],
    "maxCarsPerCategory": { "box-stock": 3, "bmax": 2 },
    "secondChanceMode": "everyone",
    "secondChanceThreshold": 3,
    "timerEnabled": true,
    "confirmWinnerSelection": true
  },
  "entries": [
    { "playerName": "Racer X",    "category": "box-stock", "count": 2 },
    { "playerName": "Speedy",     "category": "bmax",      "count": 1 },
    { "playerName": "Turbo King", "category": "box-stock", "count": 3 }
  ]
}
Ticketed

Ticketed Mode

Ticket-based bracket tournament. Players enter with tickets instead of cars. Eliminated when tickets run out. Supports rebuys and boss battles.

In this mode, count in each entry represents number of tickets, not cars. One car entity is created per player assigned that many tickets.

Settings Fields

FieldTypeDefaultDescription
lanesnumber3Number of racing lanes (3 or 5)
enabledCategoriesstring[]all 4 categoriesActive categories
maxTicketsPerCategoryobject{"box-stock":3,...}Max tickets a player can buy per category
rebuy_enabledbooleanfalseAllow players to purchase additional tickets mid-event
rebuy_ticket_amountnumber1Tickets granted on rebuy
boss_battle_enabledbooleantrueInclude a Boss Battle round
timerEnabledbooleanfalseShow lap timer
confirmWinnerSelectionbooleantrueConfirm dialog before saving results
dynamicCategoriesEnabledbooleanfalseAuto-create unknown categories
Invalid fields: secondChanceMode, secondChanceThreshold, reEntryEnabled, reEntryMaxCars, pointsHeats, pointsDistribution, topCutSize, maxCarsPerCategory

Sample Payload

Ticketed Mode — POST body
{
  "name": "Grand Prix Qualifier",
  "mode": "TICKETED",
  "dryRun": false,
  "externalId": "my-app-event-002",
  "settings": {
    "lanes": 3,
    "enabledCategories": ["stock", "open"],
    "maxTicketsPerCategory": { "stock": 5, "open": 3 },
    "rebuy_enabled": true,
    "rebuy_ticket_amount": 1,
    "boss_battle_enabled": true,
    "reEntryEnabled": false
  },
  "entries": [
    { "playerName": "Ace",     "category": "stock", "count": 3 },
    { "playerName": "Blitz",   "category": "open",  "count": 2 },
    { "playerName": "Circuit", "category": "stock", "count": 5 }
  ]
}
Points

Points Mode

Heat-based accumulation tournament. Players earn points across multiple heats based on finishing position. Supports an optional top-cut playoff.

Settings Fields

FieldTypeDefaultDescription
lanesnumber3Number of racing lanes (3 or 5)
enabledCategoriesstring[]all 4 categoriesActive categories
maxCarsPerCategoryobject{"box-stock":3,...}Max cars per player per category
pointsHeatsnumber3Number of heat rounds each car races
pointsDistributionstring"3,2,1"Comma-separated points per finishing position (1st, 2nd, 3rd…)
topCutSizenumber0Top N cars advance to a playoff. 0 = AUTO
timerEnabledbooleanfalseShow lap timer
confirmWinnerSelectionbooleantrueConfirm dialog before saving results
dynamicCategoriesEnabledbooleanfalseAuto-create unknown categories
Invalid fields: secondChanceMode, secondChanceThreshold, rebuy_enabled, rebuy_ticket_amount, boss_battle_enabled, reEntryEnabled, reEntryMaxCars, maxTicketsPerCategory

Sample Payload

Points Mode — POST body
{
  "name": "Monthly Points Championship",
  "mode": "POINTS",
  "dryRun": false,
  "externalId": "my-app-event-003",
  "settings": {
    "lanes": 5,
    "enabledCategories": ["bmax", "open"],
    "maxCarsPerCategory": { "bmax": 2, "open": 2 },
    "pointsHeats": 3,
    "pointsDistribution": "3,2,1",
    "topCutSize": 4
  },
  "entries": [
    { "playerName": "Nova",  "category": "bmax", "count": 0 },
    { "playerName": "Storm", "category": "open", "count": 0 },
    { "playerName": "Blaze", "category": "open", "count": 0 }
  ]
}

Get Tournament Results

Export match-by-match results programmatically from a tournament you own.

POST GET /apiGetTournamentResults

Required Headers

HeaderValue
AuthorizationBearer <your_api_key>
Content-Typeapplication/json (If using POST body)

Parameters

FieldTypeRequiredDescription
tournamentIdstring Yes The ID of the tournament. Can be passed in the JSON body (POST) or as a query parameter (GET).
formatstring No Set to csv to receive a text/csv string. Can be passed as a query parameter (GET). Defaults to JSON.
{
  "tournamentId": "YOUR_TOURNAMENT_ID_HERE"
}

GET /apiGetTournamentResults?tournamentId=YOUR_TOURNAMENT_ID_HERE&format=csv

200 OK Example Response (JSON)

{
  "success": true,
  "results": [
    {
      "category": "Box Stock",
      "roundName": "Round 1",
      "matchNum": "M1",
      "laneNum": 1,
      "playerName": "Racer X",
      "externalId": "usr_123",
      "result": "1st Place",
      "time": "15.340s",
      "carName": "Speed Demon"
    }
  ]
}

200 OK Example Response (CSV)

category,roundName,matchNum,laneNum,playerName,externalId,result,time,carName
"Box Stock","Round 1","M1","1","Racer X","usr_123","1st Place","15.340s","Speed Demon"
"Box Stock","Round 1","M1","2","Dash","usr_456","2nd Place","16.100s","Shooting Star"
"Box Stock","Round 1","M1","3","Zoom","usr_789","3rd Place","16.850s","Thunderbolt"
Note: The carName field is only returned for Standard and Points modes. Ticketed mode does not track distinct car names per player.

Dry Run (Testing)

You can test your payload validity and simulate the response without actually creating a tournament by adding "dryRun": true to the root of your request JSON.

200 OK Dry Run Response

{
  "success": true,
  "dryRun": true,
  "message": "Dry run successful. No tournament was created.",
  "importStats": {
    "successful": 4,
    "failed": 0,
    "errors": []
  }
}

Responses

200 OK Success

Success Response
{
  "success": true,
  "tournamentId": "abc123XYZ",
  "importStats": {
    "successful": 4,
    "failed": 1,
    "errors": [
      "Entry 2: Player \"Storm\" exceeds max limit of 2 for category \"bmax\"."
    ]
  }
}

Error Codes

StatusReason
400Missing name/mode, invalid mode, or mode-specific forbidden field included
401Missing, invalid, or revoked API key
403Tournament limit reached (Free plan max: 10)
404Tournament not found
500Unexpected server error

Code Examples

Node.js

Node.js — fetch
// Store key in env: M4WD_API_KEY=m4wd_...
const API_KEY = process.env.M4WD_API_KEY;
const API_URL = 'https://us-central1-mini4wdtournament.cloudfunctions.net/apiCreateTournament';

async function createTournament(payload) {
  const res = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
  });
  const data = await res.json();
  if (!data.success) throw new Error(data.error);
  return data;
}

createTournament({
  name: 'Club Night — Round 7',
  mode: 'STANDARD',
  dryRun: false,
  settings: { lanes: 3, enabledCategories: ['box-stock'] },
  entries: [{ playerName: 'Marc', category: 'box-stock', count: 2 }]
}).then(r => console.log('Created:', r.tournamentId));

Python

Python — requests
import os, requests

API_URL = 'https://us-central1-mini4wdtournament.cloudfunctions.net/apiCreateTournament'
API_KEY = os.environ['M4WD_API_KEY']

def create_tournament(payload):
    res = requests.post(API_URL, json=payload, headers={
        'Authorization': f'Bearer {API_KEY}',
        'Content-Type': 'application/json'
    })
    data = res.json()
    if not data.get('success'): raise Exception(data.get('error'))
    return data

result = create_tournament({
    'name': 'Python Test',
    'mode': 'POINTS',
    'dryRun': false,
    'settings': { 'lanes': 5, 'pointsHeats': 3 },
    'entries': [{ 'playerName': 'Alpha', 'category': 'bmax', 'count': 1 }]
})
print('Tournament ID:', result['tournamentId'])

cURL

cURL
curl -X POST \
  https://us-central1-mini4wdtournament.cloudfunctions.net/apiCreateTournament \
  -H "Authorization: Bearer m4wd_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "cURL Test Tournament",
    "mode": "STANDARD",
    "dryRun": false,
    "settings": { "lanes": 3, "enabledCategories": ["box-stock"] },
    "entries": [{ "playerName": "Test Racer", "category": "box-stock", "count": 1 }]
  }'

Settings Quick Reference

Field Standard Single Elim. Points
lanes
enabledCategories
timerEnabled
confirmWinnerSelection
dynamicCategoriesEnabled
maxCarsPerCategory
secondChanceMode
secondChanceThreshold
maxTicketsPerCategory
rebuy_enabled
rebuy_ticket_amount
boss_battle_enabled
reEntryEnabled
reEntryMaxCars
pointsHeats
pointsDistribution
topCutSize