Matches
Matches are the live orchestration layer for scheduling, moving, replacing, and cancelling pending table work.
Use PUT /matches for idempotent single-match sync, POST /matches/bulk for up to 100 match syncs, PATCH on record or bulk routes to move work between tables, and DELETE or bulk cancel for pending-match removal.
Implementation notes
- Active or completed matches are locked and can return HTTP 409 on update or delete attempts.
- PATCH /matches/{externalID} treats a body containing only tableID as a move request.
- Bulk match routes preserve request ordering within each destination table.
Endpoints
OpenAPI/matchesCreate or replace one pending match
Synchronize a pending match keyed by partner externalID and assign it to a table and player list.
curl -X PUT https://your-domain.example/api/v1/matches \
-H "Authorization: Bearer osb_test_your_key_here" \
-H "Content-Type: application/json" \
-H "OpenScoreboard-Request-ID: demo-request-001" \
-d '{
"externalID": "match-5001",
"tableID": "table-1",
"playerListExternalID": "summer-open-singles",
"eventName": "Summer Open",
"matchRound": "Quarterfinal",
"bestOf": 5,
"playerA": { "externalID": "player-1001" },
"playerB": { "externalID": "player-1002" }
}'const baseURL = "https://your-domain.example/api/v1";
const apiKey = "osb_test_your_key_here";
async function api(path, init = {}) {
const response = await fetch(`${baseURL}${path}`, {
...init,
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"OpenScoreboard-Request-ID": "demo-request-001",
...(init.headers || {})
}
});
const body = await response.json();
if (!response.ok) {
throw new Error(body.error?.message || "Request failed");
}
return body.data;
}
await api("/matches", {
method: "PUT",
body: JSON.stringify({
externalID: "match-5001",
tableID: "table-1",
playerListExternalID: "summer-open-singles",
eventName: "Summer Open",
matchRound: "Quarterfinal",
bestOf: 5,
playerA: { externalID: "player-1001" },
playerB: { externalID: "player-1002" }
})
});/matches/{externalID}Move or replace a single match
Send only tableID to move a pending match, or send a full match payload to replace it.
curl -X PATCH https://your-domain.example/api/v1/matches/match-5001 \
-H "Authorization: Bearer osb_test_your_key_here" \
-H "Content-Type: application/json" \
-H "OpenScoreboard-Request-ID: demo-request-001" \
-d '{
"tableID": "table-2"
}'const baseURL = "https://your-domain.example/api/v1";
const apiKey = "osb_test_your_key_here";
async function api(path, init = {}) {
const response = await fetch(`${baseURL}${path}`, {
...init,
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"OpenScoreboard-Request-ID": "demo-request-001",
...(init.headers || {})
}
});
const body = await response.json();
if (!response.ok) {
throw new Error(body.error?.message || "Request failed");
}
return body.data;
}
await api("/matches/match-5001", {
method: "PATCH",
body: JSON.stringify({
tableID: "table-2"
})
});/matches/bulkSynchronize up to 100 matches
Batch-create or replace pending matches with per-match results.
curl -X POST https://your-domain.example/api/v1/matches/bulk \
-H "Authorization: Bearer osb_test_your_key_here" \
-H "Content-Type: application/json" \
-H "OpenScoreboard-Request-ID: demo-request-001" \
-d '{
"matches": [
{
"externalID": "match-5001",
"tableID": "table-1",
"playerListExternalID": "summer-open-singles",
"playerA": { "externalID": "player-1001" },
"playerB": { "externalID": "player-1002" }
},
{
"externalID": "match-5002",
"tableID": "table-2",
"playerListExternalID": "summer-open-singles",
"playerA": { "externalID": "player-1003" },
"playerB": { "externalID": "player-1004" }
}
]
}'const baseURL = "https://your-domain.example/api/v1";
const apiKey = "osb_test_your_key_here";
async function api(path, init = {}) {
const response = await fetch(`${baseURL}${path}`, {
...init,
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"OpenScoreboard-Request-ID": "demo-request-001",
...(init.headers || {})
}
});
const body = await response.json();
if (!response.ok) {
throw new Error(body.error?.message || "Request failed");
}
return body.data;
}
const batch = await api("/matches/bulk", {
method: "POST",
body: JSON.stringify({
matches: [
{
externalID: "match-5001",
tableID: "table-1",
playerListExternalID: "summer-open-singles",
playerA: { externalID: "player-1001" },
playerB: { externalID: "player-1002" }
},
{
externalID: "match-5002",
tableID: "table-2",
playerListExternalID: "summer-open-singles",
playerA: { externalID: "player-1003" },
playerB: { externalID: "player-1004" }
}
]
})
});
console.log(batch.succeeded, batch.failed);/matches/bulk/cancelCancel pending matches in bulk
Remove up to 100 pending matches by externalID.
curl -X POST https://your-domain.example/api/v1/matches/bulk/cancel \
-H "Authorization: Bearer osb_test_your_key_here" \
-H "Content-Type: application/json" \
-H "OpenScoreboard-Request-ID: demo-request-001" \
-d '{
"externalIDs": ["match-5001", "match-5002"]
}'const baseURL = "https://your-domain.example/api/v1";
const apiKey = "osb_test_your_key_here";
async function api(path, init = {}) {
const response = await fetch(`${baseURL}${path}`, {
...init,
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"OpenScoreboard-Request-ID": "demo-request-001",
...(init.headers || {})
}
});
const body = await response.json();
if (!response.ok) {
throw new Error(body.error?.message || "Request failed");
}
return body.data;
}
await api("/matches/bulk/cancel", {
method: "POST",
body: JSON.stringify({
externalIDs: ["match-5001", "match-5002"]
})
});