---
title: "Panduan pembangun"
description: "REST API Quire, OAuth, webhook, pelayan MCP dan sambungan."
image: "https://docs.quirelms.com/og.png"
---

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

# Panduan pembangun

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

Guna alamat API organisasi anda dan satu kelayakan berskop. Mula dengan satu
permintaan baca, semak respons, dan simpan rahsia di luar kawalan sumber dan
contoh dokumentasi.

Quire mempunyai satu API awam: REST merentasi HTTPS, diterangkan oleh satu dokumen
OpenAPI 3.1, dengan webhook bertandatangan untuk peristiwa dan satu pelayan MCP
untuk pembantu AI. [Rujukan API](https://docs.quirelms.com/api/) menyenaraikan setiap titik akhir dan
peristiwa.

## Alamat <!--quire:addresses-->

Setiap organisasi mempunyai alamatnya sendiri, dan API berada di bawahnya:

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

Kelayakan menentukan organisasi. Kunci untuk satu organisasi yang digunakan pada
alamat organisasi lain ditolak.

Dokumen OpenAPI disajikan di `/api/v1/openapi.json` pada mana-mata alamat
organisasi, jadi penjana klien sentiasa melihat versi yang anda panggil.

## Pengesahan <!--quire:authentication-->

**Kunci API** adalah untuk skrip dan integrasi pelayan-ke-pelayan. Seorang
pentadbir menciptanya di `/admin/integrations/api-keys`, memilih skopnya, dan
melihatnya sekali. Hantarkannya sebagai satu token pembawa:

```
curl -H "Authorization: Bearer qk_live_..." https://acme.quirelms.com/api/v1/users?limit=50
```

Kunci bermula `qk_live_` atau `qk_test_`. Berikan setiap integrasi kuncinya
sendiri.

**OAuth 2.1** adalah untuk aplikasi yang bertindak sebagai seseorang yang sudah log
masuk. Daftarkan satu pelanggan di `/admin/integrations/oauth-clients`, kemudian
guna aliran kod kebenaran dengan PKCE (`/oauth/authorize`, `/oauth/token`), atau
kelayakan pelanggan untuk satu pelanggan mesin. Penemuan berada di
`/.well-known/oauth-authorization-server`. Satu skop mengehadkan apa yang boleh
dilakukan oleh satu token; ia tidak pernah membenarkannya melakukan lebih daripada
apa yang mampu dilakukan oleh orang itu.

Skop ialah `resource:read`, `resource:write` dan `resource:delete`, contohnya
`courses:read` atau `enrolments:write`. Empat daripadanya berkuasa dan dipaparkan
dengan satu amaran pada skrin persetujuan: `audit:read`, `roles:write`,
`tenants:write` dan `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>

## Permintaan <!--quire:requests-->

- **Penghalaman**: setiap senarai menggunakan penghalaman kursor. Hantar `limit`,
  kemudian `next_cursor` daripada `page` sebagai `cursor` selagi `has_more`
  bernilai benar (contoh di bawah). Tiada offset.
- **Perubahan sejak**: `updated_since` mengembalikan apa yang berubah selepas satu
  masa. Pasangkannya dengan `include_deleted=true`, atau baca
  `/<resource>/deletions`, untuk mengetahui apa yang dibuang.
- **Pengenal luaran**: kebanyakan sumber menerima `external_id` anda sendiri, dan
  `/<resource>/ext:{external_id}` membaca atau mengemas kini mengikutnya, jadi satu
  penyegerakan tidak pernah perlu menyimpan pengenal Quire.
- **Keidempotenan**: hantar satu tajuk `Idempotency-Key` pada `POST`, `PATCH` dan
  `DELETE`. Satu percubaan semula dengan kunci yang sama memulangkan respons
  pertama dan bukannya melakukan kerja itu dua kali. Titik akhir pukal
  memerlukannya.
- **Versi**: versi utama berada dalam laluan (`/v1`). Dalamnya, setiap perubahan
  yang memecahkan ialah satu semakan bertarikh, dipilih dengan tajuk
  `Quire-Version`, contohnya `Quire-Version: 2026-09-20`. Tanpa tajuk itu, anda
  mendapat semakan yang semasa apabila kelayakan anda dikeluarkan.

Sebuah halaman senarai:

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

## Ralat <!--quire:errors-->

Setiap ralat ialah satu dokumen masalah 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..."}
```

Cabangkan pada `code`, yang stabil; `detail` ditulis untuk manusia, selamat
dipaparkan kepada mereka, dan mungkin berubah. Apabila anda tidak mengecam satu
kod, ketuakan pada `category`:

| Kategori | Status | Cuba lagi |
| --- | --- | --- |
| `validation` | 422, dengan butiran medan dalam `errors` | Tidak |
| `authentication` | 401 | Tidak |
| `authorization` | 403 | Tidak |
| `not_found` | 404 | Tidak |
| `conflict` | 409 | Kadangkala |
| `precondition` | 412 | Tidak |
| `quota` | 402 untuk pelan, 413 untuk saiz | Tidak |
| `rate_limit` | 429, dengan `Retry-After` | Ya |
| `upstream` | 502 atau 504 | Ya |
| `internal` | 500 | Ya |

Petik `request_id` apabila anda menghubungi sokongan.

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

Langgan di `/admin/webhooks`, atau melalui API di `/webhook_subscriptions`. Pilih
peristiwa mengikut nama (`enrolment.created`), mengikut bidang (`enrolment.*`) atau
semuanya (`*`). Quire mula-mula menghantar satu `webhook.ping`; langganan bermula
sebaik sahaja titik akhir anda menjawabnya.

Penghantaran mengikut spesifikasi Standard Webhooks:

```
POST /hooks/quire
webhook-id: 01JB7XQK4Z8FQ2M3N4P5R6S7T8
webhook-timestamp: 1790000000
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=
```

Untuk menyahkan satu penghantaran:

1. Bina rentetan `{webhook-id}.{webhook-timestamp}.{raw body}` daripada bait tepat
   yang diterima, sebelum sebarang penghuraian JSON.
2. Kira HMAC-SHA256 ke atasnya dengan rahsia langganan anda, dan base64-kan.
3. Bandingkan dengan setiap nilai `v1,` dalam `webhook-signature` pada masa
   pemalar. Mungkin ada dua semasa satu pusingan rahsia; mana-mana yang sepadan
   adalah sah.
4. Tolak satu cap masa yang lebih lima minit daripada jam anda.

```
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);
  });
}
```

Nyahganda pada `webhook-id`: satu penghantaran mungkin tiba lebih daripada sekali.
Badan itu membawa pengenal dan satu ringkasan pendek; ambil sumber tersebut untuk
keadaan semasanya. Penghantaran yang gagal dicuba semula dengan backoff sehingga 72
jam, dan boleh dimainkan semula daripada log penghantaran.

## MCP <!--quire:mcp-->

Pelayan MCP Quire berada di `/mcp` pada alamat organisasi, merentasi HTTP boleh
strim. Sebuah klien MCP menemui pelayan OAuth daripada
`/.well-known/oauth-protected-resource`, dan orang itu log masuk dan memberi
persetujuan seperti mana-mana pelanggan OAuth. Alat bertindak sebagai orang itu,
dengan keizinan mereka, dan alat yang memusnahkan meminta pengesahan. Pentadbir
memilih alat mana yang tersedia di `/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>

## Pelan dan API <!--quire:plans-and-the-api-->

Kunci API, pelanggan OAuth, webhook dan pelayan MCP tergolong dalam hak API pelan
tersebut, dan setiap pelan standard menyertainya. Dalam pelan tanpanya, penciptaan
satu kunci, pelanggan atau langganan ditolak, tulisan REST dan sambungan MCP
ditolak, dan bacaan REST terus berfungsi supaya data kekal boleh dieksport. Penolakan
tersebut ialah satu dokumen masalah dengan kod `commerce.plan_entitlement`, dalam
kategori `precondition`.

## Sambungan <!--quire:extensions-->

Jenis aktiviti, blok, kaedah pendaftaran, kaedah log masuk, jenis soalan, laporan,
tema dan integrasi milik Quire sendiri diisytiharkan melalui registri sambungan yang
sama yang boleh ditambah oleh sebuah pemasangan kendiri. Sambungan dikompilasi
masuk: tiada pemuat pemalam masa laksana, dan sebuah organisasi hos tidak boleh
menambah satu. Pentadbir menghidupkan atau mematikan setiap sambungan untuk
organisasi mereka di `/admin/extensions` (lihat
[panduan pentadbir](/ms/admin/extensions/)).

Untuk menulis satu, mulakan daripada blok sampel dan tema dalam
`packages/integration/extensions/src/sample.ts`. Pilih titik sambungan dan baca
kontraknya dalam `points.ts`, kemudian isytiharkan sambungannya dengan satu id,
versi, lesen, apa yang ia sediakan dan perlukan, dan sama ada sebuah organisasi
boleh mematikannya. Daftarkan ia di mana aplikasi web dan pekerja digabungkan,
supaya kedua-duanya bersetuju. Registri menyemak peraturan setiap titik semasa ia
dibina dan setiap kali anda memanggil `register`, menolak satu set yang akan tidak
sah dengan setiap masalah dinamakan, dan meninggalkan registri tanpa perubahan
apabila ia berbuat demikian. Ujian sambungan itu sendiri sepatutnya menegaskan bahawa
`extensionContractProblems` kosong baginya dan bahawa mematikannya mengubah apa yang
dipengaruhinya.

Source: https://docs.quirelms.com/ms/developer/index.mdx
