Skip to content

Référence API

Cette fiche donne une vue d'ensemble de l'API REST de TimePick, organisée par familles d'endpoints. Elle sert de point d'entrée pour un développeur ou un intégrateur qui doit appeler l'API depuis un script ou un outil externe — elle ne documente pas chaque endpoint en détail. Pour la liste exhaustive, chaque route est déclarée dans un fichier de server/src/routes/ sur GitHub.

API interne, sans garantie de stabilité

Cette API a été conçue pour l'interface web de TimePick elle-même, pas comme une API publique versionnée. Tant que le projet est en pré-V1 (état au 2026-07-13), ses chemins, ses formats de payload et ses codes d'erreur peuvent changer sans préavis ni période de dépréciation. Un usage en intégration externe reste possible mais doit être revalidé à chaque mise à jour de l'instance.

Base et authentification

URL de base : https://<votre-domaine.tld>/api (ou http://localhost:3000/api en développement).

La quasi-totalité des endpoints hors /setup, /public (lecture) et /health requièrent l'en-tête :

Authorization: Bearer <token>

Le <token> est un JWT de session, obtenu via le flux magic link :

  1. POST /api/auth/login avec { "email": "alice@example.org" } — envoie un email contenant un lien de connexion. La réponse est volontairement générique (anti-énumération), qu'un compte corresponde ou non à l'email :

    json
    {
      "data": {
        "message": "Si cet email est enregistré, vous recevrez un lien de connexion."
      }
    }
  2. Le lien reçu par email contient un token en paramètre. Le client appelle ensuite POST /api/auth/verify avec { "token": "<token>" }, qui ouvre la session et renvoie le JWT à utiliser dans l'en-tête Authorization pour tous les appels suivants.

  3. Pour terminer une session côté serveur, POST /api/auth/logout (authentifié, en-tête Authorization requis) révoque immédiatement toutes les sessions actives de l'utilisateur, pas seulement celle de l'appel : tout jeton émis avant cet appel, même non expiré, est ensuite rejeté par les routes protégées avec 401 et le code SESSION_INVALID.

Deux rôles distincts sont portés par le JWT : admin (accès à /api/admin/*) et user (accès à /api/me/* et aux réservations, si hasMemberAccess est vrai — c'est-à-dire si l'utilisateur est invité sur au moins un événement).

Détail des durées de vie de session/token, du rate limiting et des autres protections : voir Sécurité.

Enveloppe de réponse

Toutes les réponses de succès sont enveloppées dans un objet data. Exemple réel, réponse de POST /api/auth/verify :

json
{
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
    "sessionTTL": 7200,
    "user": {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "email": "alice@example.org",
      "firstName": "Alice",
      "lastName": "Dupont",
      "role": "user",
      "hasMemberAccess": true
    }
  }
}

Les réponses d'erreur suivent le même principe avec la clé error :

json
{
  "error": {
    "code": "INVALID_TOKEN",
    "message": "Lien de connexion invalide."
  }
}

Conversion snake_case → camelCase

La base de données stocke ses colonnes en snake_case (first_name, is_published…). Un middleware global (snakeToCamelMiddleware, monté avant toutes les routes dans server/src/app.ts) convertit automatiquement chaque réponse JSON de succès en camelCase (firstName, isPublished…) avant l'envoi au client. Les corps de requête envoyés par le client, eux, restent en camelCase — la conversion inverse est gérée côté requêtes SQL, pas au niveau de l'API.

Familles d'endpoints

PréfixeRôleAuthentification
/api/authConnexion par magic link (demande, vérification), rafraîchissement de session, déconnexion (révocation de toutes les sessions du compte), connexion de secours, renvoi d'invitationPublic pour la demande/vérification/secours ; user/admin pour le rafraîchissement et la déconnexion
/api/setupConfiguration initiale (création du premier admin avec prénom/nom, identité de l'organisation, test SMTP de démarrage — sortie de secours DELETE /smtp si la config enregistrée devient injoignable)Public — se ferme dès qu'un administrateur existe
/api/publicConsultation d'un événement publié par UUID et de ses créneaux, gestion de ses propres réservations, lecture de l'identité publique de l'organisation (façade d'accueil)Auth optionnelle en lecture (mode consultation sans session) ; user requis pour réserver/annuler
/api/meProfil et données de l'utilisateur connecté (événements, créneaux réservés, créneaux disponibles)user (accès à un membre admin également)
/api/adminAdministration : dashboard/analytics, utilisateurs, événements, créneaux, invitations, paramètres SMTP/emails/identité visuelle, identité de l'organisation (nom/logo/description/façade), configuration (polling, TTL), enveloppes d'email (shell-parts), codes de secours, notifications d'annulationadmin
/api/admin/uploadsUpload d'image pour l'éditeur d'emails (max 5 Mo, retourne une URL absolue)admin
/healthStatut de santé public, binaire (ok/degraded), sans détail internePublic

Deux préfixes publics annexes

Le code expose aussi /api/events (liste et détail des événements publiés, sans authentification, filtrage minimal) et /api/slots (regroupe des routes de gestion admin des créneaux et de réservation/annulation par un membre authentifié, sous un préfixe distinct de /api/admin et /api/public). Ces deux préfixes se recoupent fonctionnellement avec /api/public et /api/admin/events/:id/slots — voir le code pour arbitrer lequel utiliser dans une intégration (server/src/routes/public.events.routes.ts, server/src/routes/slots.routes.ts).

Détail — /api/admin, sous-familles

Sous-préfixe / groupeExemples de routesDescription
Dashboard / analyticsGET /admin/dashboard, GET /admin/stats, GET /admin/analytics/…KPI, réservations brutes, engagement, activité par événement
UtilisateursGET/POST /admin/users, GET/PUT/DELETE /admin/users/:id, POST /admin/users/bulk-delete, POST /admin/users/import, GET /admin/users/exportCRUD, import/export CSV, suppression groupée
ÉvénementsGET/POST /admin/events, PUT/DELETE /admin/events/:id, POST /admin/events/:id/duplicate, PUT /admin/events/:id/publish | /unpublish, PUT /admin/events/:id/opening-dateCRUD, publication, duplication, date d'ouverture
CréneauxGET/POST /admin/events/:eventId/slots, GET/PUT/DELETE /admin/slots/:idGestion des créneaux d'un événement
InvitationsPOST /admin/events/:id/invitations/send, GET /admin/events/:id/invitations, POST /admin/events/:id/invitations/:userId/resend, POST /admin/events/:id/invitations/resend-unansweredEnvoi, statut, relances
Paramètres (/admin/settings)GET/PUT/DELETE /settings/smtp, GET/PATCH /settings/email-brand, GET/PATCH /settings/email-templates/:templateKey, GET/PUT /settings/organization, POST/DELETE /settings/organization/logoSMTP, identité visuelle, modèles d'email, identité de l'organisation
ConfigurationGET/PUT /admin/config/polling-interval, GET/PUT /admin/config/magic-linkIntervalle de polling, TTL magic link
Enveloppes d'email (/admin/shell-parts)PUT/DELETE /shell-parts/:ownerKind/:ownerId/:partKindParties MJML d'en-tête/pied de page (cascade marque → modèle → événement)
Codes de secoursGET /admin/recovery-codes/status, POST /admin/recovery-codes/generate, PATCH /admin/recovery-codes/dismissGénération et statut des codes de secours admin
Notifications d'annulationGET /admin/cancellation-notifications, POST /admin/cancellation-notifications/resendNotifications en attente et relance groupée

Détail — identité de l'organisation

Nom, description et logo affichés sur la façade publique (OrganizationHome) et repris dans l'administration sont servis par les mêmes handlers, montés à trois endroits :

MéthodeCheminAuth
GET/api/admin/settings/organizationadmin
PUT/api/admin/settings/organizationadmin
POST/api/admin/settings/organization/logoadmin
DELETE/api/admin/settings/organization/logoadmin
GET/api/setup/organizationPublic — se ferme dès qu'un administrateur existe
PUT/api/setup/organizationPublic — se ferme dès qu'un administrateur existe
POST/api/setup/organization/logoPublic — se ferme dès qu'un administrateur existe
DELETE/api/setup/organization/logoPublic — se ferme dès qu'un administrateur existe
GET/api/public/organizationPublic

GET renvoie { "data": { "name", "logo", "description", "homepageFacade" } } — les 4 mêmes champs sur les trois variantes, y compris la façade publique. PUT accepte :

json
{
  "name": "Mon association",
  "description": "<p>Texte riche…</p>",
  "homepageFacade": true
}

name est la seule clé requise (chaîne vide acceptée — la façade publique retombe alors sur /login) ; description (HTML riche, assaini côté serveur, balises autorisées limitées à p/br/strong/em/a) et homepageFacade sont optionnels. La réponse renvoie l'objet complet mis à jour, dans la même forme que GET.

POST .../organization/logo attend un multipart/form-data avec un unique champ fichier logo (image, 5 Mo max, normalisée en WebP 512×512) et renvoie { "data": { "logo": "<url>" } }. DELETE .../organization/logo supprime le fichier et renvoie 204 sans corps.

Périmètre volontairement restreint de la façade publique

GET /api/public/organization ne requiert aucune authentification et ne renvoie que ces 4 champs — jamais un autre réglage de l'instance, même si l'administration en gère davantage.

Exemple de payload d'erreur de validation

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Format d'email invalide"
  }
}

La syntaxe du corps d'erreur varie selon l'endpoint et son ancienneté : certaines routes renvoient la forme étiquetée { "error": { "code", "message" } } ci-dessus, d'autres une forme plate à code frère { "error": "message", "code": "…" }. Ce qui ne varie pas : toute réponse 4xx porte un code, dans l'une ou l'autre forme. Deux exceptions, délibérées — la connexion de secours répond un code seul sans message ({ "code": "INVALID_CREDENTIALS" }, pour ne pas distinguer un compte inconnu d'un code erroné), et les replis techniques en 5xx n'ont pas de code (leur message est une phrase générique qui n'apprend rien de plus).

Lisez le code, pas le message : le message est destiné à l'utilisateur final, peut être reformulé sans préavis, et n'est pas un identifiant. La liste des codes vit dans shared/src/errorCodes.ts.

Endpoint de santé

GET /health (public, hors préfixe /api) renvoie un statut binaire sans authentification, utilisable par une sonde de supervision externe :

json
{
  "status": "ok",
  "timestamp": "2026-07-13T08:00:00.000Z",
  "services": {
    "smtp": "ok"
  }
}

status et services.smtp valent ok ou degraded (chaînes) ; le code HTTP reste 200 dans les deux cas.

Un statut détaillé réservé aux administrateurs (base de données incluse) existe à GET /api/admin/health. Voir Dépannage pour l'usage de ces deux endpoints en diagnostic.

Rate limiting

Plusieurs endpoints sensibles (/api/auth/login, /api/auth/emergency-login, envois de test SMTP/email, upload d'image) sont limités en nombre de requêtes par fenêtre de temps, indépendamment de toute configuration. Le détail des seuils est documenté dans Sécurité — Limitation de débit ; un dépassement renvoie un code HTTP 429 avec { "error": { "code": "RATE_LIMITED", … } }.

Pour aller plus loin

Publié sous licence FSL-1.1-MIT.