조직의 API 주소와 스코프가 지정된 자격 증명을 사용하세요. 읽기 요청부터 시작해 응답을 확인하고, 시크릿은 소스 관리와 문서 예제 밖에 두세요.
Quire에는 공개 API가 하나 있습니다. HTTPS 위의 REST이며 OpenAPI 3.1 문서로 설명되고, 이벤트에는 서명된 웹훅, AI 어시스턴트에는 MCP 서버가 있습니다. API 레퍼런스에 모든 엔드포인트와 이벤트가 나열됩니다.
주소
각 조직은 고유한 주소를 가지며 API는 그 아래에 있습니다.
https://acme.quirelms.com/api/v1/courses어느 조직의 주소에서 호출하느냐에 따라 자격 증명이 조직을 결정합니다. 한 조직의 키를 다른 조직의 주소에서 쓰면 거부됩니다.
OpenAPI 문서는 모든 조직 주소의 /api/v1/openapi.json에서 제공되므로
클라이언트 생성기는 항상 호출하는 버전을 보게 됩니다.
인증
API 키는 스크립트와 서버 간 연동용입니다. 관리자가
/admin/integrations/api-keys에서 하나를 만들고, 스코프를 고르며, 한 번만
확인할 수 있습니다. bearer 토큰으로 보내세요.
curl -H "Authorization: Bearer qk_live_..." https://acme.quirelms.com/api/v1/users?limit=50키는 qk_live_ 또는 qk_test_로 시작합니다. 연동마다 자체 키를 주세요.
OAuth 2.1은 로그인한 사람처럼 행동하는 애플리케이션용입니다.
/admin/integrations/oauth-clients에 클라이언트를 등록한 뒤, PKCE가 있는
인증 코드 플로우(/oauth/authorize, /oauth/token)를 쓰거나, 기계용
클라이언트에는 클라이언트 자격 증명을 쓰세요. 디스커버리는
/.well-known/oauth-authorization-server에 있습니다. 스코프는 토큰이 할 수
있는 것을 좁힐 뿐, 사람보다 더 할 수 있게 만들지 않습니다.
스코프는 resource:read, resource:write, resource:delete 형태이며, 예를
들어 courses:read나 enrolments:write입니다. 네 개는 특권이며 동의 화면에
경고와 함께 표시됩니다. audit:read, roles:write, tenants:write,
users:delete가 그렇습니다.
요청
- 페이지네이션: 모든 목록은 커서 기반입니다.
limit을 넘기고,next_cursor를page에서 가져와cursor로 넘기면서has_more가 true인 동안 반복하세요(아래 예시). 오프셋은 없습니다. - 변경 시점:
updated_since는 특정 시점 이후 바뀐 것을 반환합니다.include_deleted=true와 짝을 짓거나/<resource>/deletions를 읽어 무엇이 삭제되었는지 확인하세요. - 외부 식별자: 대부분의 리소스는 자체
external_id를 받고,/<resource>/ext:{external_id}는 그것으로 읽거나 upsert합니다. 동기화가 Quire의 식별자를 저장할 필요가 없습니다. - 멱등성:
Idempotency-Key헤더를POST,PATCH,DELETE에 보내세요. 같은 키로 재시도하면 작업을 두 번 하는 대신 첫 응답을 돌려받습니다. 벌크 엔드포인트는 이를 요구합니다. - 버전: 메이저 버전은 경로에 있습니다(
/v1). 그 안에서 각 파괴적 변경은 날짜가 붙은 개정이며,Quire-Version헤더로 고릅니다. 예:Quire-Version: 2026-09-20. 헤더가 없으면 자격 증명이 발급된 시점의 개정을 받습니다.
목록의 한 페이지:
{"data": [...], "page": {"next_cursor": "eyJ2Ijox...", "has_more": true, "limit": 100}}오류
모든 오류는 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..."}분기는 안정적인 code로 하세요. detail은 사람을 위해 쓰여 그들에게
보여줘도 안전하지만 바뀔 수 있습니다. 코드를 모르겠다면 category로
묶으세요.
| 카테고리 | 상태 | 재시도 |
|---|---|---|
validation |
422, errors에 필드 상세 |
아니요 |
authentication |
401 | 아니요 |
authorization |
403 | 아니요 |
not_found |
404 | 아니요 |
conflict |
409 | 가끔 |
precondition |
412 | 아니요 |
quota |
플랜은 402, 크기는 413 | 아니요 |
rate_limit |
429, Retry-After 동반 |
예 |
upstream |
502 또는 504 | 예 |
internal |
500 | 예 |
지원팀에 문의할 때는 request_id를 인용하세요.
웹훅
/admin/webhooks에서, 또는 API로 /webhook_subscriptions에서 구독하세요.
이벤트는 이름(enrolment.created), 영역(enrolment.*), 또는 전체(*)로
고를 수 있습니다. Quire는 먼저 webhook.ping을 보내고, 엔드포인트가
응답하면 구독이 시작됩니다.
전달은 Standard Webhooks 사양을 따릅니다.
POST /hooks/quire
webhook-id: 01JB7XQK4Z8FQ2M3N4P5R6S7T8
webhook-timestamp: 1790000000
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=전달을 검증하려면:
- JSON 파싱 전에, 받은 그대로의 바이트로 문자열
{webhook-id}.{webhook-timestamp}.{raw body}를 만드세요. - 구독 비밀로 HMAC-SHA256을 계산하고 base64로 인코딩하세요.
- 각
v1,값을webhook-signature와 상수 시간에 비교하세요. 비밀 로테이션 중에는 두 개가 있을 수 있으며, 어느 쪽이든 일치하면 유효합니다. - 시계와 5분 이상 차이나는 타임스탬프는 거부하세요.
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);
});
}webhook-id로 중복을 제거하세요. 전달은 여러 번 올 수 있습니다. 본문에는
식별자와 짧은 요약이 담겨 있으니 현재 상태는 리소스를 가져오세요. 실패한
전달은 최대 72시간 동안 백오프를 두고 재시도되며, 전달 로그에서 재생할 수
있습니다.
MCP
Quire의 MCP 서버는 조직 주소의 /mcp에 있으며 스트리머블 HTTP를 사용합니다.
MCP 클라이언트는 /.well-known/oauth-protected-resource에서 OAuth 서버를
발견하고, 사람은 다른 OAuth 클라이언트와 마찬가지로 로그인하고 동의합니다.
도구는 그 사람의 권한으로 행동하며, 파괴적인 도구는 확인을 요청합니다.
관리자는 /admin/integrations/mcp에서 어떤 도구를 쓸지 고릅니다.
플랜과 API
API 키, OAuth 클라이언트, 웹훅, MCP 서버는 플랜의 API 권한에 속하며 모든
표준 플랜에 포함됩니다. 없는 플랜에서는 키·클라이언트·구독 만들기가
거부되고, REST 쓰기와 MCP 연결이 거부되며, 데이터를 내보낼 수 있도록 REST
읽기는 계속 동작합니다. 거부 응답은 commerce.plan_entitlement 코드를 가진 precondition
카테고리의 문제 문서입니다.
확장 기능
Quire 자체의 활동 유형, 블록, 등록 방식, 로그인 방식, 문항 유형, 리포트,
테마, 연동은 셀프호스트 설치가 추가할 수 있는 같은 확장 레지스트리를 통해
선언됩니다. 확장 기능은 컴파일될 때 포함됩니다. 런타임 플러그인 로더가
없고, 호스팅 조직은 하나를 추가할 수 없습니다. 관리자는
/admin/extensions에서 조직별로 각 확장 기능을 켜거나 끕니다
(관리자 가이드 참조).
하나를 쓰려면 packages/integration/extensions/src/sample.ts의 샘플 블록과
테마에서 시작하세요. 확장 지점을 고르고 points.ts에서 그 계약을 읽은 뒤,
id, 버전, 라이선스, 제공하는 것과 요구하는 것, 조직이 끌 수 있는지를 담아
확장 기능을 선언하세요. 웹 애플리케이션과 워커가 조립되는 곳에 등록해
둘 다 동일하게 보장하세요. 레지스트리는 빌드될 때와 register를 호출할 때
각 지점의 규칙을 검사하고, 문제가 모두 명시된 잘못된 집합은 거부하며, 그럴
때는 레지스트리를 바꾸지 않습니다. 확장 기능 자체의 테스트는
extensionContractProblems가 비어 있는지, 그리고 끄면 영향받는 것이
바뀌는지 단정해야 합니다.