---
title: "Udviklervejledning"
description: "Quires REST-API, OAuth, webhooks, MCP-serveren og udvidelser."
image: "https://docs.quirelms.com/og.png"
---

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

# Udviklervejledning

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

Brug din organisations API-adresse og en legitimationsoplysning med begrænset omfang. Begynd med en læseforespørgsel, kontrollér svaret, og hold hemmeligheder ude af kildekodearkivet og dokumentationseksempler.

Quire har ét offentligt API: REST over HTTPS, beskrevet af et OpenAPI 3.1-
dokument, med signerede webhooks til hændelser og en MCP-server til AI-
assistenter. [API-referencen](https://docs.quirelms.com/api/) indeholder alle endpoints og hændelser.

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

Hver organisation har sin egen adresse, og API'et ligger under den:

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

Legitimationsoplysningen afgør organisationen. En nøgle til én organisation, der bruges på
en anden organisations adresse, afvises.

OpenAPI-dokumentet leveres på `/api/v1/openapi.json` på enhver
organisationsadresse, så klientgeneratorer altid ser den version, du kalder.

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

**API-nøgler** bruges til scripts og server-til-server-integrationer. En
administrator opretter en på `/admin/integrations/api-keys`, vælger dens
omfang og ser den én gang. Send den som et bearer-token:

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

Nøgler begynder med `qk_live_` eller `qk_test_`. Giv hver integration sin egen nøgle.

**OAuth 2.1** bruges af applikationer, der handler på vegne af en indlogget person. Registrér
en klient på `/admin/integrations/oauth-clients`, og brug derefter autorisationskodeflowet med PKCE (`/oauth/authorize`, `/oauth/token`) eller klientlegitimationsoplysninger
til en maskinklient. Discovery findes på
`/.well-known/oauth-authorization-server`. Et scope begrænser, hvad et token kan
gøre; det giver det aldrig større rettigheder end personen selv har.

Scopes er `resource:read`, `resource:write` og `resource:delete`, for
eksempel `courses:read` eller `enrolments:write`. Fire scopes har udvidede rettigheder og vises
med en advarsel på samtykkeskærmen: `audit:read`, `roles:write`,
`tenants:write` og `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>

## Forespørgsler <!--quire:requests-->

- **Sideopdeling**: Alle lister bruger cursor-sideopdeling. Send `limit`, og send derefter
  `next_cursor` fra `page` som `cursor`, mens `has_more` er sand (se eksemplet
  nedenfor). Der bruges ikke offset.
- **Ændringer siden et tidspunkt**: `updated_since` returnerer det, der er ændret efter et tidspunkt.
  Kombinér det med `include_deleted=true`, eller læs `/<resource>/deletions` for at
  se, hvad der er fjernet.
- **Eksterne identifikatorer**: De fleste ressourcer accepterer din egen `external_id`,
  og `/<resource>/ext:{external_id}` læser eller opretter/opdaterer efter denne, så en synkronisering aldrig
  behøver at gemme Quires identifikatorer.
- **Idempotens**: Send headeren `Idempotency-Key` med `POST`, `PATCH` og
  `DELETE`. Et forsøg igen med samme nøgle returnerer det første svar i stedet for
  at udføre handlingen to gange. Masseendpoints kræver den.
- **Versioner**: Hovedversionen står i stien (`/v1`). Inden for den er hver
  inkompatibel ændring en dateret revision, valgt med headeren `Quire-Version`,
  for eksempel `Quire-Version: 2026-09-20`. Uden headeren får du den
  revision, der var aktuel, da din legitimationsoplysning blev udstedt.

En side fra en liste:

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

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

Hver fejl er et RFC 9457-problemdokument:

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

Brug `code`, som er stabil; `detail` er skrevet til mennesker, kan vises til dem og
kan ændres. Hvis du ikke genkender en kode, skal du gruppere efter `category`:

| Kategori | Status | Prøv igen |
| --- | --- | --- |
| `validation` | 422, med feltoplysninger i `errors` | Nej |
| `authentication` | 401 | Nej |
| `authorization` | 403 | Nej |
| `not_found` | 404 | Nej |
| `conflict` | 409 | Nogle gange |
| `precondition` | 412 | Nej |
| `quota` | 402 for abonnement, 413 for størrelse | Nej |
| `rate_limit` | 429, med `Retry-After` | Ja |
| `upstream` | 502 eller 504 | Ja |
| `internal` | 500 | Ja |

Oplys `request_id`, når du kontakter support.

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

Abonnér på `/admin/webhooks` eller via API'et på
`/webhook_subscriptions`. Vælg hændelser efter navn (`enrolment.created`),
område (`enrolment.*`) eller alle (`*`). Quire sender først en `webhook.ping`; abonnementet
starter, når dit endpoint svarer på den.

Leveringer følger specifikationen Standard Webhooks:

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

Sådan kontrolleres en levering:

1. Byg strengen `{webhook-id}.{webhook-timestamp}.{raw body}` ud fra de
   nøjagtige modtagne bytes, før JSON-parsing.
2. Beregn HMAC-SHA256 af den med abonnementets hemmelighed, og kod resultatet som base64.
3. Sammenlign i konstant tid med hver `v1,`-værdi i `webhook-signature`.
   Der kan være to under rotation af en hemmelighed; det er gyldigt, hvis en af dem matcher.
4. Afvis et tidsstempel, der afviger mere end fem minutter fra dit ur.

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

Fjern dubletter efter `webhook-id`: en levering kan ankomme mere end én gang. Indholdet
indeholder identifikatorer og et kort resumé; hent ressourcen for dens aktuelle
tilstand. Mislykkede leveringer forsøges igen med stigende ventetid i op til 72 timer og
kan afspilles igen fra leveringsloggen.

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

Quires MCP-server er på `/mcp` på organisationens adresse via
streamable HTTP. En MCP-klient finder OAuth-serveren via
`/.well-known/oauth-protected-resource`, hvorefter personen logger ind og
giver samtykke som for enhver OAuth-klient. Værktøjer handler med personens
rettigheder, og destruktive værktøjer beder om bekræftelse. Administratorer
vælger, hvilke værktøjer der er tilgængelige på `/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>

## Abonnementer og API'et <!--quire:plans-and-the-api-->

API-nøgler, OAuth-klienter, webhooks og MCP-serveren hører til abonnementets API-
rettighed, som indgår i alle standardabonnementer. På et abonnement uden den rettighed afvises oprettelse
af nøgler, klienter eller abonnementer; REST-skrivninger og MCP-forbindelser afvises,
mens REST-læsninger stadig fungerer, så data kan eksporteres. Afvisningen er et problemdokument med koden
`commerce.plan_entitlement` i kategorien `precondition`.

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

Quires egne aktivitetstyper, blokke, tilmeldingsmetoder, loginmetoder, spørgsmålstyper,
rapporter, temaer og integrationer erklæres via det samme udvidelsesregister, som en selvhostet installation kan udvide. Udvidelser kompileres ind:
Der findes ingen pluginindlæser under kørsel, og en hostet organisation kan ikke tilføje en.
Administratorer slår hver udvidelse til eller fra for deres organisation på
`/admin/extensions` (se [administratorvejledningen](/da/admin/extensions/)).

Begynd med eksempelblokken og -temaet i
`packages/integration/extensions/src/sample.ts`, når du skriver en udvidelse. Vælg et udvidelsespunkt, og
læs dets kontrakt i `points.ts`. Deklarér derefter udvidelsen med et id, en version,
en licens, hvad den leverer og kræver, og om en organisation må slå den fra.
Registrér den dér, hvor webapplikationen og worker sammensættes, så de er enige. Registret kontrollerer hvert udvidelsespunkt efter dets egne regler, når registret bygges, og hver gang du kalder
`register`. Det afviser et ugyldigt sæt med en forklaring af hvert problem og
lader registret være uændret. Udvidelsens egne tests bør kontrollere,
at `extensionContractProblems` er tom for den, og at de funktioner, den påvirker, ændres, når den slås fra.

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