Optional server API for shared tournament hosting. Base URL: /api
The REST API is an optional feature for shared tournament hosting. It enables tournament sharing, browsing, and management across multiple users when deployed to a PHP-enabled web server.
NEWTON_API_ENABLED=false on demo or public-facing instances/tournaments directorydocker run -d \
--name newton-tournament \
-p 2020:2020 \
-v ./tournaments:/var/www/html/tournaments \
ghcr.io/skrodahl/newton:latest
The Docker image includes Alpine Linux with nginx + PHP 8.2-FPM, all API endpoints pre-configured, and persistent tournament storage via volume mount. See Docker Quick Start for the full setup guide.
/var/www/html/
├── api/
│ ├── list-tournaments.php
│ ├── upload-tournament.php
│ ├── delete-tournament.php
│ └── relay.php
├── tournaments/
│ └── [tournament JSON files]
└── [application files]
/tournaments{TournamentName}_{YYYY-MM-DD}.jsonGET /api/list-tournaments.php
Returns a list of all tournaments in the /tournaments directory.
Success response (200):
{
"tournaments": [
{
"filename": "Summer_League_2025-08-15.json",
"name": "Summer League",
"date": "2025-08-15",
"players": 16,
"status": "completed"
}
],
"count": 1
}
Returns an empty array if no tournaments exist. Skips files that can't be read or have invalid JSON.
POST /api/upload-tournament.php
Uploads a tournament JSON file to the server.
Request body:
{
"filename": "MyTournament_2025-10-02.json",
"data": { ... tournament object ... }
}
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
filename | string | Yes | Must end with .json |
data | object | Yes | Tournament data object |
Add ?overwrite=true to the URL to replace an existing file. Without it, uploading to an existing filename returns 409 Conflict.
Success response (200):
{
"success": true,
"filename": "MyTournament_2025-10-02.json",
"path": "/tournaments/MyTournament_2025-10-02.json",
"message": "Tournament uploaded successfully"
}
Error responses:
| Status | Error | Cause |
|---|---|---|
| 400 | Invalid JSON payload | Request body is not valid JSON |
| 400 | Missing filename or data | Required fields absent |
| 400 | Filename must end with .json | Invalid extension |
| 400 | Invalid filename - contains dangerous characters | Path traversal attempt |
| 409 | Tournament file already exists | File exists, no overwrite flag |
| 500 | Failed to save tournament file | Filesystem write error |
POST /api/delete-tournament.php
Deletes a tournament file from the server.
Request body:
{
"filename": "MyTournament_2025-10-02.json"
}
Success response (200):
{
"success": true,
"filename": "MyTournament_2025-10-02.json",
"message": "Tournament deleted successfully"
}
Error responses:
| Status | Error | Cause |
|---|---|---|
| 400 | Missing filename | Required field absent |
| 400 | Filename must end with .json | Invalid extension |
| 400 | Invalid filename - contains dangerous characters | Path traversal attempt |
| 404 | Tournament file not found | File doesn't exist |
| 500 | Failed to delete tournament file | Filesystem error |
POST /api/relay.php
Forwards a tournament upload to a remote NewTon instance. The browser can’t post to another origin without CORS, so PHP makes the request server-side and adds the remote server’s API key as the X-API-Key header.
Request body:
{
"url": "https://newton.example.com/api/upload-tournament.php?overwrite=true",
"apiKey": "the-remote-servers-NEWTON_API_KEY",
"payload": {
"filename": "MyTournament_2025-10-02.json",
"data": { ... tournament object ... }
}
}
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Full URL of the remote upload endpoint |
apiKey | string | No | The remote server’s NEWTON_API_KEY, sent on as X-API-Key (v5.2.1+; replaces username and password) |
payload | object | Yes | The tournament payload (filename + data) |
The remote server’s response is passed through directly — same status code, same JSON body.
Error responses:
| Status | Error | Cause |
|---|---|---|
| 400 | Missing url or payload | Required fields absent |
| 400 | Invalid remote URL | URL validation failed |
| 502 | Could not reach remote server | Remote server unreachable |
Use case: A local Docker instance backs up tournaments to a remote server that has an API key. The browser talks to the local server (same origin, no CORS), and PHP relays to the remote server with the key.
All endpoints include CORS headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
For production deployments, restrict Access-Control-Allow-Origin to your specific domain.
The application detects server availability on page load by calling /api/list-tournaments.php. If the server responds, API features (upload button, shared tournaments list, delete buttons) are shown. If not, they are silently hidden — the app works fully without them.
config.server.allowSharedTournamentDelete is enabled.When enabled in Global Settings → Server Settings → “Automatically backup tournaments on completion”, the tournament JSON is uploaded to the local server at finalization. If a remote server is configured (Global Settings → Remote Backup), the tournament is also relayed to the remote server. Both uploads are fire-and-forget.
Any file downloaded from the server must pass the Tournament Manager’s import validation before it can be loaded. Files that don’t match the expected tournament structure are rejected by the client — they cannot affect your local tournament data.
Tournaments are stored as JSON with the following top-level structure:
{
"id": 1234567890,
"name": "My Tournament",
"date": "2025-10-02",
"created": "2025-10-02T10:30:00.000Z",
"status": "completed",
"bracketSize": 16,
"players": [ ... ],
"matches": [ ... ],
"placements": { "1": 1, "2": 2 },
"exportedAt": "2025-10-02T15:45:00.000Z"
}
Match IDs follow the format FS-1-1 (Frontside Round 1 Match 1) and BS-FINAL (Backside Final). See the export format for the full schema.
basename() — prevents directory traversal.., /, \, or null bytesNEWTON_API_KEY set, Upload, Delete, Relay and saving corrections need the X-API-Key header, or get 401 Missing or wrong API key. Reading never needs it. Without it there is no authentication, and access control is the operator’s responsibility (see Privacy Architecture for the full data model)Access-Control-Allow-Origin to your domain in productionNEWTON_API_KEY on any instance reachable from the internet, so only your venue can write to it/tournaments directory regularlyNEWTON_API_ENABLED=false if you don't need sharing[Shared Tournaments] messages/api/list-tournaments.php returns 200 OKNEWTON_API_ENABLED is set to truePHP-FPM is not running or misconfigured. Check the nginx fastcgi_pass settings and verify PHP-FPM is running inside the container.
Check that /tournaments exists and is readable (755 for directory, 644 for files). Verify the JSON files are valid.
NEWTON_API_ENABLED is set to false. Restart the container after changing environment variables.
Verify the volume mount is configured: -v ./tournaments:/var/www/html/tournaments. Check host directory permissions.
docker compose logs -f
docker compose exec newton-tournament sh