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 8080: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. Used when the browser can’t make cross-origin requests with basic auth (CORS preflight blocks authenticated OPTIONS requests). PHP handles the remote request server-side — no CORS, no preflight, auth works naturally.
Request body:
{
"url": "https://newton.example.com/api/upload-tournament.php?overwrite=true",
"username": "NewTon",
"password": "tournament",
"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 |
username | string | No | Basic auth username for the remote server |
password | string | No | Basic auth password for the remote server |
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 with basic auth. The browser talks to the local server (same origin, no CORS), and PHP relays to the remote server with credentials.
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 bytesAccess-Control-Allow-Origin to your domain in production/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