{
  "openapi": "3.0.3",
  "info": {
    "title": "My Live Sport · API locale AppSport",
    "version": "1",
    "description": "API du poste AppSport, activée dans AppSport settings → Local API. Un match chargé dans la fenêtre principale est requis pour lire son état et le commander. Les lectures ne déclenchent aucun clic. La clé du poste est différente d’un jeton web. Require API key peut être désactivé explicitement ; les contrôles réseau et les droits du match restent appliqués. Une entrée Scoreboard OCR disponible avec Enable API et la source Scoreboard OCR enregistrée dans Table de marque reçoit des données graphiques par un WebSocket distinct. Le Swagger public sert à consulter le contrat ; utiliser curl ou un client natif sur le réseau de régie."
  },
  "servers": [
    {
      "url": "http://127.0.0.1:18800",
      "description": "Poste AppSport (adapter IP et port)"
    }
  ],
  "security": [
    {
      "LocalBearer": []
    },
    {}
  ],
  "paths": {
    "/api/v1/state": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire tout l’état du match",
        "operationId": "local_state",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/State"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/buttons": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire les boutons disponibles",
        "operationId": "local_buttons",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/texts": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire les textes",
        "operationId": "local_texts",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/actions": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire le catalogue actions",
        "operationId": "local_actions",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/players": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire le catalogue players",
        "operationId": "local_players",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/events": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire le catalogue events",
        "operationId": "local_events",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/eventTypes": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire le catalogue eventTypes",
        "operationId": "local_eventTypes",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/statistics": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire le catalogue statistics",
        "operationId": "local_statistics",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/selections": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire le catalogue selections",
        "operationId": "local_selections",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/match": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire les données match",
        "operationId": "local_match",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "contextKey": {
                      "type": "string"
                    },
                    "match": {
                      "$ref": "#/components/schemas/MatchDetails"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/matchDays": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire les données matchDays",
        "operationId": "local_matchDays",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "contextKey": {
                      "type": "string"
                    },
                    "matchDays": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MatchDay"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/scorers": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire les données scorers",
        "operationId": "local_scorers",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "contextKey": {
                      "type": "string"
                    },
                    "scorers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Scorer"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/standings": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire les données standings",
        "operationId": "local_standings",
        "responses": {
          "200": {
            "description": "État actuel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "contextKey": {
                      "type": "string"
                    },
                    "standings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Standing"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/buttons/{buttonId}": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire buttons/{buttonId}",
        "operationId": "local_read_buttons_buttonId",
        "parameters": [
          {
            "name": "buttonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Valeur courante ; commande conservée 60 secondes après sa création une fois terminée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Button"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Élément absent ou commande expirée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/buttons/{buttonId}/text": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire buttons/{buttonId}/text",
        "operationId": "local_read_buttons_buttonId_text",
        "parameters": [
          {
            "name": "buttonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Valeur courante ; commande conservée 60 secondes après sa création une fois terminée.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Élément absent ou commande expirée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/texts/{textId}": {
      "get": {
        "tags": [
          "Lecture"
        ],
        "summary": "Lire texts/{textId}",
        "operationId": "local_read_texts_textId",
        "parameters": [
          {
            "name": "textId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Valeur courante ; commande conservée 60 secondes après sa création une fois terminée.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Élément absent ou commande expirée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/commands/{commandId}": {
      "get": {
        "tags": [
          "Suivi"
        ],
        "summary": "Lire commands/{commandId}",
        "operationId": "local_read_commands_commandId",
        "parameters": [
          {
            "name": "commandId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Valeur courante ; commande conservée 60 secondes après sa création une fois terminée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Command"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Élément absent ou commande expirée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/buttons/{buttonId}/click": {
      "post": {
        "tags": [
          "Commandes"
        ],
        "summary": "Commander un bouton",
        "operationId": "local_buttonId_command",
        "description": "Utiliser le contextKey lu dans state. 202 accuse la réception : lire statusUrl jusqu’à accepted ou failed. accepted pour un graphique ne garantit pas la fin de son animation. params dépend de l’action annoncée dans le catalogue actions.",
        "parameters": [
          {
            "name": "buttonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,100}$"
            },
            "description": "Une clé unique par intention ; même clé et contenu ne réexécutent pas la commande pendant sa rétention."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contextKey": {
                    "type": "string",
                    "example": "futsal:1:2"
                  },
                  "gesture": {
                    "type": "string",
                    "enum": [
                      "left",
                      "middle",
                      "right",
                      "reset"
                    ],
                    "default": "left"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Commande reçue, à suivre.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Command"
                }
              }
            }
          },
          "400": {
            "description": "Paramètres ou JSON invalides.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Élément absent ou commande expirée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Corps trop volumineux.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Utiliser application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requêtes ou de commandes atteinte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/actions/{actionId}": {
      "post": {
        "tags": [
          "Commandes"
        ],
        "summary": "Exécuter une action du catalogue",
        "operationId": "local_actionId_command",
        "description": "Utiliser le contextKey lu dans state. 202 accuse la réception : lire statusUrl jusqu’à accepted ou failed. accepted pour un graphique ne garantit pas la fin de son animation. params dépend de l’action annoncée dans le catalogue actions.",
        "parameters": [
          {
            "name": "actionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,100}$"
            },
            "description": "Une clé unique par intention ; même clé et contenu ne réexécutent pas la commande pendant sa rétention."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "contextKey"
                ],
                "properties": {
                  "contextKey": {
                    "type": "string",
                    "example": "futsal:1:2"
                  },
                  "params": {
                    "type": "object",
                    "additionalProperties": true,
                    "example": {
                      "type": 1,
                      "playerId": "11",
                      "timer": 23
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Commande reçue, à suivre.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Command"
                }
              }
            }
          },
          "400": {
            "description": "Paramètres ou JSON invalides.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Élément absent ou commande expirée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Corps trop volumineux.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Utiliser application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requêtes ou de commandes atteinte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ws": {
      "get": {
        "tags": [
          "WebSocket"
        ],
        "summary": "Ouvrir le flux WebSocket du match",
        "description": "Upgrade WebSocket. Adresse ws://IP:PORT/api/v1/ws, sans query string. Clé par en-tête Bearer ou première trame {\"type\":\"auth\",\"token\":\"…\"} sous 5 secondes. Sans protection, ready est envoyé immédiatement. Messages : ready, state, unavailable, heartbeat, ack, command, error. Unavailable retire les données du match précédent. Swagger ne teste pas les WebSockets.",
        "responses": {
          "101": {
            "description": "Connexion WebSocket ouverte."
          },
          "400": {
            "description": "Paramètres ou JSON invalides.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé ou jeton absent, invalide, expiré ou révoqué.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requêtes ou de commandes atteinte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ocr/{ingressKey}": {
      "get": {
        "tags": [
          "Scoreboard OCR"
        ],
        "summary": "Connecter la source Scoreboard OCR",
        "security": [],
        "description": "Upgrade WebSocket disponible avec Enable API et scoreboardTable.vendor=scoreboard-ocr actif. HTTP 409 si une autre source est sélectionnée ou si une source OCR est déjà connectée. Copier l’URL depuis Table de marque → Scoreboard OCR. La clé d’entrée, distincte de LocalBearer, autorise uniquement les relevés. Exigée quand Require API key est coché ; sinon utiliser aussi /api/v1/ocr/ sans clé. Une seule source. Messages JSON texte, 8 Kio maximum : register avec role=ocr, ping (réponse pong), ocr avec values (1 à 64 chaînes de 255 caractères maximum ou nombres finis). Les champs vides sont ignorés et les relevés partiels fusionnés. AppSport fournit les noms bruts directement aux sorties et applique les associations facultatives d’export au match modifiable actif. Les associations explicites sont prioritaires en cas de collision. Aucun clic ni action métier sur ce canal. Les dernières valeurs sont conservées à la coupure, puis effacées au changement de match, à la désactivation ou à la réinitialisation. Swagger ne teste pas les WebSockets.",
        "parameters": [
          {
            "name": "ingressKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{64}$"
            },
            "description": "Clé privée de réception OCR fournie dans le bouton Copier l’URL OCR de Table de marque."
          }
        ],
        "responses": {
          "101": {
            "description": "Source OCR connectée."
          },
          "400": {
            "description": "Paramètres ou JSON invalides.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ocr/": {
      "get": {
        "tags": [
          "Scoreboard OCR"
        ],
        "summary": "Connecter Scoreboard OCR sans clé",
        "security": [],
        "description": "Upgrade WebSocket avec Enable API, scoreboardTable.vendor=scoreboard-ocr actif et Require API key désactivé. HTTP 409 si une autre source est sélectionnée ou si une source OCR est déjà connectée. URL ws://IP:PORT/api/v1/ocr/. Même protocole et mêmes limites que /api/v1/ocr/{ingressKey}. Aucun relevé ne remplace une variable avant sa réception. Les associations sont configurées dans Table de marque et sauvegardées avec les réglages API dans le .appsport à la demande.",
        "responses": {
          "101": {
            "description": "Source OCR connectée sans clé."
          },
          "400": {
            "description": "Paramètres ou JSON invalides.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Sport, opération, compte, équipe ou licence non autorisé.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Match indisponible, contexte modifié ou action refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "LocalBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clé locale de 64 caractères hexadécimaux, copiée depuis Local API. Facultative seulement si Require API key est désactivé sur le poste. Distincte de la clé d’entrée OCR."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "success": {
            "type": "boolean"
          },
          "errors": {
            "type": "object",
            "additionalProperties": true
          },
          "error_id": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "State": {
        "type": "object",
        "required": [
          "contextKey",
          "context",
          "buttons",
          "texts"
        ],
        "properties": {
          "contextKey": {
            "type": "string",
            "example": "futsal:1:2"
          },
          "context": {
            "type": "object",
            "properties": {
              "sport": {
                "type": "string"
              },
              "championshipId": {
                "type": "string"
              },
              "matchId": {
                "type": "string"
              }
            }
          },
          "canEdit": {
            "type": "boolean"
          },
          "editing": {
            "type": "boolean"
          },
          "buttons": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/Button"
            }
          },
          "texts": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "actions": {
            "type": "object",
            "additionalProperties": true
          },
          "players": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "events": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "eventTypes": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "statistics": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "selections": {
            "type": "object"
          },
          "prepared": {
            "type": "object",
            "nullable": true
          },
          "ocr": {
            "type": "object",
            "description": "Présent si des relevés OCR sont appliqués au match. Valeurs graphiques, sans écriture automatique dans les données du match.",
            "properties": {
              "contextKey": {
                "type": "string"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              },
              "values": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "mappedValues": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          },
          "match": {
            "$ref": "#/components/schemas/MatchDetails"
          },
          "matchDays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MatchDay"
            }
          },
          "scorers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scorer"
            }
          },
          "standings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Standing"
            }
          }
        },
        "additionalProperties": true
      },
      "Button": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "on": {
            "type": "boolean"
          },
          "preview": {
            "type": "boolean"
          },
          "gestures": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "left",
                "middle",
                "right",
                "reset"
              ]
            }
          }
        }
      },
      "Command": {
        "type": "object",
        "required": [
          "id",
          "state",
          "statusUrl"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "state": {
            "type": "string",
            "enum": [
              "pending",
              "accepted",
              "failed"
            ]
          },
          "statusUrl": {
            "type": "string"
          },
          "contextKey": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "MatchSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "sport": {
            "type": "string"
          },
          "championshipId": {
            "type": "string"
          },
          "dayId": {
            "type": "string"
          },
          "dayLabel": {
            "type": "string"
          },
          "date": {
            "type": "string"
          },
          "time": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "timer": {
            "type": "string"
          },
          "period": {
            "type": "string"
          },
          "homeTeamId": {
            "type": "string"
          },
          "awayTeamId": {
            "type": "string"
          },
          "homeTeam": {
            "type": "string"
          },
          "awayTeam": {
            "type": "string"
          },
          "homeName": {
            "type": "string"
          },
          "awayName": {
            "type": "string"
          },
          "homeShortName": {
            "type": "string"
          },
          "awayShortName": {
            "type": "string"
          },
          "homeScore": {
            "type": "number",
            "nullable": true
          },
          "awayScore": {
            "type": "number",
            "nullable": true
          },
          "homeShootoutScore": {
            "type": "number",
            "nullable": true
          },
          "awayShootoutScore": {
            "type": "number",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "MatchDetails": {
        "type": "object",
        "description": "Données sportives enregistrées. Textes absents vides, scores absents null ; aucun numéro de journée déduit de son ID. Au tennis, les scores sont les sets gagnés et les noms incluent les partenaires en double.",
        "properties": {
          "id": {
            "type": "string"
          },
          "sport": {
            "type": "string"
          },
          "championshipId": {
            "type": "string"
          },
          "dayId": {
            "type": "string"
          },
          "dayLabel": {
            "type": "string"
          },
          "date": {
            "type": "string"
          },
          "time": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "timer": {
            "type": "string"
          },
          "period": {
            "type": "string"
          },
          "homeTeamId": {
            "type": "string"
          },
          "awayTeamId": {
            "type": "string"
          },
          "homeTeam": {
            "type": "string"
          },
          "awayTeam": {
            "type": "string"
          },
          "homeName": {
            "type": "string"
          },
          "awayName": {
            "type": "string"
          },
          "homeShortName": {
            "type": "string"
          },
          "awayShortName": {
            "type": "string"
          },
          "homeScore": {
            "type": "number",
            "nullable": true
          },
          "awayScore": {
            "type": "number",
            "nullable": true
          },
          "homeShootoutScore": {
            "type": "number",
            "nullable": true
          },
          "awayShootoutScore": {
            "type": "number",
            "nullable": true
          },
          "competition": {
            "type": "string"
          },
          "season": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "stadium": {
            "type": "string"
          },
          "venue": {
            "type": "string"
          },
          "officials": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "role": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              },
              "additionalProperties": false
            }
          },
          "commentators": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "role": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              },
              "additionalProperties": false
            }
          },
          "weather": {
            "type": "object",
            "properties": {
              "sky": {
                "type": "string"
              },
              "temperature": {
                "type": "string"
              },
              "humidity": {
                "type": "string"
              },
              "wind": {
                "type": "string"
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "MatchDay": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "relation": {
            "type": "string",
            "enum": [
              "previous",
              "current",
              "next"
            ]
          },
          "matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MatchSummary"
            }
          }
        },
        "additionalProperties": false
      },
      "Scorer": {
        "type": "object",
        "description": "Un événement de score sauvegardé. Hors tirs au but et événements optimistes. side désigne le bénéficiaire du score, y compris pour un CSC. Football/futsal/handball/hockey : 1 point ; basket : 1, 2 ou 3. Tableau vide au tennis.",
        "properties": {
          "eventId": {
            "type": "string"
          },
          "playerId": {
            "type": "string"
          },
          "playerName": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "side": {
            "type": "string",
            "enum": [
              "home",
              "away"
            ]
          },
          "type": {
            "type": "integer",
            "enum": [
              1,
              2,
              6
            ]
          },
          "timer": {
            "type": "string",
            "nullable": true
          },
          "additionalTimer": {
            "type": "integer",
            "nullable": true
          },
          "points": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3
          },
          "ownGoal": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "Standing": {
        "type": "object",
        "description": "Classement chargé dans la page locale, courant dans le snapshot web. dayId identifie un éventuel historique ; points peut être négatif après une pénalité.",
        "properties": {
          "entityId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "dayId": {
            "type": "string"
          },
          "phaseId": {
            "type": "string"
          },
          "groupId": {
            "type": "string"
          },
          "groupName": {
            "type": "string"
          },
          "rank": {
            "type": "number",
            "nullable": true
          },
          "previousRank": {
            "type": "number",
            "nullable": true
          },
          "rankMovement": {
            "type": "number",
            "nullable": true
          },
          "played": {
            "type": "number",
            "nullable": true
          },
          "wins": {
            "type": "number",
            "nullable": true
          },
          "draws": {
            "type": "number",
            "nullable": true
          },
          "losses": {
            "type": "number",
            "nullable": true
          },
          "goalsFor": {
            "type": "number",
            "nullable": true
          },
          "goalsAgainst": {
            "type": "number",
            "nullable": true
          },
          "goalDifference": {
            "type": "number",
            "nullable": true
          },
          "points": {
            "type": "number",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "MatchSnapshot": {
        "type": "object",
        "required": [
          "contextKey",
          "context",
          "match",
          "matchDays",
          "scorers",
          "standings"
        ],
        "properties": {
          "contextKey": {
            "type": "string"
          },
          "context": {
            "type": "object",
            "properties": {
              "sport": {
                "type": "string"
              },
              "championshipId": {
                "type": "string"
              },
              "matchId": {
                "type": "string"
              }
            }
          },
          "match": {
            "$ref": "#/components/schemas/MatchDetails"
          },
          "matchDays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MatchDay"
            }
          },
          "scorers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scorer"
            }
          },
          "standings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Standing"
            }
          }
        },
        "additionalProperties": false
      }
    }
  }
}
