The matches API is intentionally open: any app can create a match and share its public page with players and families, with or without a connected club account. This page documents the write-protection and club-sync contract on top of that open API. All of it is optional and backward-compatible — existing integrations keep working exactly as before.
Body is the match JSON payload. id is optional — a UUID is generated if omitted.
Response 201
{
"id": "abc123",
"url": "https://tactiboard.es/match/abc123"
}
To protect a match from being overwritten by someone else, generate a random token
on the device before the first request and send it as
X-Write-Token on the POST that creates it:
POST /api/matches
X-Write-Token: 9f2c1a...e7
Content-Type: application/json
{ "id": "abc123", ... }
POST with the same id and the
same X-Write-Token simply upserts the match, instead of locking you out
with a 403. Apps that don't send this header are unaffected: the match
is created normally and behaves like a legacy match (see below).
id as an existing match)Both behave the same way once a match exists: the stored data is replaced with the new payload (upsert).
| Match state | Header required | Result |
|---|---|---|
| Doesn't exist yet (PUT-create) | — | Created, unprotected — same as today |
Exists, created without X-Write-Token (legacy) | — | Updated normally — no protection |
Exists, created with X-Write-Token | X-Write-Token: <token> | Updated only if the token matches; otherwise 403 |
PUT /api/matches/abc123
X-Write-Token: 9f2c1a...e7
Content-Type: application/json
{ "id": "abc123", ... }
X-Write-Token on the
POST that created it. Matches created without that header — including
via the older PUT-create flow — remain unprotected: anyone with the id can still
update them, exactly as before.
If the payload includes clubId and teamId (plus optionally
season, category, competition,
playerStats, actions), the API syncs this data into the
connected club's dashboard — players, stats, and match actions.
To authenticate this sync, send your club's app key (Dashboard → Connect → Generate App Key):
X-App-Key: <your club's app key>
X-App-Key, the match itself is still saved and its
public page works as normal. The CRM sync may be skipped, in which case the response
includes "crmSync": "skipped_auth". Until this is fully enforced,
unauthenticated syncs are logged but still processed — check
Dashboard → Connect → Sync status for details.
X-Write-Token, X-App-Key, crmSync) are additive — older clients that ignore them keep working unchanged.X-Write-Token header.GET /match/:id) and viewing API (GET /api/matches/:id) require no authentication, as before.