Zum Hauptinhalt springen

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.

Nicht aus dem Browser

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 Abbruch­kriterium.

Den Zeitstempel erst nach erfolgreichem Lauf speichern

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"
Kein Rückwärtsgang

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 :read wo Lesen genügt.
  • Die vereinsweite Obergrenze passend gesetzt (Einstellungen → API → Bereiche).
  • GET /me in der Anbindung als Selbsttest, mit Prüfung von scopes.
  • 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.
  • 429 wird 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?