Utilisez l’adresse d’API de votre organisation et un justificatif dont la portée est limitée. Commencez par une requête de lecture, vérifiez la réponse et gardez les secrets à l’extérieur du contrôle de version et des exemples de documentation.
Quire offre une API publique : REST sur HTTPS, décrite par un document OpenAPI 3.1, avec 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 a sa propre adresse, sous laquelle se trouve l’API :
https://acme.quirelms.com/api/v1/coursesLe justificatif détermine l’organisation. Une clé d’une organisation utilisée à l’adresse d’une autre est refusée.
Le document OpenAPI est accessible à /api/v1/openapi.json sur l’adresse de chaque organisation; les générateurs de clients voient donc toujours la version que vous appelez.
Authentification
Les clés d’API servent aux scripts et aux intégrations serveur à serveur. Un administrateur en crée une à /admin/integrations/api-keys, en choisit les portées et ne la voit qu’une seule 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 qui agissent au nom d’une personne connectée. Enregistrez un client à /admin/integrations/oauth-clients, puis utilisez le flux de code d’autorisation avec PKCE (/oauth/authorize, /oauth/token) ou les justificatifs client pour un client machine. La découverte se fait à /.well-known/oauth-authorization-server. Une portée limite ce qu’un jeton peut faire; 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 sont privilégiées et accompagnées d’un avertissement sur l’écran de consentement : audit:read, roles:write, tenants:write et users:delete.

Requêtes
- Pagination : chaque liste est paginée par curseur. Transmettez
limit, puis la valeurnext_cursordepagecommecursortant quehas_morevaut true (voir l’exemple ci-dessous). Il n’y a pas de décalage. - Modifications depuis une date :
updated_sincerenvoie les modifications postérieures à une heure donnée. Combinez-le avecinclude_deleted=true, ou lisez/<resource>/deletions, pour connaître les éléments supprimés. - Identifiants externes : la plupart des ressources acceptent votre propre
external_id, et/<resource>/ext:{external_id}permet de les lire ou de les créer ou mettre à jour avec cet identifiant; 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 sans répéter l’opération. Les points de terminaison groupés l’exigent. - Versions : la version majeure figure dans le chemin (
/v1). À l’intérieur de celle-ci, chaque changement incompatible est une révision datée, sélectionnée avec l’en-têteQuire-Version, par exempleQuire-Version: 2026-09-20. Sans cet en-tête, vous obtenez la révision en vigueur à la création de votre justificatif.
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 traitement sur code, qui est stable; detail est destiné aux personnes, peut leur être affiché et peut changer. Si vous ne reconnaissez pas un code, classez l’erreur selon category :
| Catégorie | État | Nouvelle tentative |
|---|---|---|
validation |
422, avec les détails des champs dans errors |
No |
authentication |
401 | No |
authorization |
403 | No |
not_found |
404 | No |
conflict |
409 | Sometimes |
precondition |
412 | No |
quota |
402 pour le forfait, 413 pour la taille | No |
rate_limit |
429, avec Retry-After |
Yes |
upstream |
502 ou 504 | Yes |
internal |
500 | Yes |
Indiquez request_id lorsque vous communiquez avec le soutien.
Webhooks
Abonnez-vous à /admin/webhooks ou au moyen de l’API, à /webhook_subscriptions. Choisissez les événements par nom (enrolment.created), par groupe (enrolment.*) ou tous (*). Quire envoie d’abord un webhook.ping; l’abonnement commence lorsque 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 :
- Construisez la chaîne
{webhook-id}.{webhook-timestamp}.{raw body}à partir des octets exacts reçus, avant toute analyse JSON. - Calculez HMAC-SHA256 sur cette chaîne avec le secret de votre abonnement, puis encodez le résultat en base64.
- Comparez en temps constant chaque valeur
v1,dewebhook-signature. Il peut y en avoir deux pendant la rotation d’un secret; une seule correspondance suffit. - Rejetez un horodatage qui s’écarte de plus de cinq minutes de votre horloge.
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 sont réessayées avec un délai progressif jusqu’à 72 heures et peuvent être rejouées à partir du journal de livraison.
MCP
Le serveur MCP de Quire se trouve à /mcp, sur l’adresse de l’organisation, et utilise HTTP en continu. Un client MCP découvre le serveur OAuth à /.well-known/oauth-protected-resource; la personne ouvre une session et donne son consentement comme pour tout client OAuth. Les outils agissent avec les autorisations de cette personne, et les outils destructifs demandent une confirmation. Les administrateurs choisissent les outils accessibles à /admin/integrations/mcp.

Forfaits et API
Les clés d’API, clients OAuth, webhooks et serveur MCP relèvent du droit d’accès à l’API du forfait, qui est compris dans tous les forfaits standards. Sans ce droit, 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 continuent afin que les données restent exportables. Le refus est un document de problème dont le code est commerce.plan_entitlement, dans la catégorie precondition.
Extensions
Les types d’activité, blocs, méthodes d’inscription, méthodes de connexion, types de questions, rapports, thèmes et intégrations propres à Quire sont déclarés par le même registre d’extensions auquel une installation autohébergée peut ajouter des entrées. Les extensions sont intégrées à la compilation : il n’y a pas de chargeur de greffons à l’exécution, et une organisation hébergée ne peut pas en ajouter. Les administrateurs activent ou désactivent chaque extension pour leur organisation à /admin/extensions (consultez le guide de l’administrateur).
Pour en créer 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, les éléments qu’elle fournit et exige, et l’autorisation ou non pour une organisation de la désactiver. Inscrivez-la à l’endroit où l’application Web et le processus de travail sont assemblés, afin qu’ils soient tous deux d’accord. Le registre vérifie les règles propres à chaque point lors de sa création et à chaque appel de register, refuse tout ensemble invalide en nommant chaque problème et reste inchangé en cas de refus. Les tests de l’extension doivent vérifier que extensionContractProblems est vide pour celle-ci et que sa désactivation modifie les éléments concernés.