Zugänge

SDK für JavaScript und TypeScript

Das offizielle Paket für Node.js ab Version 18. Ohne Abhängigkeiten und mit Typen für jede Antwort.

Installation

#

Das Paket @quellenkontor/sdk läuft in Node.js ab Version 18. Es nutzt nur fetch und keine Node-Module, hat keine Abhängigkeiten und bringt Typen für TypeScript mit.

npm
npm install @quellenkontor/sdk
pnpm, yarn, bun
pnpm add @quellenkontor/sdk
yarn add @quellenkontor/sdk
bun add @quellenkontor/sdk
Das Paket ist ein ES-Modul. In älteren CommonJS-Projekten lädst du es mit const { Quellenkontor } = await import("@quellenkontor/sdk").

Einrichten

#
JavaScript
import { Quellenkontor } from "@quellenkontor/sdk";

// liest den Schlüssel aus QK_KEY
const qk = new Quellenkontor();

// oder ausdrücklich, mit Optionen
const qk2 = new Quellenkontor({
  apiKey: process.env.QK_KEY,
  timeoutMs: 10_000,
  wiederholungen: 3,
});
OptionStandardBedeutung
apiKeyUmgebungsvariable QK_KEYDein API-Schlüssel. Du kannst ihn auch als ersten Parameter übergeben: new Quellenkontor(schluessel)
basisUrlhttps://api.quellenkontor.dev/v1Nur für Tests gegen einen anderen Server
timeoutMs15000Zeitlimit je Anfrage in Millisekunden
wiederholungen2Wiederholungen bei Netzfehlern und Status 502, 503, 504, mit 300 und 600 Millisekunden Pause
fetchglobales fetchEigene fetch-Funktion, etwa für Proxys oder Tests
cachetrueZwischenspeicher im Speicher dieser Instanz an oder aus, siehe unten
cacheTtlMs300000 (5 Minuten)Wie lange eine Antwort ohne erneute Anfrage aus dem Zwischenspeicher kommt
offlinetrueOffline-Stand als Fallback nutzen, wenn die API nicht erreichbar ist, siehe unten

Zwischenspeicher und ETag

Innerhalb von cacheTtlMs kommt eine wiederholte Anfrage mit denselben Parametern ohne jeden Netzwerkaufruf aus dem Zwischenspeicher der Instanz zurück. Danach fragt das SDK mit der gemerkten ETag im Header If-None-Match nach: Hat sich der Wert nicht geändert, antwortet die API mit 304, das SDK reicht die gecachte Antwort weiter, und die Abfrage zählt nicht gegen dein Kontingent. Mehr zum Sparen von Abfragen unter Kontingente und Limits.

JavaScript
const qk = new Quellenkontor({ cache: false }); // immer eine frische Anfrage

// oder den Zwischenspeicher gezielt leeren, etwa nach einem Webhook
const qk2 = new Quellenkontor();
qk2.cacheLeeren();

Offline-Stand

Ist die API nach allen Wiederholungen nicht erreichbar, weicht das SDK auf einen mitgelieferten Offline-Stand aus: den Verlauf jeder Tabelle mit Verlauf, im Paket erzeugt aus dem aktuellen Bestand (derselbe Stand wie /export). Die Antwort trägt dann offline: true und den datenstand des Pakets statt des amtlich aktuellen, ohne die berechneten Zusatzfelder wie zitat. Reine Rechner ohne Tabelle, etwa Kündigungsfrist, Urlaubsanspruch oder Dienstwagen, haben keinen Offline-Stand und bleiben ohne Verbindung ein QuellenkontorFehler mit Code netzwerk.

JavaScript
const qk = new Quellenkontor({ offline: false }); // stattdessen immer den Netzwerkfehler werfen

// mit Offline-Stand (Standard): erkennbar am Feld offline
const qkMitFallback = new Quellenkontor();
const ml = await qkMitFallback.hr.mindestlohn({ datum: "2027-01-15" });
if (ml.offline) console.warn("Offline-Stand vom", ml.datenstand);

Alle Methoden

#

Jeder Datensatz ist eine Methode unter qk.hr. Sie liefert ein Promise mit dem Objekt, das auch die API liefert. Die Felder stehen in der Referenz.

MethodeReferenz
qk.hr.mindestlohn({ datum?: string } = {})Mindestlohn
qk.hr.mindestausbildungsverguetung({ beginn?: string; ausbildungsjahr?: number; bestandteil?: "ausbildungsjahr_1" | "ausbildungsjahr_2" | "ausbildungsjahr_3" | "ausbildungsjahr_4" } = {})Ausbildungsvergütung
qk.hr.pflegemindestlohn({ datum?: string; bestandteil?: "pflegehilfskraft" | "pflegehilfskraft_ost" | "qualifizierte_pflegehilfskraft" | "qualifizierte_pflegehilfskraft_ost" | "pflegefachkraft" | "zusatzurlaub_tage" } = {})Pflegemindestlohn
qk.hr.rechengroessen({ jahr?: number; datum?: string } = {})Beitragsbemessungsgrenzen
qk.hr.beitragssaetze({ datum?: string } = {})Beitragssätze
qk.hr.sachbezugswerte({ jahr?: number; datum?: string } = {})Sachbezugswerte
qk.hr.pfaendungsfreigrenzen({ datum?: string; unterhaltspflichten?: number; netto?: number } = {})Pfändungsfreigrenzen
qk.hr.uebergangsbereich({ datum?: string } = {})Übergangsbereich
qk.hr.minijobAbgaben({ datum?: string; bestandteil?: "gewerblich_kv" | "gewerblich_rv" | "gewerblich_rv_eigenanteil" | "gewerblich_pauschsteuer" | "gewerblich_u1" | "gewerblich_u2" | "gewerblich_insolvenzgeldumlage" | "privat_kv" | "privat_rv" | "privat_rv_eigenanteil" | "privat_pauschsteuer" | "privat_u1" | "privat_u2" | "privat_unfallversicherung" } = {})Minijob-Abgaben
qk.hr.kuenstlersozialabgabe({ jahr?: number; datum?: string; bestandteil?: "abgabesatz" } = {})Künstlersozialabgabe
qk.hr.ausgleichsabgabe({ jahr?: number; datum?: string; arbeitsplaetze?: number; besetzt?: number; bestandteil?: "quote_3_bis_unter_5" | "quote_2_bis_unter_3" | "quote_ueber_0_bis_unter_2" | "quote_0" | "klein_unter_40_weniger_als_1" | "klein_unter_40_null" | "klein_unter_60_weniger_als_2" | "klein_unter_60_weniger_als_1" | "klein_unter_60_null" | "pflichtquote_prozent" | "pflicht_ab_arbeitsplaetzen" } = {})Ausgleichsabgabe
qk.hr.steuerfreieBetraege({ datum?: string; bestandteil?: "sachbezug_freigrenze" | "aufmerksamkeiten_freigrenze" | "betriebsveranstaltung_freibetrag" | "rabatt_freibetrag" | "gesundheitsfoerderung_freibetrag" | "mahlzeit_auswaerts_preisgrenze" | "aktivrente_freibetrag" | "vermoegensbeteiligung_freibetrag" | "kurzfristige_betreuung_freibetrag" | "bav_steuerfrei_prozent_bbg" | "bav_zusatzbetrag_neuzusage" | "inflationsausgleichspraemie" | "corona_praemie" | "pflegebonus" | "uebungsleiter_freibetrag" | "ehrenamt_freibetrag" | "fahrtkostenzuschuss_pauschalsteuer" | "erholungsbeihilfe_arbeitnehmer" | "erholungsbeihilfe_ehegatte" | "erholungsbeihilfe_kind" | "sachzuwendungen_37b_hoechstbetrag" | "sachzuwendungen_37b_steuersatz" | "arbeitnehmer_pauschbetrag" | "homeoffice_tag" | "homeoffice_hoechstbetrag" | "entfernungspauschale_bis_20_km" | "entfernungspauschale_ab_21_km" | "entfernungspauschale_hoechstbetrag" } = {})Freibeträge und Pauschalen
qk.hr.reisekostenInland({ datum?: string; bestandteil?: "verpflegung_24_stunden" | "verpflegung_an_abreisetag" | "verpflegung_ueber_8_stunden" | "kuerzung_fruehstueck_prozent" | "kuerzung_mittag_abend_prozent" | "uebernachtung_pauschale_arbeitgeber" | "uebernachtung_berufskraftfahrer" | "kilometer_pkw" | "kilometer_andere_kfz" } = {})Reisekosten
qk.hr.sfnZuschlaege({ datum?: string; grundlohn_stunde?: number; bestandteil?: "nachtarbeit" | "nachtarbeit_0_bis_4_uhr" | "sonntagsarbeit" | "feiertagsarbeit" | "weihnachten_und_1_mai" | "grundlohn_hoechstbetrag_steuer" | "grundlohn_hoechstbetrag_sv" } = {})SFN-Zuschläge
qk.hr.dienstwagen({ listenpreis: number; antrieb?: "verbrenner" | "elektro" | "hybrid"; anschaffung?: string; ueberlassung?: string; entfernung_km?: number; fahrten_monat?: number; zuzahlung_monat?: number; co2_g_km?: number; reichweite_km?: number; batterie_kwh?: number; datum?: string })Dienstwagen
qk.hr.einkommensteuerEckwerte({ jahr?: number; datum?: string; bestandteil?: "grundfreibetrag" | "spitzensteuersatz_ab" | "reichensteuer_ab" | "eingangssteuersatz" | "spitzensteuersatz" | "reichensteuersatz" | "kinderfreibetrag_je_elternteil" | "bea_freibetrag_je_elternteil" | "soli_freigrenze" | "soli_satz" } = {})Grundfreibetrag
qk.hr.kuendigungsfrist({ eintritt: string; zugang: string; seite?: "arbeitgeber" | "arbeitnehmer"; probezeit?: boolean })Kündigungsfrist
qk.hr.urlaubsanspruch({ arbeitstage_pro_woche: number; jahr?: number; eintritt?: string; austritt?: string })Urlaubsanspruch
qk.hr.mutterschutz({ termin?: string; geburt?: string; fall?: "standard" | "fruehgeburt" | "mehrlinge" | "behinderung" | "fehlgeburt"; ssw?: number } = {})Mutterschutz
qk.hr.feiertage({ jahr?: number; land?: "BW" | "BY" | "BE" | "BB" | "HB" | "HH" | "HE" | "MV" | "NI" | "NW" | "RP" | "SL" | "SN" | "ST" | "SH" | "TH" } = {})Feiertage
qk.hr.arbeitstage({ von: string; bis: string; land: "BW" | "BY" | "BE" | "BB" | "HB" | "HH" | "HE" | "MV" | "NI" | "NW" | "RP" | "SL" | "SN" | "ST" | "SH" | "TH"; samstag?: boolean; regionale?: boolean })Arbeitstage
qk.hr.regelaltersgrenze({ geburtsdatum?: string; geburtsjahr?: number; vertrauensschutz?: boolean } = {})Regelaltersgrenze
qk.hr.pausen({ arbeitszeit_stunden: number; jugendlich?: boolean })Pausenregelung
qk.hr.verlauf(datensatz)alle Stufen einer Tabelle seit 2015
qk.datensaetze()Katalog, ohne Schlüssel
qk.aenderungen()Änderungsprotokoll, ohne Schlüssel
qk.anfrage(pfad, werte, format)rohe Anfrage an jeden Pfad, auch für CSV

Beispiele

#

Kündigungsfrist

JavaScript
const frist = await qk.hr.kuendigungsfrist({ eintritt: "2017-03-01", zugang: "2026-11-10" });
console.log(frist.ende); // "2027-02-28"

Feiertage für mehrere Standorte

JavaScript
const laender = ["HE", "BY", "NW"] as const;
const ergebnisse = await Promise.all(laender.map((land) => qk.hr.feiertage({ jahr: 2027, land })));
for (const e of ergebnisse) console.log(e.land_name, e.anzahl);

Arbeitstage eines Monats

JavaScript
const januar = await qk.hr.arbeitstage({ von: "2027-01-01", bis: "2027-01-31", land: "HE" });
console.log(januar.arbeitstage, januar.feiertage_an_arbeitstagen.map((f) => f.name));

Verlauf in die eigene Datenbank

JavaScript
const { verlauf } = await qk.hr.verlauf("rechengroessen");
for (const stufe of verlauf) await speichere(stufe); // deine Funktion

Fehler behandeln

#

Antwortet die API mit einem Fehler, wirft das SDK einen QuellenkontorFehler mit status, code, parameter und der Meldung. Ohne Verbindung ist der Status 0 und der Code netzwerk.

JavaScript
import { Quellenkontor, QuellenkontorFehler } from "@quellenkontor/sdk";

try {
  const r = await qk.hr.rechengroessen({ jahr: 2030 });
} catch (e) {
  if (e instanceof QuellenkontorFehler && e.code === "kein_wert") {
    // Wert noch nicht verkündet: Hinweis anzeigen statt zu raten
  } else if (e instanceof QuellenkontorFehler && e.status === 429) {
    // Kontingent aufgebraucht
  } else {
    throw e;
  }
}

Alle Codes stehen unter Fehler.

CSV und rohe Anfragen

#

Mit anfrage erreichst du jeden Pfad der API. Als drittes Argument "csv" liefert sie den CSV-Text:

JavaScript
import { writeFile } from "node:fs/promises";

const csv = await qk.anfrage("/hr/feiertage", { jahr: 2027, land: "HE", trennzeichen: "semikolon" }, "csv");
await writeFile("feiertage-2027-he.csv", csv);

Einsatz in Next.js und Serverless

#

Ruf das SDK nur auf dem Server auf: in Server Components, Route Handlers oder Server Actions. Den Schlüssel hinterlegst du bei Vercel unter Environment Variables als QK_KEY.

app/api/feiertage/route.ts
import { Quellenkontor } from "@quellenkontor/sdk";

const qk = new Quellenkontor();

export async function GET(request: Request) {
  const land = new URL(request.url).searchParams.get("land") ?? "HE";
  const daten = await qk.hr.feiertage({ jahr: 2027, land: land as "HE" });
  return Response.json(daten, { headers: { "Cache-Control": "s-maxage=86400" } });
}

Der eigene Cache-Header im Beispiel speichert die Antwort einen Tag lang am Rand deines Hostings. So zählt nicht jeder Seitenaufruf gegen dein Kontingent. Mehr dazu unter Abfragen sparen.

Fehlt etwas oder ist etwas unklar? Schreib uns, wir ergänzen die Doku.