Access methods

SDK for JavaScript and TypeScript

The official package for Node.js 18 and later. No dependencies, with types for every response.

Installation

#

The package @quellenkontor/sdk runs on Node.js 18 and later. It only uses fetch, not any Node modules, has no dependencies and ships with types for TypeScript.

npm
npm install @quellenkontor/sdk
pnpm, yarn, bun
pnpm add @quellenkontor/sdk
yarn add @quellenkontor/sdk
bun add @quellenkontor/sdk
The package is an ES module. In older CommonJS projects, load it with const { Quellenkontor } = await import("@quellenkontor/sdk").

Setup

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

// reads the key from QK_KEY
const qk = new Quellenkontor();

// or explicitly, with options
const qk2 = new Quellenkontor({
  apiKey: process.env.QK_KEY,
  timeoutMs: 10_000,
  wiederholungen: 3,
});
OptionDefaultMeaning
apiKeyEnvironment variable QK_KEYYour API key. You can also pass it as the first argument: new Quellenkontor(schluessel)
basisUrlhttps://api.quellenkontor.dev/v1Only for testing against a different server
timeoutMs15000Timeout per request, in milliseconds
wiederholungen2Retries on network errors and status 502, 503, 504, with 300 and 600 millisecond delays
fetchglobal fetchYour own fetch function, for example for proxies or tests
cachetrueTurn the in-memory cache for this instance on or off, see below
cacheTtlMs300000 (5 minutes)How long a response comes from the cache without a new request
offlinetrueUse the bundled offline data as a fallback when the API is unreachable, see below

Cache and ETag

Within cacheTtlMs, a repeated request with the same parameters comes back from this instance's cache without any network call. After that, the SDK asks again with the stored ETag in the If-None-Match header: if the value has not changed, the API responds with 304, the SDK returns the cached response, and the request does not count against your quota. More on saving requests under Quotas and limits.

JavaScript
const qk = new Quellenkontor({ cache: false }); // always a fresh request

// or clear the cache on purpose, for example after a webhook
const qk2 = new Quellenkontor();
qk2.cacheLeeren();

Offline data

If the API is unreachable after every retry, the SDK falls back to bundled offline data: the history of every table with a history, generated into the package from the current data set (the same data as /export). That response then carries offline: true and the package's own datenstand instead of the currently official one, without the computed extra fields such as zitat. Plain calculators without a table, for example notice period, vacation entitlement or company car, have no offline data and stay a QuellenkontorFehler with code netzwerk without a connection.

JavaScript
const qk = new Quellenkontor({ offline: false }); // always throw the network error instead

// with the offline fallback (default): recognizable by the offline field
const qkMitFallback = new Quellenkontor();
const ml = await qkMitFallback.hr.mindestlohn({ datum: "2027-01-15" });
if (ml.offline) console.warn("Offline data from", ml.datenstand);

All methods

#

Every dataset is a method under qk.hr. It returns a Promise for the same object the API returns. The fields are listed in the reference.

MethodReference
qk.hr.mindestlohn({ datum?: string } = {})Minimum wage
qk.hr.mindestausbildungsverguetung({ beginn?: string; ausbildungsjahr?: number; bestandteil?: "ausbildungsjahr_1" | "ausbildungsjahr_2" | "ausbildungsjahr_3" | "ausbildungsjahr_4" } = {})Apprentice pay
qk.hr.pflegemindestlohn({ datum?: string; bestandteil?: "pflegehilfskraft" | "pflegehilfskraft_ost" | "qualifizierte_pflegehilfskraft" | "qualifizierte_pflegehilfskraft_ost" | "pflegefachkraft" | "zusatzurlaub_tage" } = {})Care minimum wage
qk.hr.rechengroessen({ jahr?: number; datum?: string } = {})Contribution ceilings
qk.hr.beitragssaetze({ datum?: string } = {})Contribution rates
qk.hr.sachbezugswerte({ jahr?: number; datum?: string } = {})Meal and lodging values
qk.hr.pfaendungsfreigrenzen({ datum?: string; unterhaltspflichten?: number; netto?: number } = {})Garnishment exemptions
qk.hr.uebergangsbereich({ datum?: string } = {})Midi-job zone
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" } = {})Mini-job levies
qk.hr.kuenstlersozialabgabe({ jahr?: number; datum?: string; bestandteil?: "abgabesatz" } = {})Artists' social levy
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" } = {})Compensatory levy
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" } = {})Tax-free amounts
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" } = {})Travel expenses
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" } = {})Night and holiday premiums
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 })Company car
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" } = {})Basic tax allowance
qk.hr.kuendigungsfrist({ eintritt: string; zugang: string; seite?: "arbeitgeber" | "arbeitnehmer"; probezeit?: boolean })Notice period
qk.hr.urlaubsanspruch({ arbeitstage_pro_woche: number; jahr?: number; eintritt?: string; austritt?: string })Vacation entitlement
qk.hr.mutterschutz({ termin?: string; geburt?: string; fall?: "standard" | "fruehgeburt" | "mehrlinge" | "behinderung" | "fehlgeburt"; ssw?: number } = {})Maternity protection
qk.hr.feiertage({ jahr?: number; land?: "BW" | "BY" | "BE" | "BB" | "HB" | "HH" | "HE" | "MV" | "NI" | "NW" | "RP" | "SL" | "SN" | "ST" | "SH" | "TH" } = {})Public holidays
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 })Working days
qk.hr.regelaltersgrenze({ geburtsdatum?: string; geburtsjahr?: number; vertrauensschutz?: boolean } = {})Retirement age
qk.hr.pausen({ arbeitszeit_stunden: number; jugendlich?: boolean })Rest breaks
qk.hr.verlauf(datensatz)all steps of a table since 2015
qk.datensaetze()Catalog, no key needed
qk.aenderungen()Changelog, no key needed
qk.anfrage(pfad, werte, format)raw request to any path, including CSV

Examples

#

Notice period (Kündigungsfrist)

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

Public holidays for multiple locations

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

Working days in a month

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

History into your own database

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

Handling errors

#

If the API responds with an error, the SDK throws a QuellenkontorFehler with status, code, parameter and the message. Without a connection, the status is 0 and the code is 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") {
    // Value not promulgated yet: show a notice instead of guessing
  } else if (e instanceof QuellenkontorFehler && e.status === 429) {
    // Quota used up
  } else {
    throw e;
  }
}

All codes are listed under Errors.

CSV and raw requests

#

With anfrage you can reach any path of the API. With "csv" as the third argument, it returns the 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);

Using it in Next.js and serverless

#

Only call the SDK on the server: in Server Components, Route Handlers or Server Actions. Store the key on Vercel under Environment Variables as 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" } });
}

The custom cache header in the example stores the response for one day at the edge of your hosting, so not every page view counts against your quota. More on this under Saving requests.

Something missing or unclear? Write to us and we will extend the docs.