{"openapi":"3.1.0","info":{"title":"API V-Box — Golf de Nîmes Vacquerolles","version":"1.0.0","description":"Réservation du simulateur V-Box par les pros du golf. Authentification par clé API personnelle (Bearer), délivrée par l'accueil du golf. Toutes les dates sont en ISO 8601 ; le golf applique ses horaires en Europe/Paris. Limite : 120 requêtes par minute et par clé."},"servers":[{"url":"https://vbox.thomasfanlogolf.fr/api/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Clé API au format vbx_<prefix>_<secret>"}},"schemas":{"AvailabilityResponse":{"type":"object","properties":{"timezone":{"type":"string"},"slotMinutes":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"free":{"type":"array","items":{"type":"object","properties":{"start":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"end":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["start","end"],"additionalProperties":false}},"quotas":{"type":"array","items":{"type":"object","properties":{"weekStart":{"type":"string"},"limitMin":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"usedMin":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remainingMin":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]}},"required":["weekStart","limitMin","usedMin","remainingMin"],"additionalProperties":false}},"maxDaysAhead":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["timezone","slotMinutes","free","quotas","maxDaysAhead"],"additionalProperties":false},"ReservationDto":{"type":"object","properties":{"id":{"type":"string"},"start":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"end":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"status":{"type":"string","enum":["HELD","CONFIRMED","CANCELLED"]},"holdExpiresAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"clientName":{"type":["string","null"]},"clientEmail":{"type":["string","null"]},"note":{"type":["string","null"]},"externalRef":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["id","start","end","status","holdExpiresAt","clientName","clientEmail","note","externalRef","createdAt"],"additionalProperties":false},"ReservationListResponse":{"type":"object","properties":{"reservations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"start":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"end":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"status":{"type":"string","enum":["HELD","CONFIRMED","CANCELLED"]},"holdExpiresAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"clientName":{"type":["string","null"]},"clientEmail":{"type":["string","null"]},"note":{"type":["string","null"]},"externalRef":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["id","start","end","status","holdExpiresAt","clientName","clientEmail","note","externalRef","createdAt"],"additionalProperties":false}}},"required":["reservations"],"additionalProperties":false},"CreateReservationBody":{"type":"object","properties":{"start":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"end":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"hold":{"default":false,"type":"boolean"},"holdMinutes":{"default":35,"type":"integer","minimum":5,"maximum":60},"clientName":{"type":"string","maxLength":120},"clientEmail":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"note":{"type":"string","maxLength":500},"externalRef":{"type":"string","maxLength":120}},"required":["start","end"]},"ApiErrorBody":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["UNAUTHORIZED","FORBIDDEN","VALIDATION","NOT_FOUND","SLOT_UNAVAILABLE","OUTSIDE_OPENING_HOURS","INVALID_DURATION","TOO_FAR_AHEAD","IN_THE_PAST","QUOTA_EXCEEDED","HOLD_EXPIRED","RATE_LIMITED","INTERNAL"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}},"paths":{"/availability":{"get":{"summary":"Plages libres du simulateur et quotas du pro","description":"free = horaires d'ouverture − blocages − réservations actives, limité à [maintenant, maintenant + maxDaysAhead]. Fenêtre de 62 jours maximum.","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date-time"},"description":"Début de la fenêtre (ISO 8601 avec fuseau, ex. 2026-11-16T00:00:00+01:00)"},{"name":"to","in":"query","required":true,"schema":{"type":"string","format":"date-time"},"description":"Fin de la fenêtre (exclue)"}],"responses":{"200":{"description":"Disponibilités","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailabilityResponse"}}}},"400":{"description":"Erreur : VALIDATION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"401":{"description":"Erreur : UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"429":{"description":"Erreur : RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"500":{"description":"Erreur : INTERNAL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}}}}},"/reservations":{"get":{"summary":"Réservations du pro (tous statuts) qui chevauchent la fenêtre","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date-time"},"description":"Début de la fenêtre (ISO 8601 avec fuseau, ex. 2026-11-16T00:00:00+01:00)"},{"name":"to","in":"query","required":true,"schema":{"type":"string","format":"date-time"},"description":"Fin de la fenêtre (exclue)"}],"responses":{"200":{"description":"Liste","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReservationListResponse"}}}},"400":{"description":"Erreur : VALIDATION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"401":{"description":"Erreur : UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"429":{"description":"Erreur : RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"500":{"description":"Erreur : INTERNAL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}}}},"post":{"summary":"Créer une réservation (confirmée, ou provisoire avec hold: true)","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Clé unique par tentative : renvoyer la même clé renvoie la même réservation (en-tête Idempotent-Replayed: true)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateReservationBody"}}}},"responses":{"201":{"description":"Réservation créée","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReservationDto"}}}},"400":{"description":"Erreur : VALIDATION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"401":{"description":"Erreur : UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"409":{"description":"Erreur : SLOT_UNAVAILABLE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"422":{"description":"Erreur : OUTSIDE_OPENING_HOURS, INVALID_DURATION, TOO_FAR_AHEAD, IN_THE_PAST, QUOTA_EXCEEDED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"429":{"description":"Erreur : RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"500":{"description":"Erreur : INTERNAL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}}}}},"/reservations/{id}":{"delete":{"summary":"Annuler une réservation du pro (idempotent)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Annulée"},"401":{"description":"Erreur : UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"404":{"description":"Erreur : NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"422":{"description":"Erreur : IN_THE_PAST","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"429":{"description":"Erreur : RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"500":{"description":"Erreur : INTERNAL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}}}}},"/reservations/{id}/confirm":{"post":{"summary":"Confirmer une réservation provisoire","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Réservation confirmée","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReservationDto"}}}},"401":{"description":"Erreur : UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"404":{"description":"Erreur : NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"409":{"description":"Erreur : HOLD_EXPIRED, SLOT_UNAVAILABLE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"429":{"description":"Erreur : RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}},"500":{"description":"Erreur : INTERNAL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorBody"}}}}}}}}}