API REST — CovoitGroupe

Documentation pour les intégrations tierces

Introduction

CovoitGroupe expose une API REST JSON qui permet à des outils tiers de créer et gérer des covoiturages sans passer par l'interface web. Cas d'usage typiques :

  • Un outil de gestion d'asso ou de club crée automatiquement un covoiturage lors d'un événement.
  • Une application mobile pousse la liste des membres et des voitures disponibles.
  • Un script récupère l'état des présences et des assignations pour les afficher ailleurs.

Une fois créé via l'API, l'événement est accessible via l'interface web habituelle — les participants peuvent répondre, les conducteurs peuvent être modifiés, etc.

Authentification

CovoitGroupe utilise un système de double slug à la place d'une authentification par token ou session :

🔒 Slug admin

Accès complet en lecture et écriture. À garder secret — c'est la clé de l'événement. Toutes les routes d'écriture utilisent ce slug.

👥 Slug visiteur

Accès collaboratif : lecture, ajout de participants et de conducteurs, assignations. C'est ce lien que vous partagez avec les participants.

Les deux slugs sont des chaînes de 10 caractères alphanumériques générées aléatoirement (environ 3,6×10¹⁵ combinaisons), retournés à la création de l'événement. Aucun header d'authentification n'est nécessaire : posséder le slug admin suffit. La suppression de l'événement lui-même requiert le slug admin.

Format & limites

Requêtes

  • • Corps JSON, header Content-Type: application/json
  • • Base URL : https://groupe.covoitequipe.fr
  • • CORS ouvert pour tous les domaines sur /api/*

Rate limiting (par IP)

  • • Création événement : 10 / heure
  • • Ajout participant : 60 / heure
  • • Ajout conducteur : 30 / heure
  • • Global : 3 000 / jour, 600 / heure

Réponses d'erreur

{ "error": "Event not found" }        → 404
{ "error": "Discussion disabled" }    → 403
{ "error": "Message limit reached" }  → 429
{ "error": "Internal server error" }  → 500

Couverture UI ↔ API

L'API permet de réaliser l'intégralité des actions disponibles dans l'interface web.

Action dans l'UI Endpoint API Statut
Créer un événementPOST /api/events
Créer event + participants + voitures d'un coupPOST /api/events/bulk
Modifier les infos de l'événementPUT /api/events/<admin_slug>
Dupliquer un événementPOST /api/events/<admin_slug>/duplicate
Lire tout l'état (présences, assignations…)GET /api/events/<slug>
Ajouter un participantPOST /api/events/<slug>/participants
Importer une liste de participantsPOST /api/events/<slug>/participants/import
Marquer présence / absencePUT /api/participants/<id>
Définir le besoin de covoituragePUT /api/participants/<id>
Assigner un participant à un conducteurPUT /api/participants/<id>
Assigner un point de ramassagePUT /api/participants/<id>
Supprimer un participantDELETE /api/participants/<id>
Ajouter un conducteurPOST /api/events/<slug>/drivers
Importer une liste de conducteursPOST /api/events/<slug>/drivers/import
Modifier places / aller-retour / notes d'un conducteurPUT /api/drivers/<id>
Supprimer un conducteurDELETE /api/drivers/<id>
Ajouter / modifier / supprimer un point de ramassagePOST, PUT, DELETE /api/…/pickup_points
Ajouter / modifier / supprimer un accompagnantPOST, PUT, DELETE /api/…/accompagnants
Lire / poster / supprimer un messageGET, POST, DELETE /api/events/<slug>/messages

Événements

POST /api/events/bulk Recommandé pour les intégrations

Crée un événement complet en une seule requête atomique : infos de l'événement, liste de participants, conducteurs et points de ramassage. Idéal pour les imports depuis un outil tiers.

Corps de la requête

{
  "event": {
    "title": "Répétition chorale",       // requis
    "date": "2026-06-20",               // format YYYY-MM-DD
    "location": "Salle Molière, Lyon",
    "address": "12 rue Molière, Lyon",
    "startTime": "20:00",               // heure de début (HH:MM)
    "departureTime": "19:30",           // heure de départ
    "note": "Répétition de printemps",
    "official_link": "https://…",
    "contextType": "music",             // generic | sport | music | corporate | scout
    "returnEnabled": 1,                 // 0 = aller seul, 1 = aller + retour
    "mode": "classic"                   // "classic" (défaut) | "light" (lien unique sans covoiturage)
  },
  "participants": [
    { "name": "Alice" },
    { "name": "Bob", "is_coming": 1 },  // 1 = présent, 0 = absent, null = pas répondu
    { "name": "Claire" }
  ],
  "drivers": [
    { "name": "Marie", "seats": 4, "drives_outbound": 1, "drives_return": 0 },
    { "name": "Paul",  "seats": 3, "drives_outbound": 1, "drives_return": 1 }
  ],
  "pickup_points": [
    { "name": "Gare Part-Dieu", "address": "Place Charles Béraudier", "time": "19:30" },
    { "name": "Mairie du 8e",   "address": "Place Ambroise Courtois",  "time": "19:45" }
  ]
}

Réponse 201 Created

{
  "admin_slug": "a1b2c3d4e5",
  "view_slug":  "f6g7h8i9j0",
  "admin_url":  "https://groupe.covoitequipe.fr/a1b2c3d4e5",  // lien organisateur (privé)
  "view_url":   "https://groupe.covoitequipe.fr/f6g7h8i9j0"   // lien participants (à partager)
}
⚠️ Conservez admin_slug — c'est la seule façon de modifier l'événement ensuite. Limites : 200 participants, 100 conducteurs, 20 points de ramassage par requête.
POST /api/events

Crée un événement vide. Utilisez ensuite les endpoints participants/conducteurs pour le peupler, ou préférez POST /api/events/bulk.

Corps de la requête

// Mêmes champs que dans "event" de /api/events/bulk
{ "title": "Sortie scouts", "date": "2026-07-10", "contextType": "scout", "mode": "classic" }

Réponse 201 Created

{ "slug": "a1b2c3d4e5", "id": 42, "mode": "classic",
  "view_slug": "f6g7h8i9j0",
  "admin_url": "…/a1b2c3d4e5", "view_url": "…/f6g7h8i9j0" }
GET /api/events/<slug>

Retourne l'état complet de l'événement : infos, participants (avec présences et assignations), conducteurs, points de ramassage, accompagnants. Fonctionne avec le slug admin et le slug visiteur (le slug visiteur ne retourne pas view_slug dans la réponse).

Réponse 200 OK

{
  "event": {
    // ni slug, ni view_slug, ni identifiants internes : la réponse est
    // lisible avec le lien participant, elle ne doit rien révéler de plus
    "title": "Répétition chorale", "date": "2026-06-20",
    "location": "Salle Molière", "address": "12 rue Molière",
    "startTime": "20:00", "departureTime": "19:30",
    "note": "…", "official_link": "…",
    "contextType": "music", "returnEnabled": 1,
    "mode": "classic",                  // "classic" | "light"
    "pickupEnabled": 0,                 // ramassage activé
    "cargoEnabled": 0,                  // coffres activés
    "discussionEnabled": 1,             // espace de discussion activé
    "accompagnantsEnabled": 0,          // accompagnants activés
    "created_at": "2026-06-10T14:32:00"
  },
  "participants": [
    { "id": 1, "name": "Alice", "is_coming": null,
      "needs_carpool": 0, "needs_carpool_return": 0,
      "driver_id": null, "return_driver_id": null,
      "pickup_point_id": null, "acc_driver_id": null },
    { "id": 2, "name": "Bob", "is_coming": 1,
      "needs_carpool": 1, "driver_id": 5, "pickup_point_id": 3,
      "acc_driver_id": null }
  ],
  "drivers": [
    { "id": 5, "name": "Marie", "seats": 4,
      "drives_outbound": 1, "drives_return": 0,
      "pickup_point_ids": [3],
      "cargo_size": null,               // null | "small" | "medium" | "large"
      "notes": "Voiture rouge, plaque AB-123" }
  ],
  "pickup_points": [
    { "id": 3, "name": "Gare Part-Dieu", "address": "…", "time": "19:30" }
  ],
  "accompagnants": [],
  "messages": [                        // présent si discussionEnabled = 1
    { "id": 1, "author": "Marie", "body": "Qui conduit ?", "created_at": "2026-06-18T19:42:00Z" }
  ],
  "view_slug": "f6g7h8i9j0",           // présent uniquement avec le slug admin
  "is_view_mode": false
}
Lire les présences : is_coming vaut null (pas répondu), 1 (présent) ou 0 (absent).
PUT /api/events/<admin_slug>

Met à jour les informations de l'événement. Nécessite le slug admin.

{
  "title": "Nouveau titre",
  "date": "2026-06-21",
  "location": "…", "address": "…",
  "startTime": "20:30", "departureTime": "19:45",
  "note": "…", "official_link": "…",
  "contextType": "generic",
  "returnEnabled": 0,
  "pickupEnabled": 1,                 // activer/désactiver le ramassage
  "cargoEnabled": 0,
  "discussionEnabled": 1,             // activer/désactiver l'espace de discussion
  "accompagnantsEnabled": 0
}

Le champ mode (classic/light) est fixé à la création et ne peut pas être modifié via cet endpoint.

Réponse

{ "success": true }
POST /api/events/<admin_slug>/duplicate

Duplique l'événement pour une nouvelle date. Copie les conducteurs, les points de ramassage et tous les réglages (mode, pickupEnabled, discussionEnabled, etc.). Les participants ne sont pas copiés (à remplir ensuite).

{ "date": "2026-07-18" }

Réponse 201 Created

{ "slug": "z9y8x7w6v5" }  // admin slug du nouvel événement

Participants

POST /api/events/<slug>/participants

Ajoute un participant à l'événement. Fonctionne avec le slug admin et le slug visiteur (modèle collaboratif : chacun peut s'ajouter depuis le lien partagé).

{ "name": "Alice" }

Réponse 201 Created

{ "id": 12, "name": "Alice", "is_coming": null,
  "needs_carpool": 0, "needs_carpool_return": 0,
  "driver_id": null, "return_driver_id": null,
  "pickup_point_id": null }
POST /api/events/<slug>/participants/import

Importe une liste de participants en une seule requête. Maximum 200 par appel.

{ "items": [{ "name": "Alice" }, { "name": "Bob" }, { "name": "Claire" }] }

Réponse 201 Created

{ "created": [ { "id": 12, "name": "Alice", … }, … ] }
PUT /api/participants/<id>

Met à jour un participant : nom, présence, besoin covoiturage, conducteur assigné, point de ramassage. Tous les champs sont requis (envoyez l'objet participant complet).

{
  "name": "Alice Dupont",
  "is_coming": 1,                // 1 = présent, 0 = absent, null = pas répondu
  "needs_carpool": 1,            // 1 = besoin d'une place à l'aller
  "needs_carpool_return": 0,     // 1 = besoin d'une place au retour
  "driver_id": 5,                // null pour désassigner (aller)
  "return_driver_id": null,      // null pour désassigner (retour)
  "pickup_point_id": 3,          // null pour désassigner
  "acc_driver_id": null          // ID d'un accompagnant avec voiture (null sinon)
}

Réponse

{ "success": true }
DELETE /api/participants/<id>

Supprime un participant.

{ "success": true }

Conducteurs

POST /api/events/<slug>/drivers

Ajoute un conducteur. Fonctionne avec le slug admin et le slug visiteur (modèle collaboratif).

{
  "name": "Marie",
  "seats": 4,                    // entre 1 et 9, défaut 2
  "notes": "Voiture rouge"       // note visible dans le mode répartition (optionnel)
}

Réponse 201 Created

{ "id": 5, "name": "Marie", "seats": 4,
  "drives_outbound": 1, "drives_return": 0,
  "pickup_point_ids": [],
  "notes": "Voiture rouge" }
POST /api/events/<slug>/drivers/import

Importe plusieurs conducteurs. Maximum 100 par appel.

{ "items": [{ "name": "Marie", "seats": 4 }, { "name": "Paul", "seats": 3 }] }

Réponse 201 Created

{ "created": [ { "id": 5, "name": "Marie", … }, … ] }
PUT /api/drivers/<id>

Met à jour un conducteur.

{
  "name": "Marie Martin",
  "seats": 5,
  "drives_outbound": 1,           // conduit à l'aller
  "drives_return": 1,             // conduit au retour
  "pickup_point_ids": [3, 7],     // IDs des points de ramassage desservis
  "cargo_size": "medium",         // null | "small" | "medium" | "large"
  "notes": "Voiture rouge"        // note affichée dans le mode répartition (null pour effacer)
}

Réponse

{ "success": true }
DELETE /api/drivers/<id>

Supprime un conducteur. Les participants qui lui étaient assignés sont automatiquement désassignés (driver_id → null).

{ "success": true }

Points de ramassage

POST /api/events/<slug>/pickup_points
{ "name": "Gare Part-Dieu", "address": "Place Charles Béraudier", "time": "19:30" }

Réponse 201 Created

{ "id": 3, "name": "Gare Part-Dieu", "address": "…", "time": "19:30" }
PUT /api/pickup_points/<id>
{ "name": "Gare Part-Dieu", "address": "Place Charles Béraudier, 69003", "time": "19:45" }

Réponse

{ "success": true }
DELETE /api/pickup_points/<id>

Supprime un point de ramassage. Les participants qui lui étaient assignés sont automatiquement désassignés.

{ "success": true }

Accompagnants

POST /api/events/<slug>/accompagnants

Ajoute un accompagnant (groupe sans covoiturage, optionnellement avec un véhicule). Nécessite d'activer accompagnantsEnabled sur l'événement.

{
  "name": "Famille Dupont",
  "people_count": 3,    // nombre de personnes dans le groupe
  "has_car": 1,         // 1 = possède un véhicule
  "seats": 4            // places disponibles dans ce véhicule (si has_car = 1)
}

Réponse 201 Created

{ "id": 8, "name": "Famille Dupont", "people_count": 3, "has_car": 1, "seats": 4 }
PUT /api/accompagnants/<id>
{
  "name": "Famille Dupont",
  "people_count": 2,
  "has_car": 0,
  "seats": 0,
  "driver_id": null,    // ID d'un conducteur principal (si l'accompagnant voyage avec lui)
  "acc_car_id": null    // ID d'un autre accompagnant avec voiture
}

Réponse

{ "success": true }
DELETE /api/accompagnants/<id>
{ "success": true }

Discussion

Espace de messages rattaché à l'événement. Disponible uniquement si discussionEnabled = 1 (à activer via PUT /api/events/<admin_slug>). Maximum 200 messages par événement.

GET /api/events/<slug>/messages

Retourne les messages dans l'ordre chronologique. Fonctionne avec le slug admin et le slug visiteur.

Réponse 200 OK

[
  { "id": 1, "author": "Marie", "body": "Qui conduit vendredi ?", "created_at": "2026-06-18T19:42:00Z" },
  { "id": 2, "author": "Paul",  "body": "Je peux prendre 3 personnes", "created_at": "2026-06-18T20:01:00Z" }
]
POST /api/events/<slug>/messages

Poste un message. Fonctionne avec le slug admin et le slug visiteur. Retourne 403 si la discussion est désactivée sur l'événement.

{ "author": "Marie", "body": "Qui conduit vendredi ?" }

Réponse 201 Created

{ "id": 1, "author": "Marie", "body": "Qui conduit vendredi ?", "created_at": "2026-06-18T19:42:00Z" }
DELETE /api/events/<admin_slug>/messages/<id>

Supprime un message. Nécessite le slug admin (non disponible depuis le lien visiteur).

{ "success": true }

Exemple complet — Python

Créer un covoiturage depuis un script Python, puis récupérer l'état des présences après que les participants ont répondu.

import requests

BASE = "https://groupe.covoitequipe.fr"

# 1. Créer l'événement avec tout le contenu
resp = requests.post(f"{BASE}/api/events/bulk", json={
    "event": {
        "title": "Répétition chorale du 20 juin",
        "date": "2026-06-20",
        "location": "Salle Molière, Lyon",
        "startTime": "20:00",
        "contextType": "music",
        "discussionEnabled": 1
    },
    "participants": [{"name": n} for n in ["Alice", "Bob", "Claire", "David"]],
    "drivers": [
        {"name": "Marie", "seats": 4, "drives_outbound": 1, "drives_return": 1},
        {"name": "Paul",  "seats": 3, "drives_outbound": 1, "drives_return": 0},
    ],
    "pickup_points": [
        {"name": "Gare Part-Dieu", "address": "Place Charles Béraudier", "time": "19:30"}
    ]
})

slugs = resp.json()
admin_slug = slugs["admin_slug"]
view_url   = slugs["view_url"]

print(f"Lien participants : {view_url}")
print(f"Lien organisateur : {slugs['admin_url']}")

# 2. Plus tard : récupérer l'état après que les gens ont répondu
state = requests.get(f"{BASE}/api/events/{admin_slug}").json()

for p in state["participants"]:
    statut = {1: "✓ Présent", 0: "✗ Absent", None: "? Pas répondu"}[p["is_coming"]]
    driver = next((d for d in state["drivers"] if d["id"] == p["driver_id"]), None)
    voiture = f" → {driver['name']}" if driver else ""
    print(f"  {p['name']} — {statut}{voiture}")