Aller au contenu

API publique

Une API pour brancher Cadanse au reste de vos outils — tableur, comptabilité, infolettre, automatisation. Tout ce qui sort se lit ; ce qui entre s'écrit avec une clé d'idempotence. Une clé par studio, un en-tête, du JSON.

Le contrat

Le document OpenAPI 3.0 est servi sans clé et engendré depuis le code qui répond : il ne peut pas décrire autre chose que ce que l'API rend réellement. C'est lui qu'il faut donner à un générateur de client.

Ouvrir openapi.json

Commencer

Fabriquez une clé au tableau de bord, dans Configuration. Son secret n'est montré qu'une fois — recopiez-le tout de suite, Cadanse n'en conserve qu'une empreinte et ne peut pas vous le redonner.

Vérifiez-la sur la route d'identité, qui n'exige aucune portée et rend votre studio, vos portées et votre quota restant :

curl -H "Authorization: Bearer cad_..." \
  https://cadanse.app/api/v1/moi

Puis lisez une collection. Chaque réponse porte « data » et « next » ; repassez « next » en « apres » jusqu'à ce qu'il soit nul.

curl -H "Authorization: Bearer cad_..." \
  "https://cadanse.app/api/v1/familles?limite=100"

120 requêtes par minute et 20000 par jour, par clé. Un dépassement rend 429 avec l'en-tête « Retry-After ». Le compteur est fermé par défaut : injoignable, il refuse plutôt que de laisser passer.

Pagination et filtre

Les trois paramètres acceptés par chaque collection.

ParamètreTypeDéfaut
apresCurseur exclusif : le dernier identifiant reçu à la page précédente, c'est-à-dire la valeur de « next ». Absent, la lecture commence au début.string—
limiteNombre de lignes par page. Une valeur supérieure à 200 est ramenée à 200 sans erreur ; une valeur non entière ou nulle est refusée.integer50
modifieDepuisNe rend que les lignes dont « updatedAt » est postérieur ou égal à cet instant ISO 8601. Une date invalide est REFUSÉE, jamais ignorée : un filtre qui disparaît en silence fait relire toute la collection à chaque sondage.string—

Collections

Chacune demande la portée « <ressource>:lecture » (par exemple « familles:lecture » ; cours, camps, événements et représentations n'exposent aucune coordonnée de famille). Les champs listés sont exhaustifs : ce qui n'y figure pas ne sort pas de Cadanse.

/api/v1/familles

Demande la portée « familles:lecture ».

/api/v1/familles
ChampTypeNul possible
idstringnon
lastNamestringnon
contactFirstNamestringoui
contactEmailstringnon
contactPhonestringoui
addressstringoui
parent2FirstNamestringoui
parent2LastNamestringoui
parent2Emailstringoui
parent2Phonestringoui
emailOptInbooleannon
smsOptInbooleannon
emailCommercialOptOutAtstringoui
smsOptOutAtstringoui
archivedAtstringoui
mergedIntoIdstringoui
createdAtstringnon
updatedAtstringnon

/api/v1/eleves

Demande la portée « eleves:lecture ».

/api/v1/eleves
ChampTypeNul possible
idstringnon
familyIdstringnon
firstNamestringnon
lastNamestringnon
birthDatestringoui
isAdultbooleannon
competitivebooleannon
photoConsentAtstringoui
photoConsentWithdrawnAtstringoui
createdAtstringnon
updatedAtstringnon

/api/v1/cours

Demande la portée « cours:lecture ».

/api/v1/cours
ChampTypeNul possible
idstringnon
sessionIdstringnon
namestringnon
descriptionstringoui
registrationEmailNotestringoui
styleIdstringnon
levelIdstringoui
stream"RECREATIONAL" | "COMPETITIVE" | "BOTH" | "ADULT_RECREATIONAL" | "ADULT_COMPETITIVE" | "ADULT_BOTH"non
minAgenumberoui
maxAgenumberoui
ageCutoffMonthnumberoui
ageCutoffDaynumberoui
capacitynumbernon
pricestringnon
taxExemptbooleannon
instructorIdstringoui
assistantIdsstring[]non
coInstructorIdsstring[]non
roomIdstringoui
trialsOpenbooleannon
approvalRequiredbooleannon
slotsobject[]non
createdAtstringnon
updatedAtstringnon

/api/v1/inscriptions

Demande la portée « inscriptions:lecture ».

/api/v1/inscriptions
ChampTypeNul possible
idstringnon
studentIdstringnon
classIdstringnon
status"ENROLLED" | "WAITLISTED" | "CANCELLED" | "CART"non
enrolledAtstringnon
paymentIdstringoui
ageOverridebooleannon
prereqOverridebooleannon
offeredAtstringoui
offerExpiresAtstringoui
updatedAtstringnon

/api/v1/presences

Demande la portée « presences:lecture ».

/api/v1/presences
ChampTypeNul possible
idstringnon
enrollmentIdstringnon
datestringnon
status"PRESENT" | "ABSENT" | "LATE" | "EXCUSED"non
createdAtstringnon
updatedAtstringnon

/api/v1/paiements

Demande la portée « paiements:lecture ».

/api/v1/paiements
ChampTypeNul possible
idstringnon
familyIdstringnon
invoiceNumberstringoui
descriptionstringoui
amountstringnon
status"PENDING" | "PAID" | "REFUNDED" | "FAILED"non
method"CASH" | "INTERAC" | "CARD" | "CHEQUE" | "OTHER" | "CHEQUE_VACANCES" | "COUPON_SPORT" | "PASS_SPORT" | "PASS_CULTURE" | "CREDIT" | "BANK"oui
dueDatestringoui
paidAtstringoui
processingSincestringoui
createdAtstringnon
updatedAtstringnon

/api/v1/evenements

Demande la portée « evenements:lecture » ET la capacité « EVENEMENTS » en plus de l'API publique.

/api/v1/evenements
ChampTypeNul possible
idstringnon
sessionIdstringnon
roomIdstringoui
namestringnon
descriptionstringoui
startDatestringnon
endDatestringnon
joursstring[]non
dailyStartTimestringoui
dailyEndTimestringoui
minAgenumberoui
maxAgenumberoui
capacitynumbernon
pricestringnon
typesBilletsobject[]non
priceIncludesTaxbooleannon
registrationOpenbooleannon
createdAtstringnon
updatedAtstringnon

/api/v1/camps

Demande la portée « camps:lecture » ET la capacité « CAMPS » en plus de l'API publique.

/api/v1/camps
ChampTypeNul possible
idstringnon
sessionIdstringnon
roomIdstringoui
namestringnon
descriptionstringoui
startDatestringnon
endDatestringnon
dailyStartTimestringoui
dailyEndTimestringoui
minAgenumberoui
maxAgenumberoui
capacitynumbernon
pricestringnon
taxExemptbooleannon
registrationOpenbooleannon
createdAtstringnon
updatedAtstringnon

/api/v1/representations

Demande la portée « representations:lecture » ET la capacité « BILLETTERIE » en plus de l'API publique.

/api/v1/representations
ChampTypeNul possible
idstringnon
showIdstringnon
datestringnon
venuestringoui
venueRoomIdstringoui
venueMapIdstringoui
ticketPricestringoui
ticketsOnSalebooleannon
ticketsOnSaleAtstringoui
earlyTicketsOnSaleAtstringoui
maxTicketsPerFamilynumberoui
updatedAtstringnon

Écrire

Chaque écriture demande la portée « <ressource>:ecriture » (par exemple « inscriptions:ecriture »), et l'enregistrement d'un paiement sa propre portée « paiements:ecriture », à cocher séparément. Chacune passe par la même règle que l'écran correspondant du tableau de bord : une inscription refusée à l'écran est refusée ici, et le code d'erreur nomme la règle qui a refusé. Les champs listés sont les seuls acceptés ; un champ inconnu, ou un champ que l'API ne lit pas, fait 400.

L'en-tête « Idempotency-Key » est obligatoire : une valeur à vous, unique par écriture. Réessayer avec la même clé rend la réponse d'origine pendant 24 heures, avec l'en-tête « Idempotent-Replayed: true » — c'est ce qui vous laisse réessayer sans risquer une deuxième inscription. La même clé sur un corps différent fait 409 ; une écriture refusée, elle, libère la clé, pour que vous puissiez corriger votre envoi et réessayer avec la même.

Une création, et la même relancée :

curl -X POST -H "Authorization: Bearer cad_..." \
  -H "Idempotency-Key: 8f1c-..." \
  -H "Content-Type: application/json" \
  -d '{"lastName":"…","contactEmail":"…"}' \
  https://cadanse.app/api/v1/familles

POST /api/v1/familles

Demande la portée « familles:ecriture » et l'en-tête « Idempotency-Key ». La famille est créée sans élève : un élève se crée par sa propre écriture. La réponse porte la ligne au même format qu'en lecture.

POST /api/v1/familles
ChampTypeRequis
lastNamestringoui
contactEmailstringoui
contactFirstNamestringnon
contactPhonestringnon
addressstringnon
parent2FirstNamestringnon
parent2LastNamestringnon
parent2Emailobjectnon
parent2Phonestringnon

POST /api/v1/eleves

Demande la portée « eleves:ecriture » et l'en-tête « Idempotency-Key ». L'élève est rattaché à une famille existante. Ni note médicale ni note interne : aucune des deux ne se lit par l'API, et ce qui ne se lit pas ne s'écrit pas.

POST /api/v1/eleves
ChampTypeRequis
familyIdstringoui
firstNamestringoui
lastNamestringoui
birthDatestringnon
isAdultbooleannon
competitivebooleannon

POST /api/v1/inscriptions

Demande la portée « inscriptions:ecriture » et l'en-tête « Idempotency-Key ». Passe par la même fonction de domaine que le tableau de bord : capacité, conflits d'horaire, limites par participant et inscriptibilité de la session peuvent la refuser, et l'erreur nomme alors la règle. Un cours complet met l'élève en LISTE D'ATTENTE — c'est un succès, et le champ « status » de la réponse le dit. La facture de la session est émise comme elle le serait au tableau de bord.

POST /api/v1/inscriptions
ChampTypeRequis
studentIdstringoui
classIdstringoui

POST /api/v1/presences

Demande la portée « presences:ecriture » et l'en-tête « Idempotency-Key ». Marque une séance d'une inscription, aux mêmes conditions que la feuille du tableau de bord : jamais d'avance, jamais un jour de fermeture, jamais une séance annulée ou déplacée. Un statut « EXCUSED » émet un crédit de reprise quand le studio en accorde ; une transition vers « ABSENT » prévient la famille et émet l'événement « absence.signalee ». Remarquer la même séance met la marque à jour sans rien réémettre.

POST /api/v1/presences
ChampTypeRequis
enrollmentIdstringoui
datestringoui
status"PRESENT" | "ABSENT" | "LATE" | "EXCUSED"oui

POST /api/v1/paiements

Demande la portée « paiements:ecriture » et l'en-tête « Idempotency-Key ». Le montant envoyé EST le total, taxes comprises : c'est la saisie de comptoir, pas un encaissement par processeur. La facture reçoit son numéro et son empreinte fiscale, et la famille en est avertie au portail — relance courriel comprise si elle reste due.

POST /api/v1/paiements
ChampTypeRequis
familyIdstringoui
amountnumberoui
method"CASH" | "INTERAC" | "CARD" | "CHEQUE" | "OTHER" | "CHEQUE_VACANCES" | "COUPON_SPORT" | "PASS_SPORT" | "PASS_CULTURE"non
descriptionstringnon
status"PENDING" | "PAID"non
dueDatestringnon

Erreurs

Toute erreur rend la même forme : « error.code » et « error.message ». Le code est contractuel, le message peut être reformulé.

CodeStatutQuand
MISSING_KEY401Aucun en-tête « Authorization: Bearer … » n'accompagne la requête.
INVALID_KEY401La clé est inconnue ou révoquée. Les deux cas rendent le même message.
STUDIO_SUSPENDED403Le compte du studio est suspendu.
STUDIO_ONBOARDING403La configuration initiale du studio n'est pas terminée.
PLAN_REQUIRED403Le palier du studio n'inclut pas l'API publique, ou pas la capacité que demande la collection visée.
QUOTA_UNAVAILABLE503Le compteur de quota est momentanément injoignable ; la requête est refusée plutôt que laissée passer. Réessayez après le délai de « Retry-After ».
RATE_LIMITED429Quota par minute ou quotidien dépassé. L'en-tête « Retry-After » donne le délai en secondes.
SCOPE_REQUIRED403La clé est valide mais ne porte pas la portée qu'exige la route.
INVALID_PARAMETER400Un paramètre de requête est mal formé — « limite » non entière ou nulle, « modifieDepuis » qui n'est pas une date ISO 8601.
IDEMPOTENCY_KEY_REQUIRED400L'en-tête « Idempotency-Key » manque, est vide, ou dépasse 255 caractères. Il est obligatoire sur toute écriture : un connecteur qui réessaie est le cas normal, pas l'exception.
IDEMPOTENCY_KEY_REUSED409Cette clé a déjà servi, pour une requête DIFFÉRENTE. Rendre la réponse d'origine ferait croire à l'appelant qu'il vient de créer ce que décrit une réponse qui parle d'autre chose. Une clé vaut pour une écriture et une seule.
IDEMPOTENCY_IN_PROGRESS409Une écriture portant cette clé court encore. Réessayez après le délai de « Retry-After » : la réponse d'origine sera alors rendue.
INVALID_BODY400Le corps n'est pas du JSON valide, ou un champ ne satisfait pas le schéma de l'écriture. Le message nomme le champ fautif. Le refus ne consomme PAS la clé d'idempotence : corrigez et réessayez avec la même.
PAYLOAD_TOO_LARGE413Le corps de la requête dépasse la taille permise ; il est refusé avant tout décodage. Réduisez-le (par lots plus petits) avant de réessayer.
FAMILY_NOT_FOUND404Le « familyId » visé n'existe pas dans ce studio.
STUDENT_NOT_FOUND404Le « studentId » visé n'existe pas dans ce studio.
CLASS_NOT_FOUND404Le « classId » visé n'existe pas dans ce studio.
ENROLLMENT_NOT_FOUND404L'« enrollmentId » visé n'existe pas dans ce studio.
ALREADY_ENROLLED409Cet élève est déjà inscrit à ce cours. Réinscrire ne crée pas de seconde ligne — le refus est ce qui empêche une double facturation.
SESSION_NOT_ENROLLABLE409La session du cours n'accepte pas d'inscription : inactive, terminée, ou dont la période est passée.
SCHEDULE_CONFLICT409L'élève a déjà un cours qui chevauche celui-ci. Seul le studio peut assumer un conflit d'horaire, depuis son tableau de bord : l'API ne porte pas cette dérogation.
PARTICIPANT_LIMIT409L'inscription dépasse une limite fixée par le studio — nombre de cours par élève, ou un seul cours par discipline.
ATTENDANCE_DATE_IN_FUTURE409La date de la présence est encore à venir. Une présence ne se prend pas d'avance : elle nourrit le décompte de séances tenues et l'avis d'absence à la famille.
STUDIO_CLOSED409Le studio est fermé ce jour-là : aucune séance n'y a lieu.
SESSION_NOT_HELD409La séance de ce cours a été annulée ou déplacée : elle n'a pas eu lieu à la date demandée. Le message dit lequel des deux.
METHOD_NOT_OFFERED422Le moyen de paiement n'existe pas dans le pays du studio : `INTERAC` est propre au Canada, et `BANK` (compte bancaire débité) au Canada (débit préautorisé), aux États-Unis (ACH) et en France (SEPA) ; les Chèques-Vacances, coupons sport, Pass'Sport et Pass Culture sont propres à la France.
INTERNAL_ERROR500Erreur interne. Le détail est consigné côté Cadanse, jamais rendu à l'appelant.

Webhooks sortants

L'API se lit et s'écrit à votre initiative ; les webhooks, eux, viennent à vous. Une destination se branche au tableau de bord, dans Configuration : une adresse HTTPS, les événements souscrits, et un secret de signature montré une fois. Cadanse y envoie un POST « application/json » à chaque événement.

Vérifier la signature

Chaque envoi porte l'en-tête « Cadanse-Signature », de la forme « t=<millisecondes>,v1=<hex> » : « v1 » est le HMAC-SHA256 du texte « <t>.<corps brut> », avec votre secret comme clé. Vérifiez le corps BRUT, avant toute désérialisation. Vérifiez la signature d'ABORD et l'horodatage ensuite : refuser sur l'âge avant d'avoir prouvé l'authenticité apprend à qui vous écrit quelle heure vous acceptez. Au-delà de 5 minutes, refusez — c'est ce qui empêche de rejouer chez vous un corps capté une fois. L'en-tête « Cadanse-Event » nomme l'événement, pour router sans lire le corps.

Cadanse-Signature: t=1776171600000,v1=<hex>
Cadanse-Event: inscription.creee

L'enveloppe

Toujours la même forme : « id », « type », « createdAt », « studioId » et « data ». « id » est celui de la LIVRAISON, pas de l'objet : les reprises d'un même envoi le répètent — c'est ce qui vous permet de dédoublonner — et un rejeu demandé à la main en porte un nouveau. « createdAt » est l'instant de l'événement ; aucune charge ne porte un second horodatage. Les champs de « data » sont des identifiants : les fiches complètes se lisent aux collections ci-dessus.

Reprises

Une réponse 2xx conclut la livraison. Sinon, 3 reprises, à 1 h, 6 h, 24 h, puis plus rien : un événement livré trois jours trop tard n'informe plus personne. La fenêtre totale est de 48 h. Répondez vite — un appel est abandonné au-delà de 2 secondes — et faites votre travail après avoir répondu. Le tableau de bord montre chaque livraison, son code de réponse et le message d'erreur de votre serveur ; une livraison épuisée s'y rejoue à la main.

Les événements

Souscrire un événement qui n'envoie encore rien est permis, et la colonne de droite le dit : c'est l'état réel, lu dans le code qui émet.

Les événements
ÉvénementChamps de « data »État
inscription.creeeinscriptionId, eleveId, coursId, familleId, statut, paiementIdenvoyé
paiement.recupaiementId, familleId, montant, moyen, numeroFacture, payeLeenvoyé
absence.signaleeinscriptionId, eleveId, coursId, date, statutenvoyé
famille.creeefamilleId, nom, courrielenvoyé
billet.venducommandeId, representationId, familleId, quantite, surLesGenoux, paiementId, venduLeenvoyé

Connecteurs

Zapier, Make et les assistants qui parlent MCP se branchent sur tout ce qui précède, et sur rien d'autre : la même clé, les mêmes portées, le même quota, le même journal. Un connecteur n'ouvre pas une porte de plus — il emprunte une porte que vous avez déjà ouverte, et la clé se révoque au tableau de bord.

Zapier et Make

Rien à installer : créez une connexion « API par clé » avec l'en-tête « Authorization » et, pour valeur, « Bearer » suivi de votre clé. Testez-la sur https://cadanse.app/api/v1/moi — cette route répond quelles que soient les portées de la clé et vous rend le nom du studio et la liste de ce qu'elle permet.

Vos déclencheurs sont les webhooks ci-dessus. Zapier comme Make vous donnent une URL d'écoute : collez-la au tableau de bord et cochez les événements voulus. Ni l'un ni l'autre ne vérifie la signature pour vous — faites-la vérifier par une étape de code avant d'agir sur une charge, avec la recette donnée plus haut.

Vos actions sont les écritures ci-dessus, appelées par une étape « requête HTTP ». Renseignez toujours « Idempotency-Key » : un scénario que la plateforme rejoue après une panne recréerait sinon la même fiche deux fois.

Serveur MCP

Un assistant qui parle MCP se branche sur https://cadanse.app/api/v1/mcp — transport HTTP, requêtes POST, même en-tête « Authorization ». C'est votre studio qui décide : brancher un assistant fait sortir des données vers un outil que vous choisissez, sous une clé que vous créez et que vous pouvez révoquer.

{
  "mcpServers": {
    "cadanse": {
      "type": "http",
      "url": "https://cadanse.app/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer cad_..."
      }
    }
  }
}

Le serveur expose 9 outils de lecture et 5 outils d'écriture — exactement les collections et les écritures documentées ci-dessus, aucune autre. Le nom d'un outil est celui de son opération, en minuscules soulignées :

lister_familles, lister_eleves, lister_cours, lister_inscriptions, lister_presences, lister_paiements, lister_evenements, lister_camps, lister_representations, creer_famille, creer_eleve, creer_inscription, marquer_presence, creer_paiement

Le catalogue dépend de la clé : « tools/list » ne rend que les outils couverts par ses portées. Un outil hors portée n'existe pas pour cette clé — l'appeler répond « outil inconnu », ce qui évite d'apprendre à un curieux quelles portées existent.

Les lots JSON-RPC sont refusés : un appel est une requête, et une requête est une unité de quota. Les lectures faites par MCP sont consignées comme les autres — une fenêtre par clé, par collection et par jour — au même journal que le studio peut relire.

Les outils d'écriture agissent immédiatement, et ce qu'ils écrivent est visible du personnel et des familles. Chacun accepte un argument « cleIdempotence » facultatif : sans lui, deux appels identiques font deux écritures — c'est voulu, deux paiements comptant du même montant le même samedi sont deux paiements. Le journal d'audit garde la trace de chaque écriture, qu'elle vienne d'un assistant ou d'un appel HTTP.

Pourquoi cette page ne peut pas mentir

Elle est rendue depuis le même document que openapi.json, lui-même engendré depuis les sérialiseurs qui écrivent les réponses. Un champ retiré du code disparaît d'ici sans que personne y pense ; un champ documenté qui ne serait pas servi est impossible à écrire.