---
title: "Panduan pengembang"
description: "REST API Quire, OAuth, webhook, server MCP, dan ekstensi."
image: "https://docs.quirelms.com/og.png"
---

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

# Panduan pengembang

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

Gunakan alamat API organisasi dan kredensial dengan cakupan terbatas. Mulailah dengan permintaan baca, periksa responsnya, serta simpan rahasia di luar kontrol sumber dan contoh dokumentasi.

Quire memiliki satu API publik: REST melalui HTTPS yang dijelaskan dalam dokumen OpenAPI 3.1, webhook bertanda tangan untuk peristiwa, dan server MCP bagi asisten AI. [Referensi API](https://docs.quirelms.com/api/) mencantumkan setiap endpoint dan peristiwa.

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

Setiap organisasi memiliki alamat sendiri, dan API berada di bawah alamat tersebut:

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

Kredensial menentukan organisasi. Kunci milik satu organisasi yang digunakan di alamat organisasi lain akan ditolak.

Dokumen OpenAPI tersedia di `/api/v1/openapi.json` pada alamat organisasi mana pun, sehingga generator klien selalu mendapatkan versi yang sedang Anda panggil.

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

**Kunci API** digunakan untuk skrip dan integrasi server-ke-server. Administrator membuatnya di `/admin/integrations/api-keys`, memilih cakupannya, lalu melihatnya sekali. Kirim sebagai bearer token:

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

Awalan kunci adalah `qk_live_` atau `qk_test_`. Berikan kunci tersendiri untuk setiap integrasi.

**OAuth 2.1** digunakan untuk aplikasi yang bertindak sebagai orang yang sudah masuk. Daftarkan klien di `/admin/integrations/oauth-clients`, lalu gunakan alur kode otorisasi dengan PKCE (`/oauth/authorize`, `/oauth/token`) atau kredensial klien untuk klien mesin. Discovery tersedia di `/.well-known/oauth-authorization-server`. Cakupan membatasi tindakan token; cakupan tidak pernah memberikan kemampuan melebihi hak orang tersebut.

Cakupannya adalah `resource:read`, `resource:write`, dan `resource:delete`, misalnya `courses:read` atau `enrolments:write`. Empat cakupan bersifat istimewa dan diberi peringatan pada layar 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-->

- **Paginasi**: setiap daftar dipaginasi menggunakan cursor. Kirim `limit`, lalu teruskan `next_cursor` dari `page` sebagai `cursor` selama `has_more` bernilai true (lihat contoh di bawah). Tidak ada offset.
- **Perubahan sejak waktu tertentu**: `updated_since` mengembalikan perubahan setelah waktu yang diberikan. Pasangkan dengan `include_deleted=true` atau baca `/<resource>/deletions` untuk mengetahui hal yang dihapus.
- **ID eksternal**: sebagian besar sumber daya menerima `external_id` sendiri, dan `/<resource>/ext:{external_id}` dapat membaca atau melakukan upsert berdasarkan ID tersebut, sehingga sinkronisasi tidak perlu menyimpan ID Quire.
- **Idempotensi**: kirim header `Idempotency-Key` pada `POST`, `PATCH`, dan `DELETE`. Pengulangan dengan kunci yang sama mengembalikan respons pertama alih-alih menjalankan pekerjaan dua kali. Endpoint massal mewajibkannya.
- **Versi**: versi utama ada di jalur (`/v1`). Di dalamnya, setiap perubahan yang tidak kompatibel merupakan revisi bertanggal yang dipilih melalui header `Quire-Version`, misalnya `Quire-Version: 2026-09-20`. Tanpa header, Anda mendapatkan revisi yang berlaku saat kredensial diterbitkan.

Contoh halaman daftar:

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

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

Setiap kesalahan berupa 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..."}
```

Gunakan `code` sebagai dasar logika karena nilainya stabil; `detail` ditulis untuk pengguna, aman untuk ditampilkan, dan dapat berubah. Jika tidak mengenali kode, kelompokkan berdasarkan `category`:

| Kategori | Status | Coba lagi |
| --- | --- | --- |
| `validation` | 422, dengan detail kolom di `errors` | Tidak |
| `authentication` | 401 | Tidak |
| `authorization` | 403 | Tidak |
| `not_found` | 404 | Tidak |
| `conflict` | 409 | Kadang |
| `precondition` | 412 | Tidak |
| `quota` | 402 untuk paket, 413 untuk ukuran | Tidak |
| `rate_limit` | 429, dengan `Retry-After` | Ya |
| `upstream` | 502 atau 504 | Ya |
| `internal` | 500 | Ya |

Sebutkan `request_id` saat menghubungi dukungan.

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

Berlangganan melalui `/admin/webhooks` atau API di `/webhook_subscriptions`. Pilih peristiwa berdasarkan nama (`enrolment.created`), area (`enrolment.*`), atau semua peristiwa (`*`). Quire mengirim `webhook.ping` terlebih dahulu; langganan dimulai setelah endpoint Anda menjawabnya.

Pengiriman mengikuti spesifikasi Standard Webhooks:

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

Untuk memverifikasi pengiriman:

1. Dari byte persis yang diterima, sebelum mengurai JSON, susun string `{webhook-id}.{webhook-timestamp}.{raw body}`.
2. Hitung HMAC-SHA256 dengan rahasia langganan, lalu enkode sebagai base64.
3. Bandingkan dengan setiap nilai `v1,` pada `webhook-signature` dalam waktu konstan. Saat rotasi rahasia mungkin ada dua nilai; salah satu yang cocok sudah valid.
4. Tolak timestamp yang selisihnya lebih dari lima menit dari 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);
  });
}
```

Hindari duplikasi berdasarkan `webhook-id`: pengiriman yang sama dapat tiba lebih dari sekali. Isi pesan membawa ID dan ringkasan singkat; ambil sumber daya untuk mendapatkan status terkini. Pengiriman gagal dicoba ulang dengan jeda bertahap hingga 72 jam dan dapat diputar ulang dari log pengiriman.

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

Server MCP Quire berada di `/mcp` pada alamat organisasi melalui HTTP streaming. Klien MCP menemukan server OAuth melalui `/.well-known/oauth-protected-resource`; pengguna masuk dan memberikan persetujuan seperti pada klien OAuth lainnya. Alat bertindak sebagai pengguna tersebut dengan izinnya; alat destruktif meminta konfirmasi. Administrator memilih alat 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>

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

Kunci API, klien OAuth, webhook, dan server MCP termasuk dalam hak API paket; semua paket standar menyertakannya. Pada paket yang tidak memiliki hak tersebut, pembuatan kunci, klien, atau langganan ditolak; penulisan REST dan koneksi MCP juga ditolak, sedangkan pembacaan REST tetap berfungsi agar data dapat diekspor. Penolakan berupa dokumen masalah dengan kode `commerce.plan_entitlement` dalam kategori `precondition`.

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

Jenis aktivitas, blok, metode pendaftaran, metode masuk, jenis soal, laporan, tema, dan integrasi bawaan Quire dideklarasikan melalui registri ekstensi yang juga dapat ditambahkan oleh instalasi hosting mandiri. Ekstensi dikompilasi ke dalam Quire: tidak ada pemuat plugin saat runtime dan organisasi hosting tidak dapat menambahkan ekstensi. Administrator mengaktifkan atau menonaktifkan ekstensi untuk organisasinya di `/admin/extensions` (lihat [panduan administrator](/id/admin/extensions/)).

Untuk membuat ekstensi, mulai dari contoh blok dan tema di `packages/integration/extensions/src/sample.ts`. Pilih titik ekstensi dan baca kontraknya di `points.ts`, lalu deklarasikan ekstensi dengan ID, versi, lisensi, hal yang disediakan dan dibutuhkan, serta apakah organisasi dapat menonaktifkannya. Daftarkan ekstensi di tempat aplikasi web dan worker dirangkai agar keduanya selaras. Saat dibangun dan setiap kali Anda memanggil `register`, registri memeriksa aturan tiap titik; registri menolak kumpulan yang tidak valid dengan menyebutkan semua masalah, tanpa mengubah isinya. Pengujian ekstensi sebaiknya memastikan `extensionContractProblems` kosong untuk ekstensi itu dan penonaktifannya mengubah hal yang dipengaruhinya.

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