Utilisez l’adresse API de votre organisation et des identifiants aux portées limitées. Commencez par une requête de lecture, vérifiez la réponse et gardez les secrets hors du contrôle de version et des exemples de documentation.
Quire propose une API publique : REST sur HTTPS, décrite dans un document OpenAPI 3.1, des webhooks signés pour les événements et un serveur MCP pour les assistants d’IA. La référence de l’API répertorie tous les points de terminaison et événements.
Adresses
Chaque organisation dispose de sa propre adresse, qui héberge l’API :
https://acme.quirelms.com/api/v1/coursesLes identifiants déterminent l’organisation. Une clé utilisée à l’adresse d’une autre organisation est refusée.
Le document OpenAPI est publié à /api/v1/openapi.json sur l’adresse de chaque organisation ; les générateurs de clients consultent donc toujours la version appelée.
Authentification
Les clés API servent aux scripts et aux intégrations de serveur à serveur. Un administrateur en crée une dans /admin/integrations/api-keys, en choisit les portées et ne peut la voir qu’une fois. Envoyez-la comme jeton porteur :
curl -H "Authorization: Bearer qk_live_..." https://acme.quirelms.com/api/v1/users?limit=50Les clés commencent par qk_live_ ou qk_test_. Attribuez une clé distincte à chaque intégration.
OAuth 2.1 sert aux applications agissant au nom d’une personne connectée. Enregistrez un client dans /admin/integrations/oauth-clients, puis utilisez le flux de code d’autorisation avec PKCE (/oauth/authorize, /oauth/token), ou les identifiants du client pour un client machine. Le document de découverte est publié dans /.well-known/oauth-authorization-server. Une portée restreint les opérations du jeton ; elle ne lui permet jamais d’en faire plus que la personne.
Les portées sont resource:read, resource:write et resource:delete, par exemple courses:read ou enrolments:write. Quatre portées privilégiées sont accompagnées d’un avertissement sur l’écran de consentement : audit:read, roles:write, tenants:write et users:delete.

Requêtes
- Pagination : chaque liste utilise une pagination par curseur. Indiquez
limit, puis transmetteznext_cursordepageen tant quecursortant quehas_morevaut true (voir l’exemple ci-dessous). Il n’y a pas de décalage. - Modifications depuis une date :
updated_sincerenvoie les changements postérieurs à un instant. Associez-le àinclude_deleted=true, ou consultez/<resource>/deletions, pour connaître les suppressions. - Identifiants externes : la plupart des ressources acceptent votre propre
external_idet/<resource>/ext:{external_id}permet de lire ou de créer/mettre à jour une ressource à partir de celui-ci ; une synchronisation n’a donc pas besoin de conserver les identifiants Quire. - Idempotence : envoyez un en-tête
Idempotency-KeyavecPOST,PATCHetDELETE. Une nouvelle tentative avec la même clé renvoie la première réponse au lieu de répéter l’opération. Les points de terminaison groupés l’exigent. - Versions : la version majeure figure dans le chemin (
/v1). Elle contient des révisions datées pour chaque modification incompatible, sélectionnées avec l’en-têteQuire-Version, par exempleQuire-Version: 2026-09-20. Sans cet en-tête, vous obtenez la révision en vigueur lors de l’émission de vos identifiants.
Exemple de page d’une liste :
{"data": [...], "page": {"next_cursor": "eyJ2Ijox...", "has_more": true, "limit": 100}}Erreurs
Chaque erreur est un document de problème RFC 9457 :
{"type": "https://quire.com/errors/enrolment.seat_limit_reached",
"title": "Seat limit reached", "status": 409,
"code": "enrolment.seat_limit_reached", "category": "conflict",
"detail": "The course has no seats left, so this enrolment was not created. ...",
"request_id": "01JB7XQK4Z..."}Fondez votre logique sur code, qui est stable ; detail est rédigé pour les personnes, peut leur être affiché et peut changer. Si vous ne connaissez pas un code, utilisez sa category :
| Catégorie | Statut | Nouvelle tentative |
|---|---|---|
validation |
422, avec le détail des champs dans errors |
Non |
authentication |
401 | Non |
authorization |
403 | Non |
not_found |
404 | Non |
conflict |
409 | Parfois |
precondition |
412 | Non |
quota |
402 pour le forfait, 413 pour la taille | Non |
rate_limit |
429, avec Retry-After |
Oui |
upstream |
502 ou 504 | Oui |
internal |
500 | Oui |
Indiquez request_id lorsque vous contactez l’assistance.
Webhooks
Créez un abonnement dans /admin/webhooks ou via l’API à /webhook_subscriptions. Choisissez les événements par nom (enrolment.created), par domaine (enrolment.*) ou tous (*). Quire envoie d’abord un événement webhook.ping ; l’abonnement démarre quand votre point de terminaison y répond.
Les livraisons suivent la spécification Standard Webhooks :
POST /hooks/quire
webhook-id: 01JB7XQK4Z8FQ2M3N4P5R6S7T8
webhook-timestamp: 1790000000
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=Pour vérifier une livraison :
- À partir des octets exacts reçus, avant tout décodage JSON, construisez la chaîne
{webhook-id}.{webhook-timestamp}.{raw body}. - Calculez son HMAC-SHA256 avec le secret de votre abonnement, puis encodez-le en base64.
- Comparez en temps constant le résultat à chaque valeur
v1,dewebhook-signature. Il peut y en avoir deux pendant la rotation d’un secret ; une seule correspondance suffit. - Rejetez tout horodatage qui diffère de votre horloge de plus de cinq minutes.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(secret, id, timestamp, rawBody, header) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = createHmac('sha256', Buffer.from(secret.replace(/^whsec_/, ''), 'base64'))
.update(`${id}.${timestamp}.${rawBody}`).digest();
return header.split(' ').some((part) => {
const [version, value] = part.split(',');
const given = Buffer.from(value ?? '', 'base64');
return version === 'v1' && given.length === expected.length && timingSafeEqual(given, expected);
});
}Éliminez les doublons à l’aide de webhook-id : une livraison peut arriver plusieurs fois. Le corps contient des identifiants et un bref résumé ; récupérez la ressource pour connaître son état actuel. Les livraisons échouées font l’objet de nouvelles tentatives avec temporisation pendant un maximum de 72 heures ; vous pouvez également les rejouer depuis le journal de distribution.
MCP
Le serveur MCP de Quire se trouve à /mcp sur l’adresse de l’organisation, et utilise HTTP en flux. Un client MCP découvre le serveur OAuth dans /.well-known/oauth-protected-resource ; la personne se connecte et donne son consentement comme avec tout client OAuth. Les outils agissent selon les autorisations de cette personne, et les outils destructifs demandent confirmation. Les administrateurs choisissent les outils disponibles dans /admin/integrations/mcp.

Forfaits et API
Les clés API, clients OAuth, webhooks et le serveur MCP relèvent de l’option API du forfait, incluse dans chaque forfait standard. Dans un forfait qui ne l’inclut pas, la création d’une clé, d’un client ou d’un abonnement est refusée, les écritures REST et les connexions MCP sont refusées, mais les lectures REST restent possibles afin que les données puissent être exportées. Le refus est un document de problème avec le code commerce.plan_entitlement, dans la catégorie precondition.
Extensions
Les types d’activité, blocs, méthodes d’inscription, modes de connexion, types de question, rapports, thèmes et intégrations de Quire sont déclarés dans le même registre d’extensions que peut enrichir une installation auto-hébergée. Les extensions sont intégrées à la compilation : aucun chargeur de modules n’est disponible à l’exécution, et une organisation utilisant Quire hébergé ne peut pas en ajouter. Les administrateurs activent ou désactivent chaque extension pour leur organisation dans /admin/extensions (consultez le guide de l’administrateur).
Pour en écrire une, partez du bloc et du thème d’exemple dans packages/integration/extensions/src/sample.ts. Choisissez le point d’extension et lisez son contrat dans points.ts, puis déclarez l’extension avec un identifiant, une version, une licence, ses éléments fournis et requis, et l’autorisation ou non pour une organisation de la désactiver. Enregistrez-la à l’endroit où l’application Web et le worker sont assemblés afin qu’ils partagent la même configuration. À la construction et à chaque appel à register, le registre vérifie les règles propres à chaque point d’extension, refuse en nommant tous les problèmes un ensemble invalide et conserve le registre inchangé. Les tests de l’extension doivent vérifier que extensionContractProblems ne renvoie aucun problème et que la désactivation modifie les éléments concernés.