Rezepte
Fertige Abläufe für die Aufgaben, für die die Schnittstelle in der Praxis gebaut wurde. Alle Beispiele setzen zwei Variablen voraus:
BASIS="https://deine-domain.de/api/v1"
KEY="mv_a1b2c3d4_…"
1. Kommende Veranstaltungen auf der Website zeigen
Bereiche: veranstaltungen:read
curl -H "Authorization: Bearer $KEY" \
"$BASIS/veranstaltungen?sort=start_at&per_page=10"
Für die Detailseite dann GET /veranstaltungen/{id} – die Antwort enthält zusätzlich
Gruppen, Preisstufen und Leitung.
Der Schlüssel gehört auf den Server der Website, nicht in deren JavaScript. Dort stünde er für jeden lesbar im Quelltext.
2. Anmeldung von der Website zurückschreiben
Bereiche: veranstaltungen:write
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"first_name": "Maja",
"last_name": "Beispiel",
"email": "maja@example.org",
"birth_date": "2015-04-02",
"event_group_id": "<uuid>",
"answers": { "zeltwunsch": "mit Lea" }
}' \
"$BASIS/veranstaltungen/<event-id>/teilnehmer"
Der Status ist danach registered – bestätigt wird in der Anwendung oder ausdrücklich:
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"status":"confirmed"}' \
"$BASIS/veranstaltungen/<event-id>/teilnehmer/<tid>/status"
Und wenn das Geld da ist:
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"amount":95,"method":"manual","reference":"Überweisung 12.07."}' \
"$BASIS/veranstaltungen/<event-id>/teilnehmer/<tid>/zahlungen"
Die Antwort ist die neu berechnete Anmeldung samt Zahlungsstand.
3. Interessenten in die Warteliste eintragen
Bereiche: warteliste:write
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"group_id": "<uuid der Stufe>",
"first_name": "Jonas",
"last_name": "Beispiel",
"birth_date": "2018-09-12",
"contact_name": "Familie Beispiel",
"email": "familie@example.org"
}' \
"$BASIS/warteliste"
Ohne waitlist_date gilt der heutige Tag – und der bestimmt die Reihenfolge auf der Liste.
4. Inkrementeller Abgleich der Mitglieder
Bereiche: mitglieder:read
Das Werkzeug merkt sich den Zeitpunkt seines letzten Laufs und holt beim nächsten Mal nur noch die Änderungen. Wichtig ist, alle Seiten zu holen:
SEIT="2026-07-01T00:00:00Z"
SEITE=1
while : ; do
ANTWORT=$(curl -s -H "Authorization: Bearer $KEY" \
"$BASIS/mitglieder?updated_since=$SEIT&per_page=200&page=$SEITE")
echo "$ANTWORT" | jq -c '.data[]'
GESAMT=$(echo "$ANTWORT" | jq '.meta.total')
if [ $((SEITE * 200)) -ge "$GESAMT" ]; then break; fi
SEITE=$((SEITE + 1))
done
meta.total ist die Gesamtzahl über alle Seiten – daraus ergibt sich das Abbruchkriterium.
Sonst fehlen nach einem Abbruch genau die Änderungen, die zwischen dem letzten verarbeiteten Eintrag und dem Fehler lagen.
5. Ist der Bulli am Wochenende frei?
Bereiche: verleih:read, zum Buchen verleih:write
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"start_date":"2026-08-14","end_date":"2026-08-16",
"items":[{"vehicle_id":"<uuid>","quantity":1}]}' \
"$BASIS/verleih/pruefen"
{ "data": { "frei": true, "konflikte": [] } }
Dann buchen – dieselbe Prüfung läuft dabei noch einmal, deshalb ist ein 409 not_available
auch nach einem freien Ergebnis möglich, wenn jemand schneller war:
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"borrower_name":"Maja Beispiel","start_date":"2026-08-14","end_date":"2026-08-16",
"purpose":"Materialtransport","items":[{"vehicle_id":"<uuid>","quantity":1}]}' \
"$BASIS/verleih"
Rückgabe mit Zustandsprotokoll:
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"aktion":"zurueck","items":[{"item_id":"<uuid>","returned_ok":false,"damage_note":"Delle hinten links"}]}' \
"$BASIS/verleih/<id>/status"
6. Zuwendungsbestätigung ausstellen
Bereiche: spenden:write, für die Ablage zusätzlich bank:write
Erst prüfen, ob die Aussteller-Angaben vollständig sind:
curl -s -H "Authorization: Bearer $KEY" "$BASIS/spenden/einstellungen" | jq '.meta.missing'
Dann ausstellen, PDF holen und als Beleg ablegen:
ID=$(curl -s -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"donor_name":"Maja Beispiel","amount":50,"donation_date":"2026-07-15"}' \
"$BASIS/spenden" | jq -r .data.id)
curl -H "Authorization: Bearer $KEY" -o bestaetigung.pdf "$BASIS/spenden/$ID/pdf"
curl -X POST -H "Authorization: Bearer $KEY" "$BASIS/spenden/$ID/als-beleg"
Eine ausgestellte Bestätigung trägt eine fortlaufende Nummer. Sie lässt sich nur
stornieren (POST /spenden/{id}/storno mit grund), nicht löschen.
7. Belege aus einem Scan-Ordner nachreichen
Bereiche: bank:write
for datei in ~/scans/*.pdf; do
ID=$(curl -s -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d "{\"description\":\"$(basename "$datei" .pdf)\",\"receipt_date\":\"2026-08-01\",\"amount\":0,\"direction\":\"expense\"}" \
"$BASIS/bank/belege" | jq -r .data.id)
curl -X PUT -H "Authorization: Bearer $KEY" -H "Content-Type: application/pdf" \
--data-binary @"$datei" \
"$BASIS/bank/belege/$ID/inhalt?name=$(basename "$datei")"
done
Der Rumpf ist der rohe Dateiinhalt – kein JSON, kein multipart. Bis 20 MB je Beleg.
8. Serienmail in zwei Schritten
Bereiche: mails:write
# 1. Entwurf mit Empfängern
ID=$(curl -s -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"subject":"Sommerlager 2026",
"body_html":"<p>Hallo {{vorname}},</p><p>die Anmeldung ist offen.</p>",
"targets":[{"target_type":"group","target_id":"<uuid>"}]}' \
"$BASIS/mails" | jq -r .data.id)
# 2. Anhang (optional)
curl -X PUT -H "Authorization: Bearer $KEY" -H "Content-Type: application/pdf" \
--data-binary @programm.pdf "$BASIS/mails/$ID/anhaenge?name=programm.pdf"
# 3. Senden – der ausdrückliche zweite Schritt
curl -X POST -H "Authorization: Bearer $KEY" "$BASIS/mails/$ID/senden"
Zwischen Schritt 1 und 3 lässt sich der Entwurf in der Anwendung ansehen. Nach dem Senden ist die Nachricht unveränderlich – auch ihre Anhänge.
9. Wöchentliche Kontrolle: Wer hat an den Bankdaten gearbeitet?
Bereiche: stammdaten:read
curl -H "Authorization: Bearer $KEY" \
"$BASIS/stammdaten/protokoll?objekt_art=bank_transaction&updated_since=2026-08-13T00:00:00Z&per_page=200"
updated_since heißt hier seither angelegt. Schreibende Zugriffe über die
Schnittstelle stehen dort unter api:<Name des Schlüssels>.
10. Ein kleiner Client in JavaScript
Ein Rumpf, der Fehler, Blättern und Drosselung berücksichtigt:
const BASIS = process.env.MV_BASIS; // https://…/api/v1
const KEY = process.env.MV_KEY;
async function api(pfad, optionen = {}) {
const antwort = await fetch(`${BASIS}${pfad}`, {
...optionen,
headers: {
Authorization: `Bearer ${KEY}`,
'Content-Type': 'application/json',
...optionen.headers
}
});
if (antwort.status === 429) {
const warten = Number(antwort.headers.get('RateLimit-Reset') || 60);
await new Promise((r) => setTimeout(r, warten * 1000));
return api(pfad, optionen);
}
const körper = await antwort.json();
if (!antwort.ok) {
const fehler = new Error(körper?.error?.message || antwort.statusText);
fehler.code = körper?.error?.code;
fehler.details = körper?.error?.details;
throw fehler;
}
return körper;
}
/** Holt alle Seiten einer Liste. */
async function alle(pfad, proSeite = 200) {
const trenner = pfad.includes('?') ? '&' : '?';
const ergebnis = [];
for (let seite = 1; ; seite += 1) {
const { data, meta } = await api(`${pfad}${trenner}per_page=${proSeite}&page=${seite}`);
ergebnis.push(...data);
if (!meta?.total || ergebnis.length >= meta.total || data.length === 0) break;
}
return ergebnis;
}
const mitglieder = await alle('/mitglieder?nur_aktive=true');
console.log(`${mitglieder.length} aktive Mitglieder`);
11. In Postman oder Insomnia importieren
curl -H "Authorization: Bearer $KEY" "$BASIS/openapi.json" -o openapi.json
Anschließend die Datei importieren. Ein Import per URL funktioniert nur, wenn sich
dabei ein Authorization-Kopf mitgeben lässt – die Beschreibung ist nicht offen
zugänglich.
Checkliste vor dem Produktivgang
- Eigener Schlüssel je angebundenem Werkzeug – nicht ein Schlüssel für alles.
- Nur die wirklich nötigen Bereiche, und
:readwo Lesen genügt. - Die vereinsweite Obergrenze passend gesetzt (Einstellungen → API → Bereiche).
-
GET /mein der Anbindung als Selbsttest, mit Prüfung vonscopes. - Ablaufdatum notiert; die Erinnerungsmails gehen 14 und 3 Tage vorher raus.
- Der Schlüssel liegt in einer Umgebungsvariable, nicht im Quelltext und nicht im Browser.
-
429wird abgewartet, nicht sofort wiederholt. - Unbekannte Felder in Antworten werden ignoriert – es können welche hinzukommen.
- Beim Abgleich: Zeitstempel erst nach erfolgreichem Lauf speichern.
Hat dies deine Frage beantwortet?