API publique / Public API
Référence API complète (français) / Complete API reference (French).
Référence originale en français. Original reference in French.
Télécharger le Markdown / Download Markdown
Cette API permet a un site de club ou a un service tiers de lire les terrains, afficher les disponibilites et creer ou annuler des reservations dans Okkre.
Base URL :
/api/v1/clubs/{clubId}
Toutes les requetes doivent envoyer une cle API dans le header Authorization.
Authorization: Bearer <cle_api>
Les deux informations sont necessaires : {clubId} dans l'URL identifie le club cible et la cle API autorise l'appel. Le serveur verifie que la cle appartient bien a ce club ; une cle seule ne suffit donc pas pour construire l'URL.
Prerequis
- Le club doit etre configure dans Okkre.
- Une cle API doit etre creee dans les reglages API du club.
- Le serveur doit avoir
SUPABASE_SERVICE_ROLE_KEY. - La base doit avoir les migrations appliquees, notamment
20260829120000_public_api_service_role_grants.sql. - Pour les appels navigateur depuis un autre domaine, configurer
PUBLIC_API_ALLOWED_ORIGINScote serveur, par exemple :
PUBLIC_API_ALLOWED_ORIGINS=https://club.example,https://staging.club.example
Les appels serveur a serveur ne sont pas concernes par CORS.
Droits des cles
Deux niveaux de cle existent :
| Droit | Capacites |
|---|---|
read | Lire les terrains, disponibilites et reservations creees par l'API. |
book | Tout read, plus creer et annuler des reservations. |
Une cle revoquee renvoie 401 api_key_revoked. Une cle read utilisee sur un endpoint de reservation renvoie 403 insufficient_scope.
La gestion des cles est reservee au proprietaire du club. Les contacts staff du club n'ont pas acces aux reglages ni aux cles API.
Endpoints
| Methode | Endpoint | Droit | Description |
|---|---|---|---|
GET | /courts | read | Liste les terrains actifs du club. |
GET | /availability?from=YYYY-MM-DD&to=YYYY-MM-DD&sport=padel&court_id=... | read | Liste les creneaux disponibles avec leur duree et leur prix configures. |
POST | /reservations | book | Cree une reservation idempotente. |
GET | /reservations/{externalId} | read | Lit une reservation creee par l'API. |
DELETE | /reservations/{externalId} | book | Annule une reservation creee par l'API. |
POST | /reservations/{externalId}/cancel | book | Variante explicite pour annuler une reservation. |
Format des erreurs
Toutes les erreurs utilisent ce format :
{
"error": {
"code": "validation_error",
"message": "date doit etre une date valide au format YYYY-MM-DD."
}
}
En developpement local, certaines erreurs Supabase ajoutent error.details avec l'operation et le code base de donnees. Ces details ne sont pas renvoyes en production.
Codes courants :
| HTTP | Code | Cas |
|---|---|---|
400 | validation_error | Corps JSON invalide, champ manquant, date ou horaire invalide. |
400 | invalid_period | Parametres from / to invalides ou periode superieure a 31 jours. |
401 | missing_api_key | Header Authorization absent. |
401 | invalid_authorization_header | Header different de Bearer <cle_api>. |
401 | api_key_not_found | Cle inconnue dans la base utilisee par le serveur. |
401 | api_key_revoked | Cle revoquee. |
403 | api_key_wrong_club | Cle valide mais appartenant a un autre club. |
403 | insufficient_scope | Cle read utilisee sur une action book. |
404 | club_not_found | Club introuvable pour les disponibilites. |
404 | reservation_not_found | Reservation API introuvable. |
409 | slot_unavailable | Terrain deja occupe sur ce creneau. |
500 | database_error | Operation Supabase impossible. |
500 | internal_error | Erreur serveur inattendue. |
503 | api_key_lookup_failed | Verification de cle impossible. |
GET /courts
Retourne les terrains actifs, tries par display_order.
curl "$BASE_URL/courts" \ -H "Authorization: Bearer $OKKRE_API_KEY"
Reponse :
{
"courts": [
{
"id": "8b5d5b7d-8b91-4b22-a6d8-3a6d5f5c2a10",
"name": "Terrain 1",
"sport": "padel",
"environment": "indoor",
"display_order": 1
}
]
}
GET /availability
Retourne les creneaux disponibles selon la duree configuree dans la regle d'ouverture (par exemple 120 minutes), avec le prix applicable a chaque creneau. Les disponibilites tiennent compte des regles d'ouverture publiees et des occupations confirmees.
Parametres :
| Parametre | Obligatoire | Description |
|---|---|---|
from | Oui | Date de debut au format YYYY-MM-DD. |
to | Non | Date de fin au format YYYY-MM-DD. Si absent, vaut from. |
sport | Non | Filtre par sport, par exemple padel. |
court_id | Non | Filtre par identifiant de terrain. |
La periode ne peut pas depasser 31 jours.
curl "$BASE_URL/availability?from=2026-09-12&to=2026-09-13&sport=padel" \ -H "Authorization: Bearer $OKKRE_API_KEY"
Reponse :
{
"availability": [
{
"court": "Terrain 1",
"date": "2026-09-12",
"start_time": "18:00",
"end_time": "20:00",
"price_cents": 2400
}
]
}
Le champ court est le nom lisible du terrain. Il peut etre reutilise tel quel dans POST /reservations. price_cents est le prix du creneau entier, en centimes d'euro : reutiliser exactement les heures renvoyees garantit le tarif affiche.
POST /reservations
Cree une reservation. Cet endpoint exige une cle book.
curl -X POST "$BASE_URL/reservations" \
-H "Authorization: Bearer $OKKRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "site-order-123",
"court": "Terrain 1",
"date": "2026-09-12",
"start_time": "18:00",
"end_time": "19:30",
"person": {
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com"
},
"notes": "Optionnel"
}'
Corps JSON :
| Champ | Obligatoire | Description |
|---|---|---|
external_id | Oui | Identifiant unique cote site ou integrateur. Sert a l'idempotence. |
court | Oui | Nom lisible du terrain, ou UUID du terrain. |
date | Oui | Date locale du club, au format YYYY-MM-DD. |
start_time | Oui | Heure locale du club au format HH:MM. Doit commencer a l'heure ou a la demi-heure. |
end_time | Oui | Heure locale du club au format HH:MM. La duree doit etre un multiple de 30 minutes. |
person.first_name | Oui | Prenom, 80 caracteres maximum. |
person.last_name | Oui | Nom, 80 caracteres maximum. |
person.email | Oui si pas de telephone | Email du client. Sert a retrouver une fiche existante. |
person.phone | Oui si pas d'email | Telephone du client, entre 6 et 30 caracteres. |
notes | Non | Notes libres, tronquees a 500 caracteres. |
Reponse 201 :
{
"reservation": {
"id": "32ec69b4-7fd9-4508-9f9d-022bb2dc0f8c",
"external_id": "site-order-123",
"court_id": "8b5d5b7d-8b91-4b22-a6d8-3a6d5f5c2a10",
"starts_at": "2026-09-12T16:00:00.000Z",
"ends_at": "2026-09-12T17:30:00.000Z",
"price_cents": 2400,
"status": "confirmed",
"cancellation_reason": null
},
"idempotent_replay": false
}
starts_at et ends_at sont renvoyes en ISO UTC. Les champs date, start_time et end_time envoyes par le client sont interpretes dans le fuseau horaire du club.
Idempotence
external_id doit etre stable pour une tentative de reservation donnee. Si le meme external_id existe deja pour le club, l'API ne cree pas de doublon et renvoie la reservation existante :
{
"reservation": {
"id": "32ec69b4-7fd9-4508-9f9d-022bb2dc0f8c",
"external_id": "site-order-123",
"court_id": "8b5d5b7d-8b91-4b22-a6d8-3a6d5f5c2a10",
"starts_at": "2026-09-12T16:00:00.000Z",
"ends_at": "2026-09-12T17:30:00.000Z",
"status": "confirmed",
"cancellation_reason": null
},
"idempotent_replay": true
}
La fiche client est recherchee d'abord par email, puis par telephone. Si elle n'existe pas, elle est creee avec le role client. Les demandes simultanees avec le meme nouvel email reutilisent la fiche creee par la premiere demande.
GET /reservations/{externalId}
Lit une reservation creee par l'API pour ce club.
curl "$BASE_URL/reservations/site-order-123" \ -H "Authorization: Bearer $OKKRE_API_KEY"
Reponse :
{
"reservation": {
"id": "32ec69b4-7fd9-4508-9f9d-022bb2dc0f8c",
"external_id": "site-order-123",
"court_id": "8b5d5b7d-8b91-4b22-a6d8-3a6d5f5c2a10",
"starts_at": "2026-09-12T16:00:00.000Z",
"ends_at": "2026-09-12T17:30:00.000Z",
"status": "confirmed",
"cancellation_reason": null
}
}
DELETE /reservations/{externalId}
Annule une reservation creee par l'API. La reservation reste dans l'historique avec le statut cancelled.
curl -X DELETE "$BASE_URL/reservations/site-order-123" \
-H "Authorization: Bearer $OKKRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "Annulation client" }'
Le corps JSON est optionnel. reason est tronque a 300 caracteres.
Reponse :
{
"reservation": {
"id": "32ec69b4-7fd9-4508-9f9d-022bb2dc0f8c",
"external_id": "site-order-123",
"court_id": "8b5d5b7d-8b91-4b22-a6d8-3a6d5f5c2a10",
"starts_at": "2026-09-12T16:00:00.000Z",
"ends_at": "2026-09-12T17:30:00.000Z",
"status": "cancelled",
"cancellation_reason": "Annulation client"
},
"idempotent_replay": false
}
Rejouer l'annulation d'une reservation deja annulee est idempotent et renvoie idempotent_replay: true.
POST /reservations/{externalId}/cancel
Fait la meme annulation logique que DELETE /reservations/{externalId}. Ce format est utile pour les clients qui ne veulent pas envoyer de corps avec une requete DELETE.
curl -X POST "$BASE_URL/reservations/site-order-123/cancel" \
-H "Authorization: Bearer $OKKRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "Annulation client" }'
Conseils d'integration
- Stocker la cle API cote serveur, jamais dans du JavaScript public.
- Utiliser une cle
readpour afficher les disponibilites publiques. - Utiliser une cle
bookuniquement sur un backend controle par le club. - Generer un
external_idunique et stable, par exemple l'identifiant de commande du site. - Verifier la disponibilite juste avant de creer une reservation.
- Traiter
409 slot_unavailablecomme un conflit normal : demander au client de choisir un autre creneau. - Ne pas parser
messagepour la logique applicative ; utilisererror.code.
Tests locaux
Avec la base Supabase locale demarree :
pnpm test:api
Le test lance un serveur Next sur le port 3107, cree ses donnees de test, puis les supprime. Il verifie les cles, les terrains actifs, la creation, la lecture, l'annulation, les conflits et les restrictions d'administration.