iRefFoos developer reference

Keep the game
in play.

A field guide to the iRefFoos API for the people calling the match, bringing the stream to life, and building what comes next.

Download OpenAPI OpenAPI JSON · generated from published operations
THE PLAYBOOK

Endpoints & schemas

Published API

Published HTTP API documentation. Examples are synthetic; replace placeholder credentials with your own.

Base URLs
[
  {
    "url": "/",
    "description": "This iRefFoos server"
  }
]

GET /api/qrcode

Generate a QR image from a URL

Returns a 300-pixel-wide PNG image, not a JSON body. Save the response to a .png file. url is required. Authentication: Unauthenticated; no caller credentials are required.

Parameters

url · query · required

Text/URL to encode; no navigation or upstream fetch is performed.

{
  "name": "url",
  "in": "query",
  "description": "Text/URL to encode; no navigation or upstream fetch is performed.",
  "schema": {
    "type": "string"
  },
  "example": "https://example.com/stats/DEMO2345",
  "required": true
}

Responses

200 — PNG image bytes (synthetic example is a PNG signature representation, not JSON).
{
  "description": "PNG image bytes (synthetic example is a PNG signature representation, not JSON).",
  "content": {
    "image/png": {
      "schema": {
        "type": "string",
        "format": "binary"
      },
      "examples": {
        "synthetic": {
          "summary": "Binary PNG response",
          "value": "\\x89PNG\\r\\n\\x1a\\n…"
        }
      }
    }
  }
}
400 — url parameter required
{
  "description": "url parameter required",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "url parameter required"
          }
        }
      }
    }
  }
}
500 — Failed to generate QR code
{
  "description": "Failed to generate QR code",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Failed to generate QR code"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request GET "$BASE_URL/api/qrcode?url=https%3A%2F%2Fexample.com%2Fstats%2FDEMO2345"

GET /api/remote/{command}

Dispatch a remote command (legacy GET; modifies data)

Dispatches a command to an active remote_control host. Success confirms dispatch, not that the host applied it. Prefer POST: GET also changes state and must not be prefetched. Query deviceId takes precedence over JSON, but conflicting selectors are rejected. score_left/right aliases resolve to score_home/away. zone is accepted only by score-up commands and sends a one-shot goal, not a prepared confirmation. Only deviceId and zone are forwarded from request fields; arbitrary payload, scores or match IDs in JSON are not forwarded by this HTTP handler. Authentication: Requires an issued API key via X-API-Key (recommended) or legacy apiKey query. Missing/invalid keys return 401; revoked/expired keys and devices outside a scoped key’s grants return 403. Unrestricted keys can target any device. Alias resolution precedes authorization. Headers keep keys out of query URLs.

Parameters

command · path · required

Command to dispatch. Left/right scoring aliases map to home/away.

{
  "name": "command",
  "in": "path",
  "description": "Command to dispatch. Left/right scoring aliases map to home/away.",
  "schema": {
    "type": "string",
    "enum": [
      "score_home_up",
      "score_home_down",
      "score_away_up",
      "score_away_down",
      "score_home_prepare",
      "score_away_prepare",
      "score_goal_cancel",
      "score_left_up",
      "score_left_down",
      "score_right_up",
      "score_right_down",
      "start_game",
      "end_game",
      "end_game_with_score",
      "end_match",
      "end_match_with_score",
      "new_match",
      "new_match_with_id",
      "toggle_recording",
      "toggle_sharing",
      "toggle_summary",
      "toggle_highlights"
    ]
  },
  "example": "score_home_up",
  "required": true
}
deviceId · query · required

Target device; POST may provide it in JSON instead.

{
  "name": "deviceId",
  "in": "query",
  "description": "Target device; POST may provide it in JSON instead.",
  "schema": {
    "type": "string"
  },
  "example": "demo-table-01",
  "required": true
}
zone · query · optional

Optional scoring rod, only for score-up commands.

{
  "name": "zone",
  "in": "query",
  "description": "Optional scoring rod, only for score-up commands.",
  "schema": {
    "type": "string",
    "enum": [
      "2",
      "3",
      "5"
    ]
  },
  "example": "3",
  "required": false
}

Responses

200 — Command sent to the connected host.
{
  "description": "Command sent to the connected host.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "success": {
            "const": true
          },
          "command": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "command"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "success": true,
            "command": "score_home_up"
          }
        }
      }
    }
  }
}
400 — Invalid command, missing/malformed/conflicting target, or invalid zone.
{
  "description": "Invalid command, missing/malformed/conflicting target, or invalid zone.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Missing deviceId"
          }
        }
      }
    }
  }
}
401 — Missing or invalid key.
{
  "description": "Missing or invalid key.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "API key required"
          }
        }
      }
    }
  }
}
403 — Revoked/expired key or target not authorized.
{
  "description": "Revoked/expired key or target not authorized.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "API key is not authorized for this device"
          }
        }
      }
    }
  }
}
404 — No active remote_control session for this device
{
  "description": "No active remote_control session for this device",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "No active remote_control session for this device"
          }
        }
      }
    }
  }
}
410 — Target session exists but its host is disconnected.
{
  "description": "Target session exists but its host is disconnected.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "diag": {
            "type": "object",
            "properties": {
              "hostNull": {
                "type": "boolean"
              },
              "readyState": {
                "anyOf": [
                  {
                    "type": "integer"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "session": {
                "type": "string"
              },
              "type": {
                "type": "string"
              }
            },
            "required": [
              "hostNull",
              "readyState",
              "session",
              "type"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "error",
          "diag"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Host disconnected",
            "diag": {
              "hostNull": true,
              "readyState": null,
              "session": "DEMO42",
              "type": "remote_control"
            }
          }
        }
      }
    }
  }
}
500 — API-key storage lookup failed; no command is dispatched.
{
  "description": "API-key storage lookup failed; no command is dispatched.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Internal error"
          }
        }
      }
    }
  }
}
503 — Device authorization unavailable
{
  "description": "Device authorization unavailable",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Device authorization unavailable"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request GET "$BASE_URL/api/remote/score_home_up?deviceId=demo-table-01&zone=3" \
  --header "X-API-Key: $IREF_API_KEY"
Security requirements
[
  {
    "RemoteApiKey": []
  },
  {
    "LegacyQueryApiKey": []
  }
]

POST /api/remote/{command}

Dispatch a remote command

Dispatches a command to an active remote_control host. Success confirms dispatch, not that the host applied it. Prefer POST: GET also changes state and must not be prefetched. Query deviceId takes precedence over JSON, but conflicting selectors are rejected. score_left/right aliases resolve to score_home/away. zone is accepted only by score-up commands and sends a one-shot goal, not a prepared confirmation. Only deviceId and zone are forwarded from request fields; arbitrary payload, scores or match IDs in JSON are not forwarded by this HTTP handler. Authentication: Requires an issued API key via X-API-Key (recommended) or legacy apiKey query. Missing/invalid keys return 401; revoked/expired keys and devices outside a scoped key’s grants return 403. Unrestricted keys can target any device. Alias resolution precedes authorization. Headers keep keys out of query URLs.

Parameters

command · path · required

Command to dispatch. Left/right scoring aliases map to home/away.

{
  "name": "command",
  "in": "path",
  "description": "Command to dispatch. Left/right scoring aliases map to home/away.",
  "schema": {
    "type": "string",
    "enum": [
      "score_home_up",
      "score_home_down",
      "score_away_up",
      "score_away_down",
      "score_home_prepare",
      "score_away_prepare",
      "score_goal_cancel",
      "score_left_up",
      "score_left_down",
      "score_right_up",
      "score_right_down",
      "start_game",
      "end_game",
      "end_game_with_score",
      "end_match",
      "end_match_with_score",
      "new_match",
      "new_match_with_id",
      "toggle_recording",
      "toggle_sharing",
      "toggle_summary",
      "toggle_highlights"
    ]
  },
  "example": "score_home_up",
  "required": true
}
deviceId · query · optional

Target device; POST may provide it in JSON instead.

{
  "name": "deviceId",
  "in": "query",
  "description": "Target device; POST may provide it in JSON instead.",
  "schema": {
    "type": "string"
  },
  "example": "demo-table-01",
  "required": false
}
zone · query · optional

Optional scoring rod, only for score-up commands.

{
  "name": "zone",
  "in": "query",
  "description": "Optional scoring rod, only for score-up commands.",
  "schema": {
    "type": "string",
    "enum": [
      "2",
      "3",
      "5"
    ]
  },
  "example": "3",
  "required": false
}

Request body

JSON alternative to query fields. Query values take precedence.

{
  "required": false,
  "description": "JSON alternative to query fields. Query values take precedence.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "deviceId": {
            "type": "string"
          },
          "zone": {
            "type": [
              "string",
              "number"
            ],
            "enum": [
              "2",
              "3",
              "5",
              2,
              3,
              5
            ]
          }
        },
        "required": [],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "deviceId": "demo-table-01",
            "zone": "3"
          }
        }
      }
    }
  }
}

Responses

200 — Command sent to the connected host.
{
  "description": "Command sent to the connected host.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "success": {
            "const": true
          },
          "command": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "command"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "success": true,
            "command": "score_home_up"
          }
        }
      }
    }
  }
}
400 — Invalid command, missing/malformed/conflicting target, or invalid zone.
{
  "description": "Invalid command, missing/malformed/conflicting target, or invalid zone.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Missing deviceId"
          }
        }
      }
    }
  }
}
401 — Missing or invalid key.
{
  "description": "Missing or invalid key.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "API key required"
          }
        }
      }
    }
  }
}
403 — Revoked/expired key or target not authorized.
{
  "description": "Revoked/expired key or target not authorized.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "API key is not authorized for this device"
          }
        }
      }
    }
  }
}
404 — No active remote_control session for this device
{
  "description": "No active remote_control session for this device",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "No active remote_control session for this device"
          }
        }
      }
    }
  }
}
410 — Target session exists but its host is disconnected.
{
  "description": "Target session exists but its host is disconnected.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "diag": {
            "type": "object",
            "properties": {
              "hostNull": {
                "type": "boolean"
              },
              "readyState": {
                "anyOf": [
                  {
                    "type": "integer"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "session": {
                "type": "string"
              },
              "type": {
                "type": "string"
              }
            },
            "required": [
              "hostNull",
              "readyState",
              "session",
              "type"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "error",
          "diag"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Host disconnected",
            "diag": {
              "hostNull": true,
              "readyState": null,
              "session": "DEMO42",
              "type": "remote_control"
            }
          }
        }
      }
    }
  }
}
500 — API-key storage lookup failed; no command is dispatched.
{
  "description": "API-key storage lookup failed; no command is dispatched.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Internal error"
          }
        }
      }
    }
  }
}
503 — Device authorization unavailable
{
  "description": "Device authorization unavailable",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Device authorization unavailable"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request POST "$BASE_URL/api/remote/score_home_up?deviceId=demo-table-01&zone=3" \
  --header "X-API-Key: $IREF_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"deviceId":"demo-table-01","zone":"3"}'
Security requirements
[
  {
    "RemoteApiKey": []
  },
  {
    "LegacyQueryApiKey": []
  }
]

GET /api/remote/goal

Send a goal command (legacy GET; modifies data)

Send a one-shot goal command to a connected host. Query values take precedence over POST body values, including invalid values. At least deviceId and side must be supplied in the query or JSON body. This changes match data. A successful response means dispatch only; it is not a delivery or scoring guarantee. Prefer POST; the legacy GET variant also modifies data and must not be prefetched. Authentication: Requires an issued API key. Send X-API-Key (recommended), or the legacy apiKey query parameter. Missing/invalid keys return 401; revoked/expired keys return 403. Unrestricted keys can target any device. Device-scoped keys require an explicit grant for the canonical target device; an empty list denies all targets. Aliases resolve before authorization; conflicting query/body targets are rejected. Prefer headers to keep credentials out of URLs.

Parameters

deviceId · query · required

Target device. POST may supply this in JSON instead.

{
  "name": "deviceId",
  "in": "query",
  "description": "Target device. POST may supply this in JSON instead.",
  "required": true,
  "schema": {
    "type": "string",
    "description": "Target device with an active remote_control session."
  },
  "example": "demo-table-01"
}
side · query · required

Scoring side. POST may supply this in JSON instead.

{
  "name": "side",
  "in": "query",
  "description": "Scoring side. POST may supply this in JSON instead.",
  "required": true,
  "schema": {
    "type": "string",
    "enum": [
      "home",
      "away",
      "left",
      "right",
      "guest",
      "visitor"
    ],
    "description": "Case-insensitive; surrounding whitespace is trimmed."
  },
  "example": "home"
}
zone · query · optional

Optional scoring rod. POST may supply this in JSON instead.

{
  "name": "zone",
  "in": "query",
  "description": "Optional scoring rod. POST may supply this in JSON instead.",
  "required": false,
  "schema": {
    "type": "string",
    "enum": [
      "2",
      "3",
      "5"
    ]
  },
  "example": "3"
}

Responses

200 — Command dispatched to the host, not confirmation that the host applied it.
{
  "description": "Command dispatched to the host, not confirmation that the host applied it.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "required": [
          "success",
          "command",
          "side"
        ],
        "additionalProperties": false,
        "properties": {
          "success": {
            "const": true
          },
          "command": {
            "enum": [
              "score_home_up",
              "score_away_up"
            ]
          },
          "side": {
            "type": "string"
          }
        }
      },
      "examples": {
        "synthetic": {
          "value": {
            "success": true,
            "command": "score_home_up",
            "side": "home"
          }
        }
      }
    }
  }
}
400 — Missing, malformed or conflicting deviceId selectors, or invalid side or zone.
{
  "description": "Missing, malformed or conflicting deviceId selectors, or invalid side or zone.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "Missing deviceId"
          }
        }
      }
    }
  }
}
401 — API key missing or invalid.
{
  "description": "API key missing or invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "API key required"
          }
        }
      }
    }
  }
}
403 — API key revoked, expired, or not authorized for the resolved device.
{
  "description": "API key revoked, expired, or not authorized for the resolved device.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "API key is not authorized for this device"
          }
        }
      }
    }
  }
}
404 — No active remote_control session exists for the device.
{
  "description": "No active remote_control session exists for the device.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "No active remote_control session for this device"
          }
        }
      }
    }
  }
}
410 — The session exists but its host is disconnected.
{
  "description": "The session exists but its host is disconnected.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "required": [
          "error",
          "diag"
        ],
        "properties": {
          "error": {
            "const": "Host disconnected"
          },
          "diag": {
            "type": "object",
            "required": [
              "hostNull",
              "readyState",
              "session",
              "type"
            ],
            "properties": {
              "hostNull": {
                "type": "boolean"
              },
              "readyState": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "session": {
                "type": "string"
              },
              "type": {
                "type": "string"
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "Host disconnected",
            "diag": {
              "hostNull": true,
              "readyState": null,
              "session": "DEMO42",
              "type": "remote_control"
            }
          }
        }
      }
    }
  }
}
500 — API key lookup failed; request is not delivered.
{
  "description": "API key lookup failed; request is not delivered.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "Internal error"
          }
        }
      }
    }
  }
}
503 — Device authorization lookup unavailable; request is not delivered.
{
  "description": "Device authorization lookup unavailable; request is not delivered.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "Device authorization unavailable"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request GET "$BASE_URL/api/remote/goal?deviceId=demo-table-01&side=home&zone=3" \
  --header "X-API-Key: $IREF_API_KEY"
Security requirements
[
  {
    "RemoteApiKey": []
  },
  {
    "LegacyQueryApiKey": []
  }
]

POST /api/remote/goal

Send a goal command

Send a one-shot goal command to a connected host. Query values take precedence over POST body values, including invalid values. At least deviceId and side must be supplied in the query or JSON body. This changes match data. A successful response means dispatch only; it is not a delivery or scoring guarantee. Prefer POST; the legacy GET variant also modifies data and must not be prefetched. Authentication: Requires an issued API key. Send X-API-Key (recommended), or the legacy apiKey query parameter. Missing/invalid keys return 401; revoked/expired keys return 403. Unrestricted keys can target any device. Device-scoped keys require an explicit grant for the canonical target device; an empty list denies all targets. Aliases resolve before authorization; conflicting query/body targets are rejected. Prefer headers to keep credentials out of URLs.

Parameters

deviceId · query · optional

Target device. POST may supply this in JSON instead.

{
  "name": "deviceId",
  "in": "query",
  "description": "Target device. POST may supply this in JSON instead.",
  "required": false,
  "schema": {
    "type": "string",
    "description": "Target device with an active remote_control session."
  },
  "example": "demo-table-01"
}
side · query · optional

Scoring side. POST may supply this in JSON instead.

{
  "name": "side",
  "in": "query",
  "description": "Scoring side. POST may supply this in JSON instead.",
  "required": false,
  "schema": {
    "type": "string",
    "enum": [
      "home",
      "away",
      "left",
      "right",
      "guest",
      "visitor"
    ],
    "description": "Case-insensitive; surrounding whitespace is trimmed."
  },
  "example": "home"
}
zone · query · optional

Optional scoring rod. POST may supply this in JSON instead.

{
  "name": "zone",
  "in": "query",
  "description": "Optional scoring rod. POST may supply this in JSON instead.",
  "required": false,
  "schema": {
    "type": "string",
    "enum": [
      "2",
      "3",
      "5"
    ]
  },
  "example": "3"
}

Request body

JSON alternative to query parameters; query values win. deviceId and side must exist across query/body.

{
  "required": false,
  "description": "JSON alternative to query parameters; query values win. deviceId and side must exist across query/body.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "deviceId": {
            "type": "string",
            "description": "Target device with an active remote_control session."
          },
          "side": {
            "type": "string",
            "enum": [
              "home",
              "away",
              "left",
              "right",
              "guest",
              "visitor"
            ],
            "description": "Case-insensitive; surrounding whitespace is trimmed."
          },
          "zone": {
            "type": [
              "string",
              "number"
            ],
            "enum": [
              "2",
              "3",
              "5",
              2,
              3,
              5
            ],
            "description": "Optional scoring rod."
          }
        },
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "value": {
            "deviceId": "demo-table-01",
            "side": "home",
            "zone": "3"
          }
        }
      }
    }
  }
}

Responses

200 — Command dispatched to the host, not confirmation that the host applied it.
{
  "description": "Command dispatched to the host, not confirmation that the host applied it.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "required": [
          "success",
          "command",
          "side"
        ],
        "additionalProperties": false,
        "properties": {
          "success": {
            "const": true
          },
          "command": {
            "enum": [
              "score_home_up",
              "score_away_up"
            ]
          },
          "side": {
            "type": "string"
          }
        }
      },
      "examples": {
        "synthetic": {
          "value": {
            "success": true,
            "command": "score_home_up",
            "side": "home"
          }
        }
      }
    }
  }
}
400 — Missing, malformed or conflicting deviceId selectors, or invalid side or zone.
{
  "description": "Missing, malformed or conflicting deviceId selectors, or invalid side or zone.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "Missing deviceId"
          }
        }
      }
    }
  }
}
401 — API key missing or invalid.
{
  "description": "API key missing or invalid.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "API key required"
          }
        }
      }
    }
  }
}
403 — API key revoked, expired, or not authorized for the resolved device.
{
  "description": "API key revoked, expired, or not authorized for the resolved device.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "API key is not authorized for this device"
          }
        }
      }
    }
  }
}
404 — No active remote_control session exists for the device.
{
  "description": "No active remote_control session exists for the device.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "No active remote_control session for this device"
          }
        }
      }
    }
  }
}
410 — The session exists but its host is disconnected.
{
  "description": "The session exists but its host is disconnected.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "required": [
          "error",
          "diag"
        ],
        "properties": {
          "error": {
            "const": "Host disconnected"
          },
          "diag": {
            "type": "object",
            "required": [
              "hostNull",
              "readyState",
              "session",
              "type"
            ],
            "properties": {
              "hostNull": {
                "type": "boolean"
              },
              "readyState": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "session": {
                "type": "string"
              },
              "type": {
                "type": "string"
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "Host disconnected",
            "diag": {
              "hostNull": true,
              "readyState": null,
              "session": "DEMO42",
              "type": "remote_control"
            }
          }
        }
      }
    }
  }
}
500 — API key lookup failed; request is not delivered.
{
  "description": "API key lookup failed; request is not delivered.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "Internal error"
          }
        }
      }
    }
  }
}
503 — Device authorization lookup unavailable; request is not delivered.
{
  "description": "Device authorization lookup unavailable; request is not delivered.",
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ApiError"
      },
      "examples": {
        "synthetic": {
          "value": {
            "error": "Device authorization unavailable"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request POST "$BASE_URL/api/remote/goal?deviceId=demo-table-01&side=home&zone=3" \
  --header "X-API-Key: $IREF_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"deviceId":"demo-table-01","side":"home","zone":"3"}'
Security requirements
[
  {
    "RemoteApiKey": []
  },
  {
    "LegacyQueryApiKey": []
  }
]

GET /api/stats

List saved matches

Lists matches ordered by status and most recently updated, excluding champion scenarios. Optional filters use AND. Each match includes per-game final scores. Responses contain persisted database rows in snake_case; additional fields may be present. Synthetic examples show representative fields, not real match or player data. Authentication: Readable without credentials. An optional X-API-Key attributes usage; a supplied revoked key returns 403. This middleware does not require a key or enforce device grants.

Parameters

device_id · query · optional

Exact device ID filter (snake_case).

{
  "name": "device_id",
  "in": "query",
  "description": "Exact device ID filter (snake_case).",
  "schema": {
    "type": "string"
  },
  "example": "demo-table-01",
  "required": false
}
source · query · optional

Exact source filter.

{
  "name": "source",
  "in": "query",
  "description": "Exact source filter.",
  "schema": {
    "type": "string"
  },
  "example": "default",
  "required": false
}

Responses

200 — Matching matches; an empty array means no results.
{
  "description": "Matching matches; an empty array means no results.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "matches": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "match_id": {
                  "type": "string"
                },
                "device_id": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "score_left": {
                  "type": "integer"
                },
                "score_right": {
                  "type": "integer"
                },
                "status": {
                  "type": "string"
                },
                "current_game_number": {
                  "type": "integer"
                },
                "source": {
                  "type": "string"
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "updated_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "games": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "game_number": {
                        "type": "integer"
                      },
                      "score_left": {
                        "type": "integer"
                      },
                      "score_right": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "game_number",
                      "score_left",
                      "score_right"
                    ],
                    "additionalProperties": true
                  }
                }
              },
              "required": [
                "match_id",
                "device_id",
                "score_left",
                "score_right",
                "status",
                "current_game_number",
                "source",
                "created_at",
                "updated_at"
              ],
              "additionalProperties": true
            }
          }
        },
        "required": [
          "matches"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "matches": [
              {
                "match_id": "DEMO2345",
                "device_id": "demo-table-01",
                "score_left": 2,
                "score_right": 1,
                "status": "active",
                "current_game_number": 1,
                "source": "default",
                "created_at": "2026-01-15T12:00:00.000Z",
                "updated_at": "2026-01-15T12:00:00.000Z",
                "games": [
                  {
                    "game_number": 1,
                    "score_left": 2,
                    "score_right": 1
                  }
                ]
              }
            ]
          }
        }
      }
    }
  }
}
403 — Authorization rejected.
{
  "description": "Authorization rejected.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "API key has been revoked"
          }
        }
      }
    }
  }
}
500 — Failed to fetch matches
{
  "description": "Failed to fetch matches",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Failed to fetch matches"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request GET "$BASE_URL/api/stats?device_id=demo-table-01&source=default"
Security requirements
[
  {},
  {
    "StatsAttributionKey": []
  }
]

GET /api/stats/{matchId}

Get a saved match and its games

Responses contain persisted database rows in snake_case; additional fields may be present. Synthetic examples show representative fields, not real match or player data. Authentication: Readable without credentials. An optional X-API-Key attributes usage; a supplied revoked key returns 403. This middleware does not require a key or enforce device grants.

Parameters

matchId · path · required

Required matchId.

{
  "name": "matchId",
  "in": "path",
  "description": "Required matchId.",
  "schema": {
    "type": "string"
  },
  "example": "DEMO2345",
  "required": true
}

Responses

200 — Match and games ordered by game number.
{
  "description": "Match and games ordered by game number.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "match": {
            "type": "object",
            "properties": {
              "match_id": {
                "type": "string"
              },
              "device_id": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "score_left": {
                "type": "integer"
              },
              "score_right": {
                "type": "integer"
              },
              "status": {
                "type": "string"
              },
              "current_game_number": {
                "type": "integer"
              },
              "source": {
                "type": "string"
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "match_id",
              "device_id",
              "score_left",
              "score_right",
              "status",
              "current_game_number",
              "source",
              "created_at",
              "updated_at"
            ],
            "additionalProperties": true
          },
          "games": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "match_id": {
                  "type": "string"
                },
                "game_number": {
                  "type": "integer"
                },
                "stats": {
                  "anyOf": [
                    {
                      "type": "object",
                      "additionalProperties": true
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "final_score_left": {
                  "type": "integer"
                },
                "final_score_right": {
                  "type": "integer"
                },
                "duration_ms": {
                  "anyOf": [
                    {
                      "type": "integer"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "updated_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "started_at": {
                  "anyOf": [
                    {
                      "type": "string",
                      "format": "date-time"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "finalized_at": {
                  "anyOf": [
                    {
                      "type": "string",
                      "format": "date-time"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "required": [
                "id",
                "match_id",
                "game_number",
                "stats",
                "final_score_left",
                "final_score_right",
                "duration_ms",
                "created_at",
                "updated_at",
                "started_at",
                "finalized_at"
              ],
              "additionalProperties": true
            }
          }
        },
        "required": [
          "match",
          "games"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "match": {
              "match_id": "DEMO2345",
              "device_id": "demo-table-01",
              "score_left": 2,
              "score_right": 1,
              "status": "active",
              "current_game_number": 1,
              "source": "default",
              "created_at": "2026-01-15T12:00:00.000Z",
              "updated_at": "2026-01-15T12:00:00.000Z"
            },
            "games": [
              {
                "id": 12,
                "match_id": "DEMO2345",
                "game_number": 1,
                "stats": {
                  "zoneStats": {
                    "left3": {
                      "successes": 1,
                      "attempts": 2,
                      "avgTime": 2.4
                    }
                  }
                },
                "final_score_left": 2,
                "final_score_right": 1,
                "duration_ms": 120000,
                "created_at": "2026-01-15T12:00:00.000Z",
                "updated_at": "2026-01-15T12:00:00.000Z",
                "started_at": "2026-01-15T12:00:00.000Z",
                "finalized_at": null
              }
            ]
          }
        }
      }
    }
  }
}
403 — Authorization rejected.
{
  "description": "Authorization rejected.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "API key has been revoked"
          }
        }
      }
    }
  }
}
404 — Match not found
{
  "description": "Match not found",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Match not found"
          }
        }
      }
    }
  }
}
500 — Failed to fetch match stats
{
  "description": "Failed to fetch match stats",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Failed to fetch match stats"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request GET "$BASE_URL/api/stats/DEMO2345"
Security requirements
[
  {},
  {
    "StatsAttributionKey": []
  }
]

GET /api/stats/{matchId}/{gameNumber}

Get a saved game

Retrieves one saved game by match ID and game number. Responses contain persisted database rows in snake_case; additional fields may be present. Synthetic examples show representative fields, not real match or player data. Authentication: Readable without credentials. An optional X-API-Key attributes usage; a supplied revoked key returns 403. This middleware does not require a key or enforce device grants.

Parameters

matchId · path · required

Required matchId.

{
  "name": "matchId",
  "in": "path",
  "description": "Required matchId.",
  "schema": {
    "type": "string"
  },
  "example": "DEMO2345",
  "required": true
}
gameNumber · path · required

Required gameNumber.

{
  "name": "gameNumber",
  "in": "path",
  "description": "Required gameNumber.",
  "schema": {
    "type": "integer"
  },
  "example": 1,
  "required": true
}

Responses

200 — Saved game row.
{
  "description": "Saved game row.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "game": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "match_id": {
                "type": "string"
              },
              "game_number": {
                "type": "integer"
              },
              "stats": {
                "anyOf": [
                  {
                    "type": "object",
                    "additionalProperties": true
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "final_score_left": {
                "type": "integer"
              },
              "final_score_right": {
                "type": "integer"
              },
              "duration_ms": {
                "anyOf": [
                  {
                    "type": "integer"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              },
              "started_at": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "finalized_at": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "id",
              "match_id",
              "game_number",
              "stats",
              "final_score_left",
              "final_score_right",
              "duration_ms",
              "created_at",
              "updated_at",
              "started_at",
              "finalized_at"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "game"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "game": {
              "id": 12,
              "match_id": "DEMO2345",
              "game_number": 1,
              "stats": {
                "zoneStats": {
                  "left3": {
                    "successes": 1,
                    "attempts": 2,
                    "avgTime": 2.4
                  }
                }
              },
              "final_score_left": 2,
              "final_score_right": 1,
              "duration_ms": 120000,
              "created_at": "2026-01-15T12:00:00.000Z",
              "updated_at": "2026-01-15T12:00:00.000Z",
              "started_at": "2026-01-15T12:00:00.000Z",
              "finalized_at": null
            }
          }
        }
      }
    }
  }
}
403 — Authorization rejected.
{
  "description": "Authorization rejected.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "API key has been revoked"
          }
        }
      }
    }
  }
}
404 — Game not found
{
  "description": "Game not found",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Game not found"
          }
        }
      }
    }
  }
}
500 — Failed to fetch game stats
{
  "description": "Failed to fetch game stats",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Failed to fetch game stats"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request GET "$BASE_URL/api/stats/DEMO2345/1"
Security requirements
[
  {},
  {
    "StatsAttributionKey": []
  }
]

GET /api/stats/{matchId}/{gameNumber}/possessions

Get game possessions and computed zone statistics

Returns camelCase events plus aggregates over ALL game events, even when limit narrows the event window. avgTime is a seconds string. matchHasConfirmedGoal checks the entire match, not just this game. No API key middleware; unknown games return empty results. Authentication: Unauthenticated; no caller credentials are required.

Parameters

matchId · path · required

Required matchId.

{
  "name": "matchId",
  "in": "path",
  "description": "Required matchId.",
  "schema": {
    "type": "string"
  },
  "example": "DEMO2345",
  "required": true
}
gameNumber · path · required

Required gameNumber.

{
  "name": "gameNumber",
  "in": "path",
  "description": "Required gameNumber.",
  "schema": {
    "type": "integer"
  },
  "example": 1,
  "required": true
}
limit · query · optional

Optional latest-event window; use a positive integer.

{
  "name": "limit",
  "in": "query",
  "description": "Optional latest-event window; use a positive integer.",
  "schema": {
    "type": "integer",
    "minimum": 1
  },
  "example": 20,
  "required": false
}

Responses

200 — Event window and full-game aggregates.
{
  "description": "Event window and full-game aggregates.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "possessions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "zone": {
                  "type": "string"
                },
                "success": {
                  "type": "boolean"
                },
                "durationMs": {
                  "type": "integer"
                },
                "sequence": {
                  "type": "integer"
                },
                "timestamp": {
                  "type": "integer"
                },
                "subZone": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "confirmedGoal": {
                  "type": "boolean"
                },
                "sourceEventId": {
                  "anyOf": [
                    {
                      "type": "integer"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "originalSuccess": {
                  "anyOf": [
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "required": [
                "zone",
                "success",
                "durationMs",
                "sequence",
                "timestamp",
                "subZone",
                "confirmedGoal",
                "sourceEventId"
              ],
              "additionalProperties": true
            }
          },
          "matchHasConfirmedGoal": {
            "type": "boolean"
          },
          "computedZoneStats": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "successes": {
                  "type": "integer"
                },
                "attempts": {
                  "type": "integer"
                },
                "avgTime": {
                  "type": "string"
                },
                "laneSuccesses": {
                  "type": "integer"
                },
                "laneAttempts": {
                  "type": "integer"
                },
                "wallSuccesses": {
                  "type": "integer"
                },
                "wallAttempts": {
                  "type": "integer"
                }
              },
              "required": [
                "successes",
                "attempts",
                "avgTime",
                "laneSuccesses",
                "laneAttempts",
                "wallSuccesses",
                "wallAttempts"
              ],
              "additionalProperties": true
            }
          }
        },
        "required": [
          "possessions",
          "matchHasConfirmedGoal",
          "computedZoneStats"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "possessions": [
              {
                "zone": "left3",
                "success": true,
                "durationMs": 2400,
                "sequence": 7,
                "timestamp": 1768478400000,
                "subZone": null,
                "confirmedGoal": false,
                "sourceEventId": null,
                "originalSuccess": null
              }
            ],
            "matchHasConfirmedGoal": false,
            "computedZoneStats": {
              "left3": {
                "successes": 1,
                "attempts": 1,
                "avgTime": "2.4",
                "laneSuccesses": 0,
                "laneAttempts": 0,
                "wallSuccesses": 0,
                "wallAttempts": 0
              }
            }
          }
        }
      }
    }
  }
}
500 — Failed to fetch possession events
{
  "description": "Failed to fetch possession events",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Failed to fetch possession events"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request GET "$BASE_URL/api/stats/DEMO2345/1/possessions?limit=20"

GET /api/stats/{matchId}/all-possessions

Get possessions across every game

Returns camelCase possession events across a match, including gameNumber. No API key middleware is applied. Unknown matches return an empty array, not 404. Authentication: Unauthenticated; no caller credentials are required.

Parameters

matchId · path · required

Required matchId.

{
  "name": "matchId",
  "in": "path",
  "description": "Required matchId.",
  "schema": {
    "type": "string"
  },
  "example": "DEMO2345",
  "required": true
}

Responses

200 — All saved possessions.
{
  "description": "All saved possessions.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "possessions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "zone": {
                  "type": "string"
                },
                "success": {
                  "type": "boolean"
                },
                "durationMs": {
                  "type": "integer"
                },
                "sequence": {
                  "type": "integer"
                },
                "timestamp": {
                  "type": "integer"
                },
                "subZone": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "confirmedGoal": {
                  "type": "boolean"
                },
                "sourceEventId": {
                  "anyOf": [
                    {
                      "type": "integer"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "originalSuccess": {
                  "anyOf": [
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "gameNumber": {
                  "type": "integer"
                }
              },
              "required": [
                "zone",
                "success",
                "durationMs",
                "sequence",
                "timestamp",
                "subZone",
                "confirmedGoal",
                "sourceEventId"
              ],
              "additionalProperties": true
            }
          }
        },
        "required": [
          "possessions"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "possessions": [
              {
                "zone": "left3",
                "success": true,
                "durationMs": 2400,
                "sequence": 7,
                "timestamp": 1768478400000,
                "subZone": null,
                "confirmedGoal": false,
                "sourceEventId": null,
                "originalSuccess": null,
                "gameNumber": 1
              }
            ]
          }
        }
      }
    }
  }
}
500 — Failed to fetch all possession events
{
  "description": "Failed to fetch all possession events",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Failed to fetch all possession events"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request GET "$BASE_URL/api/stats/DEMO2345/all-possessions"

GET /api/stats/{matchId}/currentgame

Get the highest-numbered saved game

Selects the highest saved game number, which may be finalized. It does not use the match current_game_number field. Responses contain persisted database rows in snake_case; additional fields may be present. Synthetic examples show representative fields, not real match or player data. Authentication: Readable without credentials. An optional X-API-Key attributes usage; a supplied revoked key returns 403. This middleware does not require a key or enforce device grants.

Parameters

matchId · path · required

Required matchId.

{
  "name": "matchId",
  "in": "path",
  "description": "Required matchId.",
  "schema": {
    "type": "string"
  },
  "example": "DEMO2345",
  "required": true
}

Responses

200 — Saved game row.
{
  "description": "Saved game row.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "game": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "match_id": {
                "type": "string"
              },
              "game_number": {
                "type": "integer"
              },
              "stats": {
                "anyOf": [
                  {
                    "type": "object",
                    "additionalProperties": true
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "final_score_left": {
                "type": "integer"
              },
              "final_score_right": {
                "type": "integer"
              },
              "duration_ms": {
                "anyOf": [
                  {
                    "type": "integer"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              },
              "started_at": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "finalized_at": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "id",
              "match_id",
              "game_number",
              "stats",
              "final_score_left",
              "final_score_right",
              "duration_ms",
              "created_at",
              "updated_at",
              "started_at",
              "finalized_at"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "game"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "game": {
              "id": 12,
              "match_id": "DEMO2345",
              "game_number": 1,
              "stats": {
                "zoneStats": {
                  "left3": {
                    "successes": 1,
                    "attempts": 2,
                    "avgTime": 2.4
                  }
                }
              },
              "final_score_left": 2,
              "final_score_right": 1,
              "duration_ms": 120000,
              "created_at": "2026-01-15T12:00:00.000Z",
              "updated_at": "2026-01-15T12:00:00.000Z",
              "started_at": "2026-01-15T12:00:00.000Z",
              "finalized_at": null
            }
          }
        }
      }
    }
  }
}
403 — Authorization rejected.
{
  "description": "Authorization rejected.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "API key has been revoked"
          }
        }
      }
    }
  }
}
404 — No games found for this match
{
  "description": "No games found for this match",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "No games found for this match"
          }
        }
      }
    }
  }
}
500 — Failed to fetch current game
{
  "description": "Failed to fetch current game",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Failed to fetch current game"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request GET "$BASE_URL/api/stats/DEMO2345/currentgame"
Security requirements
[
  {},
  {
    "StatsAttributionKey": []
  }
]

GET /api/stats/{matchId}/qrcode

Generate a match stats QR image

Returns a 400-pixel-wide PNG image, not a JSON body. Save the response to a .png file. Encodes this server’s /stats/{matchId} URL without checking whether the match exists. Authentication: Readable without credentials. An optional X-API-Key attributes usage; a supplied revoked key returns 403. This middleware does not require a key or enforce device grants.

Parameters

matchId · path · required

Required matchId.

{
  "name": "matchId",
  "in": "path",
  "description": "Required matchId.",
  "schema": {
    "type": "string"
  },
  "example": "DEMO2345",
  "required": true
}

Responses

200 — PNG image bytes (synthetic example is a PNG signature representation, not JSON).
{
  "description": "PNG image bytes (synthetic example is a PNG signature representation, not JSON).",
  "content": {
    "image/png": {
      "schema": {
        "type": "string",
        "format": "binary"
      },
      "examples": {
        "synthetic": {
          "summary": "Binary PNG response",
          "value": "\\x89PNG\\r\\n\\x1a\\n…"
        }
      }
    }
  }
}
403 — Authorization rejected.
{
  "description": "Authorization rejected.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "API key has been revoked"
          }
        }
      }
    }
  }
}
500 — Failed to generate QR code
{
  "description": "Failed to generate QR code",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Failed to generate QR code"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request GET "$BASE_URL/api/stats/DEMO2345/qrcode"
Security requirements
[
  {},
  {
    "StatsAttributionKey": []
  }
]

GET /api/stats/device/{deviceId}/current

Get a device’s current match

Chooses the latest active match. last=true falls back to the most recent match of any status when there is no active match. Responses contain persisted database rows in snake_case; additional fields may be present. Synthetic examples show representative fields, not real match or player data. Authentication: Readable without credentials. An optional X-API-Key attributes usage; a supplied revoked key returns 403. This middleware does not require a key or enforce device grants.

Parameters

deviceId · path · required

Required deviceId.

{
  "name": "deviceId",
  "in": "path",
  "description": "Required deviceId.",
  "schema": {
    "type": "string"
  },
  "example": "demo-table-01",
  "required": true
}
last · query · optional

Fall back to the latest saved match if no active match exists. Only the exact string true enables this.

{
  "name": "last",
  "in": "query",
  "description": "Fall back to the latest saved match if no active match exists. Only the exact string true enables this.",
  "schema": {
    "type": "boolean"
  },
  "example": true,
  "required": false
}

Responses

200 — Match and games ordered by game number.
{
  "description": "Match and games ordered by game number.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "match": {
            "type": "object",
            "properties": {
              "match_id": {
                "type": "string"
              },
              "device_id": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "score_left": {
                "type": "integer"
              },
              "score_right": {
                "type": "integer"
              },
              "status": {
                "type": "string"
              },
              "current_game_number": {
                "type": "integer"
              },
              "source": {
                "type": "string"
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "match_id",
              "device_id",
              "score_left",
              "score_right",
              "status",
              "current_game_number",
              "source",
              "created_at",
              "updated_at"
            ],
            "additionalProperties": true
          },
          "games": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "match_id": {
                  "type": "string"
                },
                "game_number": {
                  "type": "integer"
                },
                "stats": {
                  "anyOf": [
                    {
                      "type": "object",
                      "additionalProperties": true
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "final_score_left": {
                  "type": "integer"
                },
                "final_score_right": {
                  "type": "integer"
                },
                "duration_ms": {
                  "anyOf": [
                    {
                      "type": "integer"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "updated_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "started_at": {
                  "anyOf": [
                    {
                      "type": "string",
                      "format": "date-time"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "finalized_at": {
                  "anyOf": [
                    {
                      "type": "string",
                      "format": "date-time"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "required": [
                "id",
                "match_id",
                "game_number",
                "stats",
                "final_score_left",
                "final_score_right",
                "duration_ms",
                "created_at",
                "updated_at",
                "started_at",
                "finalized_at"
              ],
              "additionalProperties": true
            }
          }
        },
        "required": [
          "match",
          "games"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "match": {
              "match_id": "DEMO2345",
              "device_id": "demo-table-01",
              "score_left": 2,
              "score_right": 1,
              "status": "active",
              "current_game_number": 1,
              "source": "default",
              "created_at": "2026-01-15T12:00:00.000Z",
              "updated_at": "2026-01-15T12:00:00.000Z"
            },
            "games": [
              {
                "id": 12,
                "match_id": "DEMO2345",
                "game_number": 1,
                "stats": {
                  "zoneStats": {
                    "left3": {
                      "successes": 1,
                      "attempts": 2,
                      "avgTime": 2.4
                    }
                  }
                },
                "final_score_left": 2,
                "final_score_right": 1,
                "duration_ms": 120000,
                "created_at": "2026-01-15T12:00:00.000Z",
                "updated_at": "2026-01-15T12:00:00.000Z",
                "started_at": "2026-01-15T12:00:00.000Z",
                "finalized_at": null
              }
            ]
          }
        }
      }
    }
  }
}
403 — Authorization rejected.
{
  "description": "Authorization rejected.",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "API key has been revoked"
          }
        }
      }
    }
  }
}
404 — No active match found for this device
{
  "description": "No active match found for this device",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "No active match found for this device"
          }
        }
      }
    }
  }
}
500 — Failed to get device current match
{
  "description": "Failed to get device current match",
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "examples": {
        "synthetic": {
          "summary": "Synthetic example",
          "value": {
            "error": "Failed to get device current match"
          }
        }
      }
    }
  }
}

curl example

# Set BASE_URL to your chosen iRefFoos server. Credential variables are never prefilled.
curl --request GET "$BASE_URL/api/stats/device/demo-table-01/current?last=true"
Security requirements
[
  {},
  {
    "StatsAttributionKey": []
  }
]

Authentication schemes

RemoteApiKey
{
  "type": "apiKey",
  "in": "header",
  "name": "X-API-Key",
  "description": "Issued API key. No key is embedded in this reference."
}
LegacyQueryApiKey
{
  "type": "apiKey",
  "in": "query",
  "name": "apiKey",
  "description": "Legacy alternative; prefer X-API-Key to avoid keys in URL logs."
}
StatsAttributionKey
{
  "type": "apiKey",
  "in": "header",
  "name": "X-API-Key",
  "description": "Optional usage attribution. This does not replace X-Stats-API-Key or host ownership."
}

Schemas

ApiError
{
  "type": "object",
  "required": [
    "error"
  ],
  "properties": {
    "error": {
      "type": "string"
    }
  },
  "additionalProperties": true
}
01—

This reference is generated from operations that have been published by the iRefFoos team. No live requests can be sent from this page.

View raw specification