Usa la dirección de API de tu organización y una credencial con permisos limitados. Empieza con una solicitud de lectura, comprueba la respuesta y mantén los secretos fuera del control de versiones y de los ejemplos de documentación.
Quire ofrece una API pública: REST sobre HTTPS, descrita mediante un documento OpenAPI 3.1, con webhooks firmados para eventos y un servidor MCP para asistentes de IA. La referencia de la API enumera todos los endpoints y eventos.
Direcciones
Cada organización tiene su propia dirección, y la API se encuentra bajo ella:
https://acme.quirelms.com/api/v1/coursesLa credencial determina la organización. Se rechaza una clave de una organización si se usa en la dirección de otra.
El documento OpenAPI se sirve en /api/v1/openapi.json en la dirección de cualquier organización, por lo que los generadores de clientes siempre ven la versión a la que estás llamando.
Autenticación
Las claves de API son para scripts e integraciones entre servidores. Un administrador crea una en /admin/integrations/api-keys, elige sus ámbitos y solo puede verla una vez. Envíala como token bearer:
curl -H "Authorization: Bearer qk_live_..." https://acme.quirelms.com/api/v1/users?limit=50Las claves empiezan por qk_live_ o qk_test_. Asigna una clave distinta a cada integración.
OAuth 2.1 es para aplicaciones que actúan en nombre de una persona que ha iniciado sesión. Registra un cliente en /admin/integrations/oauth-clients y luego usa el flujo de código de autorización con PKCE (/oauth/authorize, /oauth/token), o las credenciales de cliente para un cliente máquina. La detección está disponible en /.well-known/oauth-authorization-server. Un ámbito limita lo que puede hacer un token; nunca le permite hacer más de lo que podría hacer la persona.
Los ámbitos son resource:read, resource:write y resource:delete; por ejemplo, courses:read o enrolments:write. Hay cuatro ámbitos privilegiados que se muestran con una advertencia en la pantalla de consentimiento: audit:read, roles:write, tenants:write y users:delete.
Solicitudes
- Paginación: todas las listas se paginan mediante cursores. Pasa
limity después elnext_cursordepagecomocursormientrashas_moresea true (como en el ejemplo). No hay desplazamiento por offset. - Cambios desde una fecha:
updated_sincedevuelve los cambios posteriores a una hora determinada. Combínalo coninclude_deleted=trueo lee/<resource>/deletionspara saber qué se eliminó. - Identificadores externos: la mayoría de los recursos aceptan tu propio
external_id, y/<resource>/ext:{external_id}permite leer o actualizar mediante él, para que una sincronización no tenga que guardar los identificadores de Quire. - Idempotencia: envía una cabecera
Idempotency-KeyconPOST,PATCHyDELETE. Un reintento con la misma clave devuelve la primera respuesta, sin repetir la operación. Los endpoints masivos la requieren. - Versiones: la versión principal aparece en la ruta (
/v1). Dentro de ella, cada cambio incompatible corresponde a una revisión fechada, que se elige con la cabeceraQuire-Version, por ejemplo,Quire-Version: 2026-09-20. Si no envías esa cabecera, recibes la revisión vigente cuando se emitió tu credencial.
Una página de una lista:
{"data": [...], "page": {"next_cursor": "eyJ2Ijox...", "has_more": true, "limit": 100}}Errores
Todos los errores son documentos de problema 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..."}Actúa en función de code, que es estable; detail está redactado para las personas, es seguro mostrárselo y puede cambiar. Si no reconoces un código, clasifica el error según category:
| Categoría | Estado | Reintento |
|---|---|---|
validation |
422, con detalles de campos en errors |
No |
authentication |
401 | No |
authorization |
403 | No |
not_found |
404 | No |
conflict |
409 | A veces |
precondition |
412 | No |
quota |
402 para el plan, 413 para el tamaño | No |
rate_limit |
429, con Retry-After |
Sí |
upstream |
502 o 504 | Sí |
internal |
500 | Sí |
Incluye request_id cuando contactes con soporte.
Webhooks
Suscríbete en /admin/webhooks o mediante la API en /webhook_subscriptions. Elige los eventos por nombre (enrolment.created), por área (enrolment.*) o todos (*). Quire envía primero un webhook.ping; la suscripción empieza cuando tu endpoint responde.
Las entregas siguen la especificación Standard Webhooks:
POST /hooks/quire
webhook-id: 01JB7XQK4Z8FQ2M3N4P5R6S7T8
webhook-timestamp: 1790000000
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=Para verificar una entrega:
- Construye la cadena
{webhook-id}.{webhook-timestamp}.{raw body}con los bytes exactos recibidos, antes de analizar el JSON. - Calcula HMAC-SHA256 sobre ella con el secreto de tu suscripción y conviértelo a base64.
- Compara en tiempo constante con cada valor
v1,dewebhook-signature. Durante la rotación de un secreto puede haber dos; basta con que coincida uno. - Rechaza cualquier marca de tiempo que se desvíe más de cinco minutos de tu reloj.
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);
});
}Evita duplicados usando webhook-id: una entrega puede recibirse más de una vez. El cuerpo incluye identificadores y un breve resumen; consulta el recurso para obtener su estado actual. Los intentos fallidos se reintentan con pausas crecientes durante un máximo de 72 horas y se pueden volver a reproducir desde el registro de entregas.
MCP
El servidor MCP de Quire está en /mcp en la dirección de la organización, mediante HTTP transmisible. Un cliente MCP descubre el servidor OAuth en /.well-known/oauth-protected-resource, y la persona inicia sesión y da su consentimiento como con cualquier cliente OAuth. Las herramientas actúan con los permisos de esa persona, y las herramientas destructivas solicitan confirmación. Los administradores eligen las herramientas disponibles en /admin/integrations/mcp.
Planes y la API
Las claves de API, los clientes OAuth, los webhooks y el servidor MCP forman parte de la prestación de API del plan, que se incluye en todos los planes estándar. En un plan que no la incluya, se rechaza la creación de claves, clientes o suscripciones, así como las escrituras REST y las conexiones MCP; las lecturas REST siguen funcionando para que los datos se puedan exportar. El rechazo es un documento de problema con el código commerce.plan_entitlement, en la categoría precondition.
Extensiones
Los tipos de actividad, bloques, métodos de inscripción, métodos de inicio de sesión, tipos de preguntas, informes, temas e integraciones propios de Quire se declaran mediante el mismo registro de extensiones al que puede añadir extensiones una instalación autoalojada. Las extensiones se compilan con la aplicación: no hay un cargador de complementos en tiempo de ejecución, y una organización alojada no puede añadir uno. Los administradores activan o desactivan cada extensión para su organización en /admin/extensions (consulta la guía para administradores).
Para crear una, empieza con el bloque y el tema de ejemplo en packages/integration/extensions/src/sample.ts. Elige el punto de extensión y consulta su contrato en points.ts; después declara la extensión con un id, una versión, una licencia, lo que ofrece y necesita, y si una organización puede desactivarla. Regístrala donde se ensamblan la aplicación web y el worker para que ambos estén de acuerdo. El registro comprueba las reglas propias de cada punto al crearse y cada vez que llamas a register, rechaza el conjunto si no es válido e indica todos los problemas, y deja el registro sin cambios en ese caso. Las pruebas de la extensión deben comprobar que extensionContractProblems está vacío para ella y que desactivarla cambia aquello que afecta.