Saltar al contenido

Guía para desarrolladores

La API REST de Quire, OAuth, los webhooks, el servidor MCP y las extensiones.

Ver como Markdown

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/courses

La 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=50

Las 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 limit y después el next_cursor de page como cursor mientras has_more sea true (como en el ejemplo). No hay desplazamiento por offset.
  • Cambios desde una fecha: updated_since devuelve los cambios posteriores a una hora determinada. Combínalo con include_deleted=true o lee /<resource>/deletions para 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-Key con POST, PATCH y DELETE. 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 cabecera Quire-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:

  1. Construye la cadena {webhook-id}.{webhook-timestamp}.{raw body} con los bytes exactos recibidos, antes de analizar el JSON.
  2. Calcula HMAC-SHA256 sobre ella con el secreto de tu suscripción y conviértelo a base64.
  3. Compara en tiempo constante con cada valor v1, de webhook-signature. Durante la rotación de un secreto puede haber dos; basta con que coincida uno.
  4. 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.

Navegación

Escribe para buscar…

↑↓ navegar↵ seleccionarEsc cerrar