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. Ersten Request senden
Ein minimaler Aufruf braucht nur from und to. Beide Daten sind inklusiv und beziehen sich auf Europe/Berlin.
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'3. 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.
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);
}4. 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.
// 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);
});5. 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.
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();
}6. 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.
Vollständiges Antwortschema, Feldsemantik, Fehlercodes und Limits.
Zur API-Referenz