[TOC]
Exhaustive classes and fields diagram.
erDiagram
%% entities
Tournament {
int id
Type type
string name
string shortName
date startDate
date endDate
string director
string country
string location
bool online
int rounds
int gobanSize
Rules rules
double komi
}
TimeSystem {
TimeSystemType type
int mainTime
int increment
int maxTime
int byoyomi
int periods
int stones
}
Pairing {
PairingType type
PairingParams pairingParams
PlacementParams placementParams
}
Game {
int id
int table
int handicap
Result result
int drawnUpDown
bool forcedTable
}
Player {
int id
string name
string firstname
string country
string club
int rating
int rank
bool final
int mmsCorrection
set skip
map externalIds
bool licensed
}
Team {
int id
string name
set playerIds
int rating
int rank
bool final
int mmsCorrection
set skip
}
Standings {
list criteria
}
%% relationships
Tournament ||--|{ TimeSystem: "time system"
Tournament ||--|{ Pairing: "pairing"
Tournament ||--|{ Game: "round"
Tournament }o--|{ Player: "players"
Tournament }o--|{ Team: "teams"
Team }o--|{ Player: "members"
Game ||--|| Player: "black"
Game ||--|| Player: "white"
Player }|--|| Standings: "position"
Sealed class hierarchy for different tournament formats.
| Field | Type | Description |
|---|---|---|
| id | int | Tournament identifier |
| type | Type | Tournament format |
| name | string | Full tournament name |
| shortName | string | Abbreviated name |
| startDate | date | Start date |
| endDate | date | End date |
| director | string | Tournament director |
| country | string | Country code (default: "fr") |
| location | string | Venue location |
| online | bool | Is online tournament |
| rounds | int | Total number of rounds |
| gobanSize | int | Board size (default: 19) |
| rules | Rules | Scoring rules |
| komi | double | Komi value (default: 7.5) |
| timeSystem | TimeSystem | Time control |
| pairing | Pairing | Pairing system |
| tablesExclusion | list | Table exclusion rules per round |
| Type | Players/Team | Description |
|---|---|---|
| INDIVIDUAL | 1 | Individual players |
| PAIRGO | 2 | Pair Go (alternating) |
| RENGO2 | 2 | Rengo with 2 players |
| RENGO3 | 3 | Rengo with 3 players |
| TEAM2 | 2 | Team with 2 boards |
| TEAM3 | 3 | Team with 3 boards |
| TEAM4 | 4 | Team with 4 boards |
| TEAM5 | 5 | Team with 5 boards |
AGA- American Go AssociationFRENCH- French Go AssociationJAPANESE- Japanese rulesCHINESE- Chinese rules
Individual tournament participant.
| Field | Type | Description |
|---|---|---|
| id | int | Player identifier |
| name | string | Last name |
| firstname | string | First name |
| country | string | Country code |
| club | string | Club affiliation |
| rating | int | EGF-style rating |
| rank | int | Rank (-30=30k to 8=9D) |
| final | bool | Is registration confirmed |
| mmsCorrection | int | MacMahon score correction |
| skip | set | Skipped round numbers |
| externalIds | map | External IDs (AGA, EGF, FFG) |
| licensed | bool | FFG licence up to date (FR snapshot); absent = unknown |
Team participant (for team tournaments).
| Field | Type | Description |
|---|---|---|
| id | int | Team identifier |
| name | string | Team name |
| playerIds | set | Member player IDs |
| rating | int | Computed from members |
| rank | int | Computed from members |
| final | bool | Is registration confirmed |
| mmsCorrection | int | MacMahon score correction |
| skip | set | Skipped round numbers |
Single game in a round.
| Field | Type | Description |
|---|---|---|
| id | int | Game identifier |
| table | int | Table number (0 = unpaired) |
| white | int | White player ID (0 = bye) |
| black | int | Black player ID (0 = bye) |
| handicap | int | Handicap stones |
| result | Result | Game outcome |
| drawnUpDown | int | DUDD value |
| forcedTable | bool | Is table manually assigned |
| Code | Description |
|---|---|
| ? | Unknown (not yet played) |
| w | White won |
| b | Black won |
| = | Jigo (draw) |
| X | Cancelled |
| # | Both win (unusual) |
| 0 | Both lose (unusual) |
Time control configuration.
| Field | Type | Description |
|---|---|---|
| type | TimeSystemType | System type |
| mainTime | int | Main time in seconds |
| increment | int | Fischer increment |
| maxTime | int | Fischer max time |
| byoyomi | int | Byoyomi time per period |
| periods | int | Number of byoyomi periods |
| stones | int | Stones per period (Canadian) |
| Type | Description |
|---|---|
| CANADIAN | Canadian byoyomi |
| JAPANESE | Japanese byoyomi |
| FISCHER | Fischer increment |
| SUDDEN_DEATH | No overtime |
Pairing system configuration.
| Type | Description |
|---|---|
| SWISS | Swiss system |
| MAC_MAHON | MacMahon system |
| ROUND_ROBIN | Round robin (not implemented) |
| Field | Type | Description |
|---|---|---|
| mmFloor | int | MacMahon floor (default: -20 = 20k) |
| mmBar | int | MacMahon bar (default: 0 = 1D) |
| Parameter | Description |
|---|---|
| nx1 | Concavity curve factor (0.0-1.0) |
| dupWeight | Duplicate game avoidance weight |
| random | Randomization factor |
| deterministic | Deterministic pairing |
| colorBalanceWeight | Color balance importance |
| byeWeight | Bye assignment weight |
| Parameter | Description |
|---|---|
| categoriesWeight | Avoid mixing categories |
| scoreWeight | Minimize score differences |
| drawUpDownWeight | Draw-up/draw-down weighting |
| compensateDrawUpDown | Enable DUDD compensation |
| drawUpDownUpperMode | TOP, MIDDLE, or BOTTOM |
| drawUpDownLowerMode | TOP, MIDDLE, or BOTTOM |
| seedingWeight | Seeding importance |
| lastRoundForSeedSystem1 | Round cutoff for system 1 |
| seedSystem1 | First seeding method |
| seedSystem2 | Second seeding method |
| mmsValueAbsent | MMS for absent players |
| roundDownScore | Floor vs round scores |
SPLIT_AND_FOLDSPLIT_AND_RANDOMSPLIT_AND_SLIP
| Parameter | Description |
|---|---|
| barThresholdActive | Don't apply below bar |
| rankSecThreshold | Rank limit for criteria |
| nbWinsThresholdActive | Score threshold |
| defSecCrit | Secondary criteria weight |
| Parameter | Description |
|---|---|
| avoidSameGeo | Avoid same region |
| preferMMSDiffRatherThanSameCountry | Country preference |
| preferMMSDiffRatherThanSameClubsGroup | Club group preference |
| preferMMSDiffRatherThanSameClub | Club preference |
| Parameter | Description |
|---|---|
| weight | Handicap minimization weight |
| useMMS | Use MMS vs rank |
| rankThreshold | Rank threshold |
| correction | Handicap reduction |
| ceiling | Max handicap stones |
Tiebreak criteria for standings, in order of priority.
| Criterion | Description |
|---|---|
| NBW | Number of wins |
| MMS | MacMahon score |
| STS | Strasbourg score |
| CPS | Cup score |
| SCOREX | Congress score |
| Criterion | Description |
|---|---|
| SOSW / SOSM | Sum of opponent scores |
| SOSWM1 / SOSMM1 | SOS minus worst |
| SOSWM2 / SOSMM2 | SOS minus two worst |
| SODOSW / SODOSM | Sum of defeated opponent scores |
| SOSOSW / SOSOSM | Sum of opponent SOS |
| CUSSW / CUSSM | Cumulative score sum |
| Criterion | Description |
|---|---|
| CATEGORY | Player category |
| RANK | Player rank |
| RATING | Player rating |
| DC | Direct confrontation |
| SDC | Simplified direct confrontation |
| EXT | Exploits attempted |
| EXR | Exploits successful |
Player IDs can be linked to external rating databases:
| Database | Description |
|---|---|
| AGA | American Go Association |
| EGF | European Go Federation |
| FFG | French Go Association |
How to tune the pairgoth.properties file.
Pairgoth general configuration is done using the pairgoth.properties file in the installation folder.
Properties are loaded in this order (later overrides earlier):
- Default properties embedded in WAR/JAR
- User properties file (
./pairgoth.properties) in current working directory - System properties prefixed with
pairgoth.(command-line:-Dpairgoth.key=value)
Controls the running environment.
env = prod
Values:
dev- Development mode: enables CORS headers and additional loggingprod- Production: for distributed instances
Running mode for the application.
mode = standalone
Values:
standalone- Both web and API in a single process (default for jar execution)server- API onlyclient- Web UI only (connects to remote API)
Authentication method for the application.
auth = none
Values:
none- No authentication requiredsesame- Shared unique passwordoauth- Email and/or OAuth accounts
When running in client or server mode with authentication enabled:
auth.shared_secret = <16 ascii characters string>
This secret is shared between API and View webapps. Auto-generated in standalone mode.
When using sesame authentication:
auth.sesame = <password>
When using OAuth authentication:
oauth.providers = ffg,google,facebook
Comma-separated list of enabled providers: ffg, facebook, google, instagram, twitter
For each enabled provider, configure credentials:
oauth.<provider>.client_id = <client_id>
oauth.<provider>.secret = <client_secret>
Example:
oauth.ffg.client_id = your-ffg-client-id
oauth.ffg.secret = your-ffg-client-secret
oauth.google.client_id = your-google-client-id
oauth.google.secret = your-google-client-secret
Pairgoth webapp (UI) connector configuration.
webapp.protocol = http
webapp.host = localhost
webapp.port = 8080
webapp.context = /
webapp.external.url = http://localhost:8080
webapp.host(orwebapp.interface) - Hostname/interface to bind towebapp.external.url- External URL for OAuth redirects and client configuration
Pairgoth API connector configuration.
api.protocol = http
api.host = localhost
api.port = 8085
api.context = /api
api.external.url = http://localhost:8085/api
Note: In standalone mode, API port defaults to 8080 and context to /api/tour.
For HTTPS connections:
webapp.ssl.key = path/to/localhost.key
webapp.ssl.cert = path/to/localhost.crt
webapp.ssl.pass = <key passphrase>
Supports jar: URLs for embedded resources.
Persistent storage for tournaments.
store = file
store.file.path = tournamentfiles
Values for store:
file- Persistent XML files (default)memory- RAM-based (mainly for tests)
The store.file.path is relative to the current working directory.
ratings.path = ratings
Directory for caching downloaded ratings files.
For each rating source (aga, egf, ffg):
ratings.<source> = <url>
URL override for the rating source. Schemes: http://, https://, file://. If not set, the built-in default URL for that source is used:
- FFG: https://ffg.jeudego.org/echelle/echtxt/ech_ffg_V3.txt
- EGF: https://pairgoth.jeudego.org/egd/allworld_lp.zip (pairgoth-hosted mirror, zipped)
- AGA: TBD
Use this property to point at a local mirror or to a static file when the upstream is unreachable.
ratings.date = YYYY-MM-DD
Upper bound for the ratings snapshot to use, applied globally to all sources. Designed for multi-day events where ratings must not drift mid-tournament.
Behaviour:
ratings.datenot set, or today is before it: dynamic — latest ratings are fetched as usual.- Today is on or after
ratings.date: load the most recent cached snapshot whose date is ≤ratings.date. Cache files past the freeze are kept on disk but ignored at load time. - Until a cached snapshot dated ≥
ratings.dateexists, hourly fetches continue (so the cache grows toward the freeze date). Once one exists, fetches stop for that source. - Set in advance and forget: configure on day -N, freeze takes effect automatically on day 0.
ratings.<source>.enable = true | false
Whether to display the rating source button in the Add Player popup.
ratings.<source>.show = true | false
Whether to show player IDs from this rating source on the registration page.
enable makes the source available in the Add Player search
Defaults when unset:
- EGF: enabled and shown everywhere
- FFG: enabled and shown only for French tournaments
- Other sources (e.g. AGA): off
SMTP configuration for email notifications. Not yet functional.
smtp.sender = sender@example.com
smtp.host = smtp.example.com
smtp.port = 587
smtp.user = username
smtp.password = password
Logging configuration.
logger.level = info
logger.format = [%level] %ip [%logger] %message
Log levels: trace, debug, info, warn, error
Format placeholders: %level, %ip, %logger, %message
console.color = true | false
ANSI colour in the stdout log. Unset = auto-detect: on for a colour-capable terminal (including Windows Terminal), off for legacy consoles, redirected output and services. The NO_COLOR / FORCE_COLOR environment variables are also honoured.
Pairgoth can push content (pairings, results, standings) to an external tournament website, and pull registered players from it. See Pairgoth Webhook specification for the endpoint contract a consumer must implement.
webhook.url = https://my-tournament-site.example/api/pairgoth
webhook.secret = a-shared-secret
webhook.url— base URL of the consumer's pairgoth-integration endpoint.webhook.secret— shared secret sent on every request asX-Pairgoth-Secretheader
Behavior:
- If
webhook.urlis unset or blank, no webhook integration runs and the related UI buttons (Sync from website, Publish pairings/results/standings) are not shown. - If
webhook.urlis set, pairgoth performs aGET /healthagainst it at startup. A failed health check is a fatal startup error — the assumption being that the tournament site is supposed to already be running when the tournament director launches pairgoth.
display.pairing.blackFirst = false
When true, pairings are shown as "Black vs White" instead of the default "White vs Black".
session.timeout.minutes = 240
Overrides the HTTP session idle-timeout default (in minutes).
env = dev
mode = standalone
auth = none
store = file
store.file.path = tournamentfiles
logger.level = traceServer (API):
env = prod
mode = server
auth = oauth
auth.shared_secret = 1234567890abcdef
api.port = 8085
store = file
store.file.path = /var/tournaments
logger.level = infoClient (Web UI):
env = prod
mode = client
auth = oauth
auth.shared_secret = 1234567890abcdef
oauth.providers = ffg,google
oauth.ffg.client_id = your-ffg-id
oauth.ffg.secret = your-ffg-secret
oauth.google.client_id = your-google-id
oauth.google.secret = your-google-secret
webapp.port = 8080
api.external.url = http://api-server:8085/apiTo develop your own tools.
The API expects an Accept header of application/json, with no encoding or an UTF-8 encoding. Exceptions are some export operations which can have different MIME types to specify the expected format:
application/json- JSON output (default)application/xml- OpenGotha XML exportapplication/egf- EGF formatapplication/ffg- FFG formattext/csv- CSV format
GET requests return either an array or an object, as specified below.
POST, PUT and DELETE requests return either the 200 HTTP code with { "success": true } (with an optional "id" field for some POST requests), or an invalid HTTP code and (for some errors) the body { "success": false, "error": <error message> }.
All POST/PUT/DELETE requests use read/write locks for concurrency. GET requests use read locks.
When authentication is enabled, all requests require an Authorization header.
- /api/tour GET POST Tournaments handling
- /api/tour/#tid GET PUT DELETE Tournaments handling
- /api/tour/#tid/part GET POST Registration handling
- /api/tour/#tid/part/#pid GET PUT DELETE Registration handling
- /api/tour/#tid/team GET POST Team handling
- /api/tour/#tid/team/#tid GET PUT DELETE Team handling
- /api/tour/#tid/pair/#rn GET POST PUT DELETE Pairing
- /api/tour/#tid/res/#rn GET PUT DELETE Results
- /api/tour/#tid/standings GET PUT Standings
- /api/tour/#tid/stand/#rn GET Standings
- /api/tour/#tid/explain/#rn GET Pairing explanation
- /api/token GET POST DELETE Authentication
-
GET /api/tourGet a list of known tournaments idsoutput json map (id towards shortName) of known tournaments
-
GET /api/tour/#tidGet the details of tournament #tidoutput json object for tournament #tid
Supports
Accept: application/xmlto get OpenGotha XML export. -
POST /api/tourCreate a new tournamentinput json object for new tournament, or OpenGotha XML with
Content-Type: application/xmlTournament JSON structure:
{ "type": "INDIVIDUAL", "name": "Tournament Name", "shortName": "TN", "startDate": "2024-01-15", "endDate": "2024-01-16", "country": "fr", "location": "Paris", "online": false, "rounds": 5, "gobanSize": 19, "rules": "FRENCH", "komi": 7.5, "timeSystem": { ... }, "pairing": { ... } }Tournament types:
INDIVIDUAL,PAIRGO,RENGO2,RENGO3,TEAM2,TEAM3,TEAM4,TEAM5output
{ "success": true, "id": #tid } -
PUT /api/tour/#tidModify a tournamentinput json object for updated tournament (only id and updated fields required)
output
{ "success": true } -
DELETE /api/tour/#tidDelete a tournamentoutput
{ "success": true }
-
GET /api/tour/#tid/partGet a list of registered playersoutput json array of known players
-
GET /api/tour/#tid/part/#pidGet registration details for player #pidoutput json object for player #pid
-
POST /api/tour/#tid/partRegister a new playerinput
{ "name": "Lastname", "firstname": "Firstname", "rating": 1500, "rank": -5, "country": "FR", "club": "Club Name", "final": true, "mmsCorrection": 0, "egfId": "12345678", "ffgId": "12345", "agaId": "12345" }Rank values: -30 (30k) to 8 (9D). Rating in EGF-style (100 = 1 stone).
output
{ "success": true, "id": #pid } -
PUT /api/tour/#tid/part/#pidModify a player registrationinput json object for updated registration (only id and updated fields required)
output
{ "success": true } -
DELETE /api/tour/#tid/part/#pidDelete a player registrationoutput
{ "success": true }
For team tournaments (PAIRGO, RENGO2, RENGO3, TEAM2-5).
-
GET /api/tour/#tid/teamGet a list of registered teamsoutput json array of known teams
-
GET /api/tour/#tid/team/#teamidGet registration details for team #teamidoutput json object for team #teamid
-
POST /api/tour/#tid/teamRegister a new teaminput
{ "name": "Team Name", "playerIds": [1, 2, 3], "final": true, "mmsCorrection": 0 }output
{ "success": true, "id": #teamid } -
PUT /api/tour/#tid/team/#teamidModify a team registrationinput json object for updated registration (only id and updated fields required)
output
{ "success": true } -
DELETE /api/tour/#tid/team/#teamidDelete a team registrationoutput
{ "success": true }
-
GET /api/tour/#tid/pair/#rnGet pairable players for round #rnoutput
{ "games": [ { "id": 1, "t": 1, "w": 2, "b": 3, "h": 0 }, ... ], "pairables": [ 4, 5, ... ], "unpairables": [ 6, 7, ... ] }games: existing pairings for the roundpairables: player IDs available for pairing (not skipping, not already paired)unpairables: player IDs skipping the round
-
POST /api/tour/#tid/pair/#rnGenerate pairing for round #rninput
[ "all" ]or[ #pid, ... ]Optional query parameters:
legacy=true- Use legacy pairing algorithmweights_output=<file>- Output weights to file for debuggingappend=true- Append to weights output file
output
[ { "id": #gid, "t": table, "w": #wpid, "b": #bpid, "h": handicap }, ... ] -
PUT /api/tour/#tid/pair/#rnManual pairing or table renumberingFor manual pairing: input
{ "id": #gid, "w": #wpid, "b": #bpid, "h": <handicap> }For table renumbering: input
{ "renumber": <game_id or null>, "orderBy": "mms" | "table" }output
{ "success": true } -
DELETE /api/tour/#tid/pair/#rnDelete pairing for round #rninput
[ "all" ]or[ #gid, ... ]Games with results already entered are skipped unless
"all"is specified.output
{ "success": true }
-
GET /api/tour/#tid/res/#rnGet results for round #rnoutput
[ { "id": #gid, "res": <result> }, ... ]Result codes:
"w"- White won"b"- Black won"="- Jigo (draw)"X"- Cancelled"?"- Unknown (not yet played)"#"- Both win (unusual)"0"- Both lose (unusual)
-
PUT /api/tour/#tid/res/#rnSave a resultinput
{ "id": #gid, "res": <result> }output
{ "success": true } -
DELETE /api/tour/#tid/res/#rnClear all results for roundoutput
{ "success": true }
-
GET /api/tour/#tid/standingsGet standings after final roundoutput
[ { "id": #pid, "place": place, "<crit>": value }, ... ]Supports multiple output formats via Accept header:
application/json- JSON (default)application/egf- EGF formatapplication/ffg- FFG formattext/csv- CSV format
Optional query parameters:
include_preliminary=true- Include preliminary standingsindividual_standings=true- For team tournaments with individual scoring
-
GET /api/tour/#tid/stand/#rnGet standings after round #rnUse round
0for initial standings.output
[ { "id": #pid, "place": place, "<crit>": value }, ... ]Criteria names include:
nbw,mms,sts,cps,sosw,sosm,sososw,sososm,sodosw,sodosm,cussw,cussm,dc,sdc,ext,exr, etc. -
PUT /api/tour/#tid/standingsFreeze/lock standingsoutput
{ "success": true }
-
GET /api/tour/#tid/explain/#rnGet detailed pairing criteria weights for round #rnoutput Detailed pairing weight analysis and criteria breakdown
Used for debugging and understanding pairing decisions.
-
GET /api/tokenCheck authentication statusoutput Token information for the currently logged user, or error if not authenticated.
-
POST /api/tokenCreate an access tokeninput Authentication credentials (format depends on auth mode)
output
{ "success": true, "token": "..." } -
DELETE /api/tokenLogout / revoke tokenoutput
{ "success": true }
To integrate pairgoth with a tournament website.
The webhook is the inverse direction of the Pairgoth API: pairgoth is the client, the tournament website is the server. The direction is fixed because the tournament director can run pairgoth on a venue laptop behind some NAT or firewall — the website cannot reach pairgoth, but pairgoth can reach the website outbound.
Pairgoth's role:
- Pull registered players from the website on demand (Sync from website).
- Push content (pairings, results, standings) to the website when the operator clicks a Publish button.
Configuration is described in Webhook under the Configuration section. When webhook.url is set, pairgoth
performs GET <webhook.url>/health at startup; a failed check is fatal.
All requests carry an X-Pairgoth-Secret header equal to the value of webhook.secret. The website must validate
it on every endpoint and return 401 on mismatch.
JSON responses follow the shape { "status": true | false, "message"?: string, … }. A false status reaches the pairgoth UI as the error message.
The path component {code} is the tournament's shortName — used as a stable, human-meaningful identifier on the website side. {round} is a 1-based round number.
Pairgoth calls the following endpoints, all relative to webhook.url:
- /health GET Auth-gated health check
- /players/{code} GET Pull registered players
- /pairings/{code}/{round} POST Push pairings or results HTML
- /standings/{code}/{round} POST Push standings HTML
-
GET /health— health check used at pairgoth startup.output
{ "status": true, "name"?: string, "version"?: string }The
nameis shown in pairgoth's startup log line (webhook at <url> healthy: <name>) — useful for the operator to confirm the right backend.
-
GET /players/{code}— return all players registered for the tournament.output
{ "status": true, "players": [ { ... }, ... ] }on success;{ "status": false, "message": string }on error (e.g. event not found → 404).Player JSON shape (each entry):
{ "id": 12345, // stable per-website id (used by pairgoth as DatabaseId.EXT) "lastname": "Doe", "firstname": "Jane", "country": "FR", // ISO-3166 alpha-2; pairgoth normalizes GB → UK "club": "75Pa", "rank": "5k", // string: "30k".."1k", "1d".."9d", "1p".."9p" (1p..9p doubles as pro flag) "rating": 1850, // optional; if absent, pairgoth derives a default from rank "pin": "12345678", // optional EGF PIN — used as DatabaseId.EGF "rounds": "1111100000" // optional, one char per round; '1' = playing, '0' = skip }The
idfield is the website's primary key for that player, typically a registration id. Pairgoth stores it asexternalIds[EXT]and uses it as the primary deduplication key on re-sync — taking precedence overpin, since a PIN entered wrong on the source side may be corrected later. Without anid, players without a PIN duplicate on every re-sync.Re-sync semantics. Pairgoth matches each website player against existing registrants by external id (EXT > EGF > FFG > AGA). Unmatched players are inserted; matched players are updated last-wins on rank, rating, club, country, name and round participation (
rounds) — the website is treated as the source of truth. Players whose payload is identical to the current registration are counted as unchanged. The operator gets a multi-line report (X added / Y updated / Z unchanged / …) at the end.One safeguard is server-enforced: pairgoth refuses to drop a player from a round in which they are already paired. Such rejections are surfaced separately as
N blocked — already paired: <name> (round R), …so the operator can spot a misordered flow. The intended procedure is freeze the round on the website first, then resync — that prevents the website from shipping aroundsmask that excludes a paired player.
-
POST /pairings/{code}/{round}— receive pairings or results for a round.Content-Type
text/html; charset=UTF-8body HTML fragment, see Published payload.
output
{ "status": true }on success;{ "status": false, "message": string }on error.Both the Pairings tab's "Publish to website" and the Results tab's "Publish to website" hit this endpoint. The Results variant differs only in that its rendered table includes a
Resultcolumn. The website should overwrite previous content for(code, round)on each call.For TEAM tournaments, the Pairings publish renders team-vs-team rows and the Results publish renders the per-board breakdown. Both ship to the same endpoint; the website sees whichever was published most recently.
-
POST /standings/{code}/{round}— receive standings as of a given round.Content-Type
text/html; charset=UTF-8body HTML fragment, see Published payload. For TEAM* tournaments, the body contains both the team standings table and the individual standings table, each wrapped in a
<div class="standings-section team-standings">/<div class="standings-section individual-standings">.output
{ "status": true }on success;{ "status": false, "message": string }on error.
The HTML pushed to /pairings/... and /standings/... is self-contained: it carries its own <style> block and is wrapped in a .pairgoth-published container, so the website can drop it into any container without writing CSS:
<style>@layer pairgoth-published {
/* table sizing, headers, zebra rows, etc. */
}</style>
<div class="pairgoth-published">
<!-- table or sections -->
</div>Properties:
- The styles are inside a CSS
@layernamedpairgoth-published. Unlayered styles in the consuming page take precedence over the layer, so the website's own theme wins automatically — the published styles only show through where the website hasn't styled. - All selectors are scoped under
.pairgoth-published, so they don't leak. - Each result cell carries
data-result="<code>"where<code>is one of?wb=X#0. The website can re-style results without parsing the rendered string (1-0/½-½etc.). - Each table row's
td.tcell carriesdata-table="<n>"(the table number).
For all webhook endpoints, error responses should return:
401for invalid or missingX-Pairgoth-Secret.404for unknown{code}(event not found on the website).4xx/5xxwith body{ "status": false, "message": string }for other errors. Themessageis surfaced in the pairgoth UI.
If the website returns a non-2xx with no body, pairgoth synthesizes a { "status": false, "message": "upstream <status> with no body for <url>" } so the browser-side parser does not choke.