ANLEITUNG

In sechs Schritten angebunden

Diese Anleitung führt von der Key-Erstellung bis zur produktionsreifen Synchronisation des Dienstplans. Alle Codebeispiele lassen sich direkt kopieren.

1. API-Key erzeugen

Der API-Key wird im VetTime-Konto des Mandanten erzeugt und ist ausschließlich für diesen Mandanten gültig. Keys beginnen mit vt_live_. Falls ihr noch keinen Zugang habt, meldet euch über das Kontaktformular.

Legt den Key als Umgebungsvariable in eurem Backend ab, niemals im Frontend-Bundle oder in der Versionskontrolle.

2. Mitarbeiter-Mapping in VetTime hinterlegen

Die API übergibt Mitarbeitende über employee.external_id. Dafür muss im VetTime-Konto des Mandanten pro Mitarbeitendem eine Zuordnung von der internen VetTime-ID zur ID im angebundenen System (z. B. PMS-Benutzer-ID) gepflegt werden.

Ohne dieses Mapping ist external_id null. Ein Abgleich über Namen ist nicht erlaubt, da VetTime interne IDs nie ausliefert.

3. Ersten Request senden

Ein minimaler Aufruf braucht nur from und to. Beide Daten sind inklusiv und beziehen sich auf Europe/Berlin.

curl
export VETTIME_API_KEY='vt_live_...'

curl --fail-with-body \
  --get 'https://app.vettime.de/api/public/v1/shifts' \
  --header "Authorization: Bearer ${VETTIME_API_KEY}" \
  --data-urlencode 'from=2026-08-17' \
  --data-urlencode 'to=2026-08-31'

4. Paginierung über den Cursor

Solange meta.has_more den Wert true hat, ruft ihr denselben Endpunkt mit meta.next_cursor auf. Alle übrigen Parameter bleiben unverändert, der Cursor ist undurchsichtig.

JavaScript
async function* fetchAllShifts(from, to, apiKey) {
  let cursor = null;

  do {
    const url = new URL('https://app.vettime.de/api/public/v1/shifts');
    url.searchParams.set('from', from);
    url.searchParams.set('to', to);
    url.searchParams.set('limit', '250');
    if (cursor) url.searchParams.set('cursor', cursor);

    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });
    if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);

    const body = await res.json();
    yield* body.data;
    cursor = body.meta.has_more ? body.meta.next_cursor : null;
  } while (cursor);
}

5. Synchronisation als Snapshot

Die API ist kein Event-Stream. Sammelt den vollständigen Zeitraum ein und schreibt erst danach atomar. Da Zuweisungs-IDs nach erneutem Veröffentlichen wechseln können, braucht es regelmäßig einen vollständigen Abgleich statt reiner Deltas.

Sync-Strategie
// 1. Vollständigen Snapshot einsammeln
const snapshot = [];
for await (const shift of fetchAllShifts(from, to, apiKey)) {
  snapshot.push(shift);
}

// 2. Erst nach dem letzten Seitenabruf schreiben
await db.transaction(async (tx) => {
  const ids = snapshot.map((s) => s.id);

  await tx.upsertShifts(snapshot);

  // 3. Zuvor importierte Zuweisungen im Zeitraum entfernen,
  //    die im aktuellen Snapshot fehlen
  await tx.deleteShiftsInRangeExcept(from, to, ids);
});

6. Fehler robust behandeln

Bei 429 gilt Retry-After. Bei 5xx wird mit exponentiellem Backoff plus Jitter wiederholt. 400 und 401 sind nicht wiederholbar und gehören ins Log samt X-Request-Id.

Retry-Logik
async function requestWithRetry(url, apiKey, attempt = 0) {
  const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });

  if (res.status === 429) {
    const retryAfter = Number(res.headers.get('Retry-After') ?? 1);
    await sleep(retryAfter * 1000);
    return requestWithRetry(url, apiKey, attempt);
  }

  if (res.status >= 500 && attempt < 5) {
    const backoff = 2 ** attempt * 500 + Math.random() * 250; // Jitter
    await sleep(backoff);
    return requestWithRetry(url, apiKey, attempt + 1);
  }

  if (!res.ok) {
    const requestId = res.headers.get('X-Request-Id');
    const body = await res.text();
    throw new Error(`VetTime API ${res.status} (request ${requestId}): ${body}`);
  }

  return res.json();
}

7. Keys sicher betreiben

Keys niemals in URLs, Browser- oder Mobile-Code, Logs, E-Mails oder Chats übergeben. Rotiert Keys bei Personalwechsel und bei Verdacht auf Kompromittierung, ein ersetzter Key ist sofort ungültig. Beim Support-Kontakt nur X-Request-Id und UTC-Zeitpunkt nennen, nie den Key selbst.

Alle Details im Referenzteil

Vollständiges Antwortschema, Feldsemantik, Fehlercodes und Limits.

Zur API-Referenz