Developer Platform

Open Scoreboard API

Technical documentation for the current v1 REST API, with copyable examples, route-by-route notes, and authenticated personal API key management.

Public developer docs

Matches

Matches are the live orchestration layer for scheduling, moving, replacing, and cancelling pending table work.

Available in v1

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.

Single syncPUT /matches or PATCH /matches/{externalID}
Bulk syncPOST /matches/bulk
Bulk cancelPOST /matches/bulk/cancel

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
PUT/matches

Create or replace one pending match

Synchronize a pending match keyed by partner externalID and assign it to a table and player list.

matches.write
Response

Returns the synchronized match resource.

curl
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" }
  }'
JavaScript
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" }
  })
});
PATCH/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.

matches.write
Response

Returns the updated match.

curl
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"
  }'
JavaScript
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"
  })
});
POST/matches/bulk

Synchronize up to 100 matches

Batch-create or replace pending matches with per-match results.

matches.write
Response

Returns succeeded and failed counts with ordered results; may respond with HTTP 207.

curl
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" }
      }
    ]
  }'
JavaScript
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);
POST/matches/bulk/cancel

Cancel pending matches in bulk

Remove up to 100 pending matches by externalID.

matches.write
Response

Returns per-match results and may respond with HTTP 207 for partial success.

curl
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"]
  }'
JavaScript
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"]
  })
});