{
  "openapi": "3.1.0",
  "info": {
    "title": "Spaarprogramma-API",
    "version": "1.1.0",
    "description": "Koppel externe software (kassa, app, CRM, website, agent) aan het spaarprogramma van de club: leden opzoeken en aanmelden, punten bijschrijven en afboeken, beloningen inwisselen, extraatjes afstrepen en boekingen inzien. Elke sleutel hoort bij precies één club — je hoeft (en kunt) nooit een club meegeven. Schrijfacties vereisen een sleutel met de scope 'write' en komen allemaal in een auditlog. Puntenmutaties gaan automatisch mee naar de wallet-pas (Leat)."
  },
  "servers": [
    {
      "url": "https://rjeeafstaycxqlmwpccc.supabase.co/functions/v1/api"
    }
  ],
  "tags": [
    {
      "name": "Start",
      "description": "Controleer je sleutel en lees de spelregels van het programma."
    },
    {
      "name": "Leden",
      "description": "Zoeken, opzoeken op e-mail of pas, aanmelden en het profiel van één lid."
    },
    {
      "name": "Punten & beloningen",
      "description": "Saldo muteren en beloningen inwisselen. Alles gaat mee naar de wallet-pas."
    },
    {
      "name": "Extraatjes",
      "description": "Gratis extraatjes bij een boeking in een tijdvak: inzien en afstrepen."
    },
    {
      "name": "Boekingen",
      "description": "Baanboekingen uit Playtomic, per club of per lid."
    },
    {
      "name": "Toernooien",
      "description": "Toernooien en winnaars; een winst levert punten (en soms een extraatje) op."
    },
    {
      "name": "Club",
      "description": "Kerncijfers, vestigingen en binnengekomen formulieren."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Stuur de API-sleutel mee als `Authorization: Bearer bp_…`."
      }
    }
  },
  "paths": {
    "/v1/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Start"
        ],
        "summary": "Welke club en welke rechten heeft deze sleutel?",
        "description": "Begin hiermee. Geeft de clubnaam, de vestigingen en de scopes van de sleutel terug, zodat je weet of schrijfacties zijn toegestaan.",
        "responses": {
          "200": {
            "description": "Sleutelinfo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "club": {
                      "type": "object",
                      "properties": {
                        "slug": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "locations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/programme": {
      "get": {
        "operationId": "getProgramme",
        "tags": [
          "Start"
        ],
        "summary": "De spelregels: hoe je spaart, de beloningen en de extraatjes",
        "description": "Welkomstbonus, punten per euro (boeking en open match), vriend uitnodigen, recensie, toernooiwinst, de beloningencatalogus en de extraatjes per tijdvak. Handig om op je eigen scherm of website te tonen; alles komt uit de regels van de club.",
        "responses": {
          "200": {
            "description": "Programma",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "signupBonus": {
                      "type": "integer"
                    },
                    "earn": {
                      "type": "object",
                      "properties": {
                        "bookingPerEuro": {
                          "type": "number"
                        },
                        "openMatchPerEuro": {
                          "type": "number"
                        },
                        "referral": {
                          "type": "number"
                        },
                        "review": {
                          "type": "number"
                        },
                        "tournamentWin": {
                          "type": "number"
                        }
                      }
                    },
                    "rewards": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "cost": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "vouchers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "reward": {
                            "type": "string"
                          },
                          "trigger": {
                            "type": "string"
                          },
                          "conditions": {
                            "type": "object",
                            "properties": {}
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/members/lookup": {
      "get": {
        "operationId": "lookupMember",
        "tags": [
          "Leden"
        ],
        "summary": "Eén lid vinden op e-mail, pas of code",
        "description": "Voor een kassa of app: geef wat je hebt. Een e-mailadres, de inhoud van de QR op de spaarkaart (het lid-id), de code op de wallet-pas of de vriendencode. Geeft precies één lid terug of 404 — nooit een gok op naam.",
        "parameters": [
          {
            "name": "identifier",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "E-mail, lid-id (uuid), pascode of vriendencode"
          }
        ],
        "responses": {
          "200": {
            "description": "Lid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "full_name": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string",
                      "nullable": true
                    },
                    "phone": {
                      "type": "string",
                      "nullable": true
                    },
                    "tier": {
                      "type": "string",
                      "enum": [
                        "brons",
                        "zilver",
                        "goud",
                        "platina"
                      ]
                    },
                    "points_balance": {
                      "type": "integer"
                    },
                    "referral_code": {
                      "type": "string"
                    },
                    "birth_date": {
                      "type": "string",
                      "format": "date",
                      "nullable": true
                    },
                    "joined_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/members/{id}/bookings": {
      "get": {
        "operationId": "listMemberBookings",
        "tags": [
          "Boekingen"
        ],
        "summary": "Boekingen van één lid: gepland en gespeeld",
        "description": "Hetzelfde als 'Mijn boekingen' op de spaarkaart: als boeker of als medespeler, met wat het lid zelf betaalde, de punten en een eventueel extraatje. Gespeeld = laatste 180 dagen.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Boekingen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "upcoming": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {}
                      }
                    },
                    "past": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {}
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/members/{id}/redeem": {
      "post": {
        "operationId": "redeemReward",
        "tags": [
          "Punten & beloningen"
        ],
        "summary": "Een beloning inwisselen (scope: write)",
        "description": "Boekt de punten van de beloning af en legt de inwisseling vast, precies zoals de kassa-scanner. Te weinig punten: 409 `insufficient_points` met `missing`. Geef een `idempotency_key` mee als je de aanroep mogelijk herhaalt.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reward_id"
                ],
                "properties": {
                  "reward_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Uit GET /v1/rewards"
                  },
                  "location": {
                    "type": "string",
                    "description": "Vestigingsslug"
                  },
                  "idempotency_key": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Ingewisseld",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "redeemed": {
                      "type": "boolean"
                    },
                    "duplicate": {
                      "type": "boolean"
                    },
                    "redemption_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "reward": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "cost_points": {
                          "type": "integer"
                        }
                      }
                    },
                    "points_balance": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/overview": {
      "get": {
        "operationId": "getOverview",
        "tags": [
          "Club"
        ],
        "summary": "Kerncijfers van het spaarprogramma",
        "description": "Aantal leden, openstaande punten, boekingen deze maand, nieuwe leden, referrals, open matches, gespaarde en ingewisselde punten, populairste beloningen en de boekingschallenge. Gebruik dit voor vragen als 'hoe gaat het met het spaarprogramma'.",
        "responses": {
          "200": {
            "description": "Overzicht",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {}
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/locations": {
      "get": {
        "operationId": "listLocations",
        "tags": [
          "Club"
        ],
        "summary": "Cijfers per vestiging",
        "description": "Per vestiging: boekingen (30 dagen en totaal), unieke spelers, hoeveel leden er hun thuisbasis hebben, wat er is verzilverd, openstaande perks en baanomzet.",
        "responses": {
          "200": {
            "description": "Vestigingen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {}
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/members": {
      "get": {
        "operationId": "listMembers",
        "tags": [
          "Leden"
        ],
        "summary": "Zoek of doorloop leden",
        "description": "Zonder `q` de leden met het hoogste puntensaldo eerst. Met `q` zoekt hij op naam en e-mailadres. Elk lid is automatisch deelnemer aan het spaarprogramma.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Naam of e-mail"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Leden",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    },
                    "members": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "full_name": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string",
                            "nullable": true
                          },
                          "phone": {
                            "type": "string",
                            "nullable": true
                          },
                          "tier": {
                            "type": "string",
                            "enum": [
                              "brons",
                              "zilver",
                              "goud",
                              "platina"
                            ]
                          },
                          "points_balance": {
                            "type": "integer"
                          },
                          "referral_code": {
                            "type": "string"
                          },
                          "birth_date": {
                            "type": "string",
                            "format": "date",
                            "nullable": true
                          },
                          "joined_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createMember",
        "tags": [
          "Leden"
        ],
        "summary": "Meld een nieuw lid aan (scope: write)",
        "description": "Maakt een lid aan en kent de welkomstbonus toe volgens de loyaltyregel van de club. Bestaat het e-mailadres al, dan wordt niets aangemaakt en krijg je het bestaande lid terug met `already_member: true` — veilig om opnieuw aan te roepen.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "full_name",
                  "email"
                ],
                "properties": {
                  "full_name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "birth_date": {
                    "type": "string",
                    "format": "date"
                  },
                  "email_consent": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "already_member": {
                      "type": "boolean"
                    },
                    "bonus_points": {
                      "type": "integer"
                    },
                    "member": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "full_name": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string",
                          "nullable": true
                        },
                        "phone": {
                          "type": "string",
                          "nullable": true
                        },
                        "tier": {
                          "type": "string",
                          "enum": [
                            "brons",
                            "zilver",
                            "goud",
                            "platina"
                          ]
                        },
                        "points_balance": {
                          "type": "integer"
                        },
                        "referral_code": {
                          "type": "string"
                        },
                        "birth_date": {
                          "type": "string",
                          "format": "date",
                          "nullable": true
                        },
                        "joined_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/members/{id}": {
      "get": {
        "operationId": "getMember",
        "tags": [
          "Leden"
        ],
        "summary": "Eén lid, met puntenhistorie, vestigingen en perks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {}
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/members/{id}/points": {
      "post": {
        "operationId": "adjustMemberPoints",
        "tags": [
          "Punten & beloningen"
        ],
        "summary": "Punten bijschrijven of afboeken (scope: write)",
        "description": "Positief schrijft bij, negatief boekt af. Maximaal 1000 punten per aanroep. Geef een `idempotency_key` mee als je de aanroep mogelijk herhaalt — dezelfde sleutel telt maar één keer. Gebruik dit niet voor boekingen: punten over een baanboeking lopen automatisch via Playtomic. Voor een beloning uit de catalogus: POST /v1/members/{id}/redeem.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "points",
                  "description"
                ],
                "properties": {
                  "points": {
                    "type": "integer",
                    "minimum": -1000,
                    "maximum": 1000
                  },
                  "description": {
                    "type": "string"
                  },
                  "location": {
                    "type": "string",
                    "description": "Slug van de vestiging"
                  },
                  "idempotency_key": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Nieuw saldo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "points": {
                      "type": "integer"
                    },
                    "points_balance": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/rewards": {
      "get": {
        "operationId": "listRewards",
        "tags": [
          "Punten & beloningen"
        ],
        "summary": "De beloningencatalogus",
        "description": "Wat leden kunnen inwisselen en wat het kost. `active: false` = nog niet live.",
        "responses": {
          "200": {
            "description": "Beloningen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {}
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/perks": {
      "get": {
        "operationId": "listPerks",
        "tags": [
          "Extraatjes"
        ],
        "summary": "Extraatjes: wie heeft er iets tegoed",
        "description": "Standaard alleen de openstaande. Een extraatje hoort bij een boeking in een tijdvak en is geldig op de dag van die boeking (`valid_from` tot `valid_until`). Filter met `member_id` op één lid, bijvoorbeeld nadat je de pas hebt gescand.",
        "parameters": [
          {
            "name": "member_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "granted",
                "claimed",
                "expired",
                "canceled"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Perks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {}
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/perks/{id}/claim": {
      "post": {
        "operationId": "claimPerk",
        "tags": [
          "Extraatjes"
        ],
        "summary": "Een extraatje afstrepen: uitgegeven aan het lid (scope: write)",
        "description": "Kost geen punten. Mag alleen op de dag van de boeking (tussen valid_from en valid_until); anders 409 `not_valid_now`. Komt in het logboek van het dashboard op naam van je sleutel.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "location": {
                    "type": "string",
                    "description": "Vestigingsslug waar het is uitgegeven."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Afgestreept",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "perk_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings": {
      "get": {
        "operationId": "listBookings",
        "tags": [
          "Boekingen"
        ],
        "summary": "Baanboekingen in een periode",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Boekingen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {}
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/tournaments": {
      "get": {
        "operationId": "listTournaments",
        "tags": [
          "Toernooien"
        ],
        "summary": "Toernooien van de club (o.a. Kings & Queens) met inschrijvingen en winnaars",
        "description": "Gebruik dit om het juiste toernooi te vinden vóór je een winnaar meldt: het `id` gaat mee als `tournament_id` bij POST /v1/tournaments/wins. `wins` is het aantal al gemelde winnaars.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "vol",
                "gesloten",
                "afgerond",
                "geannuleerd"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Toernooien",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {}
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/tournaments/wins": {
      "get": {
        "operationId": "listTournamentWins",
        "tags": [
          "Toernooien"
        ],
        "summary": "Gemelde toernooiwinsten, nieuwste eerst",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Winsten",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {}
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "recordTournamentWin",
        "tags": [
          "Toernooien"
        ],
        "summary": "Meld de winnaar van een toernooi (Kings & Queens): punten en perk volgen automatisch",
        "description": "Geef `member_id` of het `email` van het lid. De club-regel bepaalt de punten (nu 75 voor een toernooiwinst) en zet ze op de pas; bij elke tweede winst binnen een jaar krijgt het lid de gratis baan als perk — het antwoord zegt het (`perk`). Idempotent: dezelfde winnaar voor hetzelfde `tournament_id`, of dezelfde `idempotency_key`, levert `duplicate: true` en deelt niets opnieuw uit. Vereist scope 'write'.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "member_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Het lid; of gebruik email."
                  },
                  "email": {
                    "type": "string",
                    "description": "E-mailadres van het lid, als member_id onbekend is."
                  },
                  "tournament_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Uit GET /v1/tournaments; maakt de melding idempotent per toernooi."
                  },
                  "title": {
                    "type": "string",
                    "description": "Naam van het toernooi als er geen tournament_id is, bijv. 'Kings & Queens 4 oktober'."
                  },
                  "won_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Wanneer gewonnen; standaard nu."
                  },
                  "location": {
                    "type": "string",
                    "description": "Vestigingsslug (zie GET /v1/me)."
                  },
                  "idempotency_key": {
                    "type": "string",
                    "description": "Eigen sleutel om een herhaalde melding onschadelijk te maken."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Al eerder gemeld (duplicate: true)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "duplicate": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Winst geboekt",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "win_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "duplicate": {
                      "type": "boolean"
                    },
                    "points": {
                      "type": "integer",
                      "description": "Toegekende punten."
                    },
                    "perk": {
                      "type": "string",
                      "nullable": true,
                      "description": "Bijv. 'Gratis baan (1 uur)' als deze winst de perk oplevert."
                    },
                    "wins_365d": {
                      "type": "integer",
                      "description": "Winsten van dit lid in de afgelopen 365 dagen, inclusief deze."
                    },
                    "member": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "points_balance": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/inbox": {
      "get": {
        "operationId": "listInbox",
        "tags": [
          "Club"
        ],
        "summary": "Binnengekomen formulieren van de website",
        "description": "Contactberichten, lesaanmeldingen, toernooi-inschrijvingen en eventaanvragen, nieuwste eerst. Gebruik `type` om er één soort uit te halen.",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "contact",
                "lesson",
                "tournament",
                "event"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Inbox",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {}
                  }
                }
              }
            }
          },
          "default": {
            "description": "Fout",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}