---
title: "开发者指南"
description: "Quire REST API、OAuth、webhook、MCP 服务器和扩展。"
image: "https://docs.quirelms.com/og.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.quirelms.com/zh-Hans/llms.txt
> Use this file to discover all available pages before exploring further.

# 开发者指南

<span id="developer-guide"></span>

请使用组织的 API 地址和范围受限的凭据。先发送读取请求并检查响应；不要将机密放进源代码管理或文档示例。

Quire 提供一个公共 API：通过 HTTPS 使用 REST，并由 OpenAPI 3.1 文档描述；事件通过签名 webhook 发送，AI 助手可以使用 MCP 服务器。[API 参考](https://docs.quirelms.com/api/)列出了所有端点和事件。

## 地址 <!--quire:addresses-->

每个组织都有自己的地址，API 位于该地址下：

```
https://acme.quirelms.com/api/v1/courses
```

组织由凭据决定。在另一个组织的地址使用某组织的密钥会被拒绝。

任何组织地址下的 `/api/v1/openapi.json` 都会提供 OpenAPI 文档，因此客户端生成器始终能获取您正在调用的版本。

## 身份验证 <!--quire:authentication-->

**API 密钥**用于脚本和服务器间集成。管理员在 `/admin/integrations/api-keys` 创建密钥、选择作用范围，并且只能查看一次。请将密钥作为 bearer token 发送：

```
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`。

<figure class="quire-shot" lang="en" dir="ltr"><img src="/screenshots/admin-api-keys.webp" alt="The API keys page with one key, the person it acts as, its scopes and its status, and a form to create another." width="944" height="700" loading="lazy" decoding="async"><figcaption>API keys list who each key acts as and what it may reach.</figcaption></figure>

## 请求 <!--quire:requests-->

- **分页**：所有列表都使用游标分页。先传入 `limit`，再将 `next_cursor`（来自 `page`）作为 `cursor` 继续请求；当 `has_more` 为 true 时重复此操作（见下例）。不支持 offset。
- **查询变更**：`updated_since` 会返回某个时间之后发生的变更。与 `include_deleted=true` 配合使用，或读取 `/<resource>/deletions`，即可获知哪些内容已删除。
- **外部标识符**：大多数资源都接受您自己的 `external_id`；使用 `/<resource>/ext:{external_id}` 可以按此标识符读取或更新资源，因此同步时无需保存 Quire 的标识符。
- **幂等性**：发送 `Idempotency-Key` 标头，并将其用于 `POST`、`PATCH` 和 `DELETE` 请求。使用相同密钥重试时，系统会返回首次响应，不会重复执行操作。批量端点必须使用此标头。
- **版本**：主版本写在路径中（`/v1`）。其中每项不兼容更改都使用按日期标记的修订版本，通过 `Quire-Version` 标头选择，例如 `Quire-Version: 2026-09-20`。不带此标头时，系统会提供凭据签发时对应的修订版本。

列表页面的示例：

```
{"data": [...], "page": {"next_cursor": "eyJ2Ijox...", "has_more": true, "limit": 100}}
```

## 错误 <!--quire:errors-->

所有错误都使用 RFC 9457 problem 文档：

```
{"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`。

## Webhook <!--quire:webhooks-->

可在 `/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=
```

验证投递时：

1. 根据收到的原始字节（在解析 JSON 前），使用 `{webhook-id}.{webhook-timestamp}.{raw body}` 组成字符串。
2. 使用订阅密钥计算其 HMAC-SHA256，并进行 base64 编码。
3. 以恒定时间比较结果和 `v1,` 中的 `webhook-signature` 值。密钥轮换期间可能同时存在两个值；匹配任意一个即可。
4. 如果时间戳与您的时钟相差超过五分钟，则拒绝该请求。

```
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-->

Quire 的 MCP 服务器位于组织地址的 `/mcp`，使用可流式传输的 HTTP。MCP 客户端从 `/.well-known/oauth-protected-resource` 发现 OAuth 服务器；用户像使用其他 OAuth 客户端一样登录并授权。工具会按用户本人的权限执行操作，执行破坏性操作前会要求确认。管理员可在 `/admin/integrations/mcp` 选择可用工具。

<figure class="quire-shot" lang="en" dir="ltr"><img src="/screenshots/admin-mcp.webp" alt="The AI assistants page with the server address to give an assistant and a table of the tools it can use." width="944" height="700" loading="lazy" decoding="async"><figcaption>AI assistants (MCP): the server address, and the tools an assistant may call.</figcaption></figure>

## 套餐与 API <!--quire:plans-and-the-api-->

API 权益包含 API 密钥、OAuth 客户端、webhook 和 MCP 服务器；所有标准套餐都包含此权益。不包含该权益的套餐会拒绝创建密钥、客户端或订阅，也会拒绝 REST 写入和 MCP 连接；REST 读取仍可使用，以便导出数据。拒绝响应是 problem 文档，代码为 `commerce.plan_entitlement`，类别为 `precondition`。

## 扩展 <!--quire:extensions-->

Quire 自带的活动类型、区块、选课方式、登录方式、题型、报告、主题和集成都通过同一个扩展注册表声明，自托管安装也可以在其中添加扩展。扩展会在编译时加入：没有运行时插件加载器，托管组织也不能自行添加扩展。管理员可在 `/admin/extensions` 为组织启用或关闭各个扩展（参见[管理员指南](/zh-Hans/admin/extensions/)）。

编写扩展时，可从 `packages/integration/extensions/src/sample.ts` 中的示例区块和主题开始。选择扩展点并阅读 `points.ts` 中的契约，然后声明扩展的标识符、版本、许可证、提供和依赖的内容，以及组织是否可以关闭它。在 Web 应用和 worker 组合的位置注册扩展，确保两者使用一致配置。注册表会在构建时以及每次调用 `register` 时检查各扩展点的规则；如果扩展集合无效，会列出所有问题并拒绝注册，同时保持注册表不变。扩展自身的测试应确认 `extensionContractProblems` 对该扩展返回空结果，并验证关闭扩展会改变其影响的功能。

Source: https://docs.quirelms.com/zh-Hans/developer/index.mdx
