---
title: "Hantlieding foar ûntwikkelders"
description: "De REST API fan Quire, OAuth, webhooks, de MCP-tsjinner en útwreidingen."
image: "https://docs.quirelms.com/og.png"
---

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

# Hantlieding foar ûntwikkelders

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

Brûk it API-adres fan dyn organisaasje en in bewiis mei beheinde tagong. Begjin mei in
lêsoanfraach, kontrolearje it antwurd en hâld geheimen bûten boarnekoade en dokumintaasjefoarbylden.

Quire hat ien iepenbiere API: REST oer HTTPS, beskreaun troch in OpenAPI 3.1-dokumint,
mei ûndertekene webhooks foar barrens en in MCP-tsjinner foar AI-assistinten. De
[API-referinsje](https://docs.quirelms.com/api/) list alle einpunten en barrens.

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

Elke organisaasje hat in eigen adres, en de API stiet dêrûnder:

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

It bewiis bepaalt de organisaasje. In kaai foar ien organisaasje dy't brûkt wurdt op it adres
fan in oare organisaasje, wurdt wegere.

It OpenAPI-dokumint is beskikber op `/api/v1/openapi.json` op elk organisaasje-adres,
sadat clientgenerators altyd de ferzje sjen dy'tsto oanropst.

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

**API-kaaien** binne foar skripts en yntegraasjes tusken tsjinners. In behearder makket
ien oan op `/admin/integrations/api-keys`, kiest de scopes en sjocht him ien kear. Stjoer
him as bearer-token:

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

Kaaien begjinne mei `qk_live_` of `qk_test_`. Jou elke yntegraasje in eigen kaai.

**OAuth 2.1** is foar applikaasjes dy't hannelje as in oanmelde persoan. Registrearje in
client op `/admin/integrations/oauth-clients`, en brûk dêrnei de authorization-code-flow
mei PKCE (`/oauth/authorize`, `/oauth/token`) of client credentials foar in masineclient.
Untdekking is beskikber op `/.well-known/oauth-authorization-server`. In scope beheint wat
in token dwaan kin; it jout nea mear rjochten as de persoan sels hat.

Scopes binne `resource:read`, `resource:write` en `resource:delete`, bygelyks `courses:read`
of `enrolments:write`. Fjouwer binne befoarrjochte en krije in warskôging op it
tastimmingsskerm: `audit:read`, `roles:write`, `tenants:write` en `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>

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

- **Sideferdieling**: elke list wurdt mei in cursor ferdield yn siden. Jou `limit` mei en
  brûk dêrnei de `next_cursor` út `page` as `cursor` wylst `has_more` wier is (foarbyld
  hjirûnder). Der is gjin offset.
- **Feroarings sûnt**: `updated_since` jout werom wat nei in tiidstip feroare is. Kombinearje
  it mei `include_deleted=true` of lês `/<resource>/deletions` om te sjen wat fuorthelle is.
- **Eksterne identifiers**: de measte boarnen akseptearje dyn eigen `external_id`; mei
  `/<resource>/ext:{external_id}` kinst op basis dêrfan lêze of bywurkje, sadat in syngronisaasje
  de Quire-identifiers net hoecht te bewarjen.
- **Idempotinsje**: stjoer in `Idempotency-Key`-koptekst mei by `POST`, `PATCH` en `DELETE`.
  In opnij besochte oanfraach mei deselde kaai jout it earste antwurd werom ynstee fan it wurk
  dûbel út te fieren. Bulk-einpunten fereaskje dit.
- **Ferzjes**: de haadferzje stiet yn it paad (`/v1`). Dêryn wurdt elke brekkende feroaring
  in datearre revyzje dy'tst mei de koptekst `Quire-Version` kiest, bygelyks
  `Quire-Version: 2026-09-20`. Sûnder dy koptekst krijst de revyzje dy't aktueel wie doe't
  dyn bewiis útjûn waard.

In side út in list:

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

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

Elke flater is in RFC 9457-probleemdokumint:

```
{"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..."}
```

Stjoer op `code`, dat stabyl is; `detail` is foar minsken skreaun, is feilich om sjen te
litten en kin feroarje. Ast in koade net werkenst, groepearje op `category`:

| Kategory | Status | Opnij besykje |
| --- | --- | --- |
| `validation` | 422, mei fjilddetails yn `errors` | Nee |
| `authentication` | 401 | Nee |
| `authorization` | 403 | Nee |
| `not_found` | 404 | Nee |
| `conflict` | 409 | Soms |
| `precondition` | 412 | Nee |
| `quota` | 402 foar it abonnemint, 413 foar grutte | Nee |
| `rate_limit` | 429, mei `Retry-After` | Ja |
| `upstream` | 502 of 504 | Ja |
| `internal` | 500 | Ja |

Neam `request_id` ast kontakt opnimst mei stipe.

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

Abonnearje op `/admin/webhooks` of fia de API op `/webhook_subscriptions`. Kies barrens
op namme (`enrolment.created`), op gebiet (`enrolment.*`) of allegear (`*`). Quire stjoert
earst in `webhook.ping`; it abonnemint begjint as dyn einpunt dêrop antwurdet.

Leveringen folgje de Standard Webhooks-spesifikaasje:

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

Om in levering te ferifiearjen:

1. Bou de tekst `{webhook-id}.{webhook-timestamp}.{raw body}` út de eksakte bytes dy't
   ûntfongen binne, foardatst JSON ferwurkje.
2. Berekkenje dêr HMAC-SHA256 oer mei it geheim fan dyn abonnemint en set it resultaat om
   nei base64.
3. Ferlykje it yn konstante tiid mei elke `v1,`-wearde yn `webhook-signature`. By in
   kaairotaasje kinne der twa wêze; ien oerienkomst is genôch.
4. Wegerje in tiidstimpel dy't mear as fiif minuten fan dyn klok ôfwykt.

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

Foarkom dûbele ferwurking op basis fan `webhook-id`: in levering kin mear as ien kear komme.
De ynhâld befettet identifiers en in koarte gearfetting; helje de boarne op foar de aktuele
steat. Mislearre leveringen wurde oant 72 oeren mei tanimmende tuskenskoften opnij besocht en
kinne út it leveringslogboek opnij spile wurde.

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

De MCP-tsjinner fan Quire stiet op `/mcp` op it organisaasje-adres en brûkt streamable HTTP.
In MCP-client ûntdekt de OAuth-tsjinner fia `/.well-known/oauth-protected-resource`; de
persoan meldt him oan en jout tastimming lykas by elke OAuth-client. Ark hannelje mei de
tastimmingen fan dy persoan, en foar ferneatigjende ark wurdt befêstiging frege. Behearders
kieze beskikbere ark op `/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>

## Abonneminten en de API <!--quire:plans-and-the-api-->

API-kaaien, OAuth-clients, webhooks en de MCP-tsjinner falle ûnder it API-rjocht fan it
abonnemint, en elk standert abonnemint befettet dit. Op in abonnemint sûnder dit rjocht
kinne kaaien, clients en abonneminten net oanmakke wurde, wurde REST-skriufoanfragen en
MCP-ferbiningen wegere, mar bliuwe REST-lêsoanfragen wurkjen sadat gegevens eksportearre
kinne wurde. De ôfwizing is in probleemdokumint mei koade `commerce.plan_entitlement` yn
de kategory `precondition`.

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

De eigen aktiviteitstypen, blokken, ynskriuwmetoaden, oanmeldmetoaden, fraachtypen,
rapporten, tema's en yntegraasjes fan Quire wurde oanjûn fia itselde útwreidingsregister
dat in selsbehearde ynstallaasje oanfolje kin. Utwreidingen binne ynboud: der is gjin
runtime-pluginlader en in hosted organisaasje kin der gjin tafoegje. Behearders skeakelje
elke útwreiding foar de eigen organisaasje yn of út op `/admin/extensions` (sjoch de
[hantlieding foar behearders](/fy/admin/extensions/)).

Om der ien te skriuwen, begjin mei it foarbyldblok en tema yn
`packages/integration/extensions/src/sample.ts`. Kies it útwreidingspunt en lês it kontrakt
yn `points.ts`; ferklearje dêrnei de útwreiding mei in id, ferzje, lisinsje, wat dy leveret
en fereasket en oft in organisaasje dy útskeakelje mei. Registrearje him dêr't de webapplikaasje
en worker gearstald wurde, sadat se it iens binne. It register kontrolearret de regels fan elk
útwreidingspunt by it bouwen en elke kear datst `register` oanropst; it wegeret in ûnjildige
set mei fermelding fan elk probleem en lit it register ûnferoare. De eigen tests fan de
útwreiding moatte befêstigje dat `extensionContractProblems` der leech foar is en dat
útskeakeljen feroaret wat de útwreiding beynfloedet.

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