Anleitung · Schnittstelle (API)

Code-Beispiele: PHP, Python, JavaScript, PowerShell, curl

DSGVO Server & Hosting in DE BSI Allianz für Cybersicherheit BSI Allianz für Cybersicherheit ProHunde ProHunde-Kooperation BVZ Berufsverband zertifizierter Hundetrainer BVZ-Kooperation

Code-Beispiele: PHP, Python, JavaScript, PowerShell, curl

Hier findest du Programmbeispiele zum Lesen, Anlegen, Ändern und Löschen eines Kontakts. Lass sie bei Bedarf von der Person einrichten, die dein anderes Programm betreut.

Wer hat Zugriff?

  • Administratoren verwalten Zugänge unter System → API-Schlüssel und System → MCP.
  • Programme brauchen einen aktiven, gültigen Schlüssel. Seine Stufe und Bereichsfreigaben begrenzen den Zugriff. Ein zugeordneter Kontakt muss aktiv sein; seine Rechte können zusätzlich einschränken.
  • Schreibzugriffe brauchen zusätzlich Schreiben erlauben. Jeder Bereich hat eigene Lese- und Schreibrechte. Die Schnittstelle bietet nicht jede Funktion der Oberfläche an.
  • Die Schnittstelle selbst legt keine API-Schlüssel an. Details: Rollen und Rechte.

Welche E-Mails werden verschickt?

  • Lesen sowie Schlüssel anlegen, ändern oder sperren: keine E-Mails.
  • Spezielle Assistent-Aktionen für Termine, Kunden und Tiere: keine Kundenmail und kein Rechnungsversand. Termine entstehen ohne Benachrichtigung.
  • Andere Schreibaktionen: Beachte die Beschreibung des jeweiligen Bereichs. Eine Newsletter-Abmeldung kann beispielsweise eine Meldung an den Betreiber auslösen.

Kurz gesagt: Ersetze zuerst Beispieladresse und Beispielschlüssel. Die vollständigen Dateien findest du unter /api/beispiele/ deiner Instanz. Die Schreibbeispiele verändern echte Einträge in dieser Instanz.

Begriffe: API bedeutet Schnittstelle für andere Programme. Ein Endpunkt ist eine bestimmte Abrufadresse. JSON ist das Textformat der Antworten; eine Kennung (id) bezeichnet einen Eintrag.

In allen Beispielen sind zwei Werte einzutragen:

  • die Adresse, zum Beispiel https://deine-adresse.de/api/v1
  • der Schlüssel aus System → API-Schlüssel

So bereitest du ein Beispiel vor

  1. Lege einen passenden Schlüssel unter System → API-Schlüssel an.
  2. Ersetze im gewählten Beispiel Adresse und Schlüssel.
  3. Führe zuerst nur den Leseaufruf aus.
  4. Prüfe die Antwort unter data.
  5. Verwende für Schreibversuche ausschließlich eigene Testeinträge.

1. So rufst du Daten im Terminal ab

curl ist ein Programm für Web-Anfragen. Ersetze in den ersten beiden Zeilen Adresse und Schlüssel.

Der schnellste Weg zum Ausprobieren. Denk an -g, sobald Filter mit eckigen Klammern vorkommen.

Was macht das? Diese Terminalbefehle lesen Daten und legen einen Testkontakt an. Verwende beim Ändern und Löschen ausschließlich dessen zurückgegebene Kennung.

BASE="https://deine-adresse.de/api/v1"
KEY="DEIN-API-SCHLUESSEL"

# Was darf mein Schlüssel?
curl -sg -H "Authorization: Bearer $KEY" "$BASE/me"

# Die ersten fünf Kontakte, nach Nachname sortiert
curl -sg -H "Authorization: Bearer $KEY" \
     "$BASE/contacts?per_page=5&sort=lastname&fields=id,lastname,firstname,email"

# Filtern: Nachname enthält "Mül", angelegt seit 2026
curl -sg -H "Authorization: Bearer $KEY" \
     "$BASE/contacts?lastname[like]=M%C3%BCl&created[gte]=2026-01-01"

# Anlegen
curl -sg -X POST -H "Authorization: Bearer $KEY" \
     -H "Content-Type: application/json" \
     -H "Idempotency-Key: mein-einmaliger-test-001" \
     -d '{"lastname":"Müller","firstname":"Erika","email":"erika@example.de"}' \
     "$BASE/contacts"

# Trage hier ausschließlich data.id aus der eben erhaltenen Anlege-Antwort ein.
CONTACT_ID="KENNUNG-DES-NEUEN-TESTKONTAKTS"

# Ändern
curl -sg -X PATCH -H "Authorization: Bearer $KEY" \
     -H "Content-Type: application/json" \
     -d '{"city":"Neustadt"}' \
     "$BASE/contacts/$CONTACT_ID"

# Löschen (Papierkorb)
curl -sg -X DELETE -H "Authorization: Bearer $KEY" "$BASE/contacts/$CONTACT_ID"

Übernimm nach dem Anlegen die neue Kennung aus data.id in CONTACT_ID. Erst danach führst du Ändern und Löschen aus. Für einen neuen Anlegeversuch verwendest du einen neuen Idempotency-Key.

Mit jq wird die Ausgabe lesbar:

Was macht das? Diese Befehle zeigen die Antwort übersichtlich und lesen die Gesamtzahl der Kontakte aus.

curl -sg -H "Authorization: Bearer $KEY" "$BASE/contacts?per_page=3" | jq .

# Nur die Gesamtzahl
curl -sg -H "Authorization: Bearer $KEY" \
     "$BASE/contacts?per_page=1" | jq '.meta.total'

2. PHP

Was macht das? Dieses PHP-Beispiel liest und filtert Kontakte. Anschließend legt es einen Kontakt an, ändert ihn und löscht ihn.

<?php

$BASE = 'https://deine-adresse.de/api/v1';
$KEY  = '3f7c9a2e-4b81-4d6f-9a03-7c5e12f8ab44';

function api(string $method, string $path,
             array $query = [], ?array $body = null): array
{
    global $BASE, $KEY;

    $url = $BASE . $path;
    if ($query) {
        // http_build_query kodiert auch verschachtelte Filter richtig:
        // ['lastname' => ['like' => 'Mül']]  ->  lastname%5Blike%5D=M%C3%BCl
        $url .= '?' . http_build_query($query);
    }

    $headers = ['Authorization: Bearer ' . $KEY, 'Accept: application/json'];
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_TIMEOUT        => 30,
    ]);
    if ($body !== null) {
        $headers[] = 'Content-Type: application/json';
        curl_setopt($ch, CURLOPT_POSTFIELDS,
            json_encode($body, JSON_UNESCAPED_UNICODE));
    }
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    $raw    = curl_exec($ch);
    $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $data   = json_decode((string) $raw, true) ?: [];

    if ($status >= 400) {
        throw new RuntimeException(sprintf('[%d %s] %s',
            $status,
            $data['error']['code']    ?? 'unknown',
            $data['error']['message'] ?? 'Unbekannter Fehler'));
    }
    return $data;
}

// Lesen
$result = api('GET', '/contacts', [
    'per_page' => 5,
    'sort'     => 'lastname',
    'fields'   => 'id,lastname,firstname,email',
]);
foreach ($result['data'] as $contact) {
    printf("#%d  %s, %s\n", $contact['id'],
           $contact['lastname'], $contact['firstname']);
}

// Filtern
$treffer = api('GET', '/contacts', [
    'lastname' => ['like' => 'Mül'],
    'created'  => ['gte'  => '2026-01-01'],
]);
printf("%d Treffer\n", $treffer['meta']['total']);

// Anlegen
$neu = api('POST', '/contacts', [], [
    'lastname'  => 'Müller',
    'firstname' => 'Erika',
    'email'     => 'erika.mueller@example.de',
]);
$id = $neu['data']['id'];

// Ändern und löschen
api('PATCH', '/contacts/' . $id, [], ['city' => 'Neustadt']);
api('DELETE', '/contacts/' . $id);

3. Python

Ohne Fremdbibliotheken — nur die Standardbibliothek.

Was macht das? Dieses Python-Beispiel liest und filtert Kontakte. Anschließend legt es einen Kontakt an, ändert ihn und löscht ihn.

#!/usr/bin/env python3
import json
import urllib.parse
import urllib.request

BASE = "https://deine-adresse.de/api/v1"
KEY  = "3f7c9a2e-4b81-4d6f-9a03-7c5e12f8ab44"


def encode_query(query):
    """Verschachtelte Filter zur Klammerschreibweise der Schnittstelle."""
    parts = []
    for key, value in query.items():
        if isinstance(value, dict):
            for op, operand in value.items():
                parts.append((f"{key}[{op}]", str(operand)))
        else:
            parts.append((key, str(value)))
    return urllib.parse.urlencode(parts)


def api(method, path, query=None, body=None):
    url = BASE + path
    if query:
        url += "?" + encode_query(query)

    headers = {"Authorization": f"Bearer {KEY}", "Accept": "application/json"}
    data = None
    if body is not None:
        data = json.dumps(body, ensure_ascii=False).encode("utf-8")
        headers["Content-Type"] = "application/json"

    request = urllib.request.Request(url, data=data,
                                     headers=headers, method=method)
    try:
        with urllib.request.urlopen(request, timeout=30) as response:
            raw = response.read().decode("utf-8")
            return json.loads(raw) if raw else {}
    except urllib.error.HTTPError as exc:
        error = json.loads(exc.read().decode("utf-8")).get("error", {})
        raise RuntimeError(
            f"[{exc.code} {error.get('code')}] {error.get('message')}"
        ) from None


# Lesen
result = api("GET", "/contacts", {
    "per_page": 5,
    "sort": "lastname",
    "fields": "id,lastname,firstname,email",
})
for contact in result["data"]:
    print(f"#{contact['id']}  {contact['lastname']}, {contact['firstname']}")

# Filtern
treffer = api("GET", "/contacts", {
    "lastname": {"like": "Mül"},
    "created":  {"gte": "2026-01-01"},
})
print(f"{treffer['meta']['total']} Treffer")

# Anlegen, ändern, löschen
neu = api("POST", "/contacts", body={
    "lastname": "Müller",
    "firstname": "Erika",
    "email": "erika.mueller@example.de",
})
record_id = neu["data"]["id"]
api("PATCH", f"/contacts/{record_id}", body={"city": "Neustadt"})
api("DELETE", f"/contacts/{record_id}")


# Alle Einträge durchlaufen — Seite für Seite, speicherschonend
def alle(resource, **query):
    page = 1
    while True:
        result = api("GET", f"/{resource}",
                     {**query, "page": page, "per_page": 200})
        for row in result["data"]:
            yield row
        if page >= result["meta"]["pages"]:
            return
        page += 1


for kontakt in alle("contacts", fields="id,lastname"):
    pass  # hier verarbeiten

4. JavaScript (Node.js)

Braucht Node 18 oder neuer — dort ist fetch eingebaut.

Was macht das? Dieses JavaScript-Beispiel liest und filtert Kontakte. Anschließend legt es einen Kontakt an, ändert ihn und löscht ihn.

const BASE = 'https://deine-adresse.de/api/v1';
const KEY  = '3f7c9a2e-4b81-4d6f-9a03-7c5e12f8ab44';

function buildQuery(query) {
    const params = new URLSearchParams();
    for (const [key, value] of Object.entries(query)) {
        if (value === null || value === undefined) continue;
        if (typeof value === 'object' && !Array.isArray(value)) {
            // Verschachtelte Filter: { lastname: { like: 'Mül' } }
            for (const [op, operand] of Object.entries(value)) {
                params.append(`${key}[${op}]`, String(operand));
            }
        } else {
            params.append(key, String(value));
        }
    }
    const s = params.toString();
    return s ? `?${s}` : '';
}

async function api(method, path, { query = {}, body = null } = {}) {
    const headers = { Authorization: `Bearer ${KEY}`, Accept: 'application/json' };
    const init = { method, headers };

    if (body !== null) {
        headers['Content-Type'] = 'application/json';
        init.body = JSON.stringify(body);
    }

    const response = await fetch(BASE + path + buildQuery(query), init);
    const text = await response.text();
    const data = text ? JSON.parse(text) : {};

    if (!response.ok) {
        const e = data.error ?? {};
        throw new Error(`[${response.status} ${e.code}] ${e.message}`);
    }
    return data;
}

// Lesen
const result = await api('GET', '/contacts', {
    query: { per_page: 5, sort: 'lastname',
             fields: 'id,lastname,firstname,email' },
});
for (const c of result.data) {
    console.log(`#${c.id}  ${c.lastname}, ${c.firstname}`);
}

// Filtern
const treffer = await api('GET', '/contacts', {
    query: { lastname: { like: 'Mül' }, created: { gte: '2026-01-01' } },
});
console.log(`${treffer.meta.total} Treffer`);

// Anlegen, ändern, löschen
const neu = await api('POST', '/contacts', {
    body: { lastname: 'Müller', firstname: 'Erika',
            email: 'erika.mueller@example.de' },
});
const id = neu.data.id;
await api('PATCH', `/contacts/${id}`, { body: { city: 'Neustadt' } });
await api('DELETE', `/contacts/${id}`);

Im Browser läuft derselbe Code — dann muss allerdings deine Adresse unter api.cors.origins freigegeben sein, und der Schlüssel stünde im Seitenquelltext. Lass eine Website-Anbindung deshalb über deinen eigenen Server ausführen. Die Schnittstelle erlaubt fremde Browser-Adressen nur nach entsprechender Freigabe.

5. PowerShell

Läuft unter Windows PowerShell 5.1 und unter PowerShell 7.

Was macht das? Dieses PowerShell-Beispiel liest und filtert Kontakte. Anschließend legt es einen Kontakt an, ändert ihn und löscht ihn.

$BaseUrl = 'https://deine-adresse.de/api/v1'
$ApiKey  = '3f7c9a2e-4b81-4d6f-9a03-7c5e12f8ab44'

# Windows PowerShell 5.1 spricht standardmäßig noch TLS 1.0 — hier umstellen
if ($PSVersionTable.PSVersion.Major -lt 6) {
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
}

function ConvertTo-PwQuery {
    param([hashtable] $Query)
    if (-not $Query -or $Query.Count -eq 0) { return '' }

    $parts = @()
    foreach ($key in $Query.Keys) {
        $value = $Query[$key]
        if ($value -is [hashtable]) {
            foreach ($op in $value.Keys) {
                $name = [System.Uri]::EscapeDataString("$key[$op]")
                $wert = [System.Uri]::EscapeDataString([string]$value[$op])
                $parts += "$name=$wert"
            }
        } else {
            $name = [System.Uri]::EscapeDataString($key)
            $wert = [System.Uri]::EscapeDataString([string]$value)
            $parts += "$name=$wert"
        }
    }
    return '?' + ($parts -join '&')
}

function Invoke-PwApi {
    param(
        [Parameter(Mandatory)][string] $Path,
        [string]    $Method = 'GET',
        [hashtable] $Query  = @{},
        [object]    $Body   = $null
    )

    $params = @{
        Uri         = $script:BaseUrl.TrimEnd('/') + $Path +
                      (ConvertTo-PwQuery -Query $Query)
        Method      = $Method
        Headers     = @{
            'Authorization' = "Bearer $script:ApiKey"
            'Accept'        = 'application/json'
        }
        TimeoutSec  = 30
        ErrorAction = 'Stop'
    }

    if ($null -ne $Body) {
        # Die Umwandlung nach UTF-8 ist nötig, sonst kommen Umlaute falsch an.
        $json = $Body | ConvertTo-Json -Depth 10 -Compress
        $params['Body']        = [Text.Encoding]::UTF8.GetBytes($json)
        $params['ContentType'] = 'application/json; charset=utf-8'
    }

    Invoke-RestMethod @params
}

# Lesen
$result = Invoke-PwApi -Path '/contacts' -Query @{
    per_page = 5; sort = 'lastname'; fields = 'id,lastname,firstname,email'
}
$result.data | Format-Table -AutoSize id, lastname, firstname, email

# Filtern
$treffer = Invoke-PwApi -Path '/contacts' -Query @{
    lastname = @{ like = 'Mül' }
    created  = @{ gte  = '2026-01-01' }
}
Write-Host "$($treffer.meta.total) Treffer"

# Anlegen, ändern, löschen
$neu = Invoke-PwApi -Path '/contacts' -Method POST -Body @{
    lastname = 'Müller'; firstname = 'Erika'; email = 'erika.mueller@example.de'
}
$id = $neu.data.id
Invoke-PwApi -Path "/contacts/$id" -Method PATCH `
             -Body @{ city = 'Neustadt' } | Out-Null
Invoke-PwApi -Path "/contacts/$id" -Method DELETE | Out-Null

Variablennamen in PowerShell unterscheiden nicht zwischen Groß- und Kleinschreibung. Eine Variable $raw ist dieselbe wie ein Schalter -Raw. Verwende für Inhalt und Schalter eindeutig verschiedene Namen; im vollständigen Beispiel ist das so gelöst.

6. Und in anderen Sprachen?

Die Schnittstelle ist gewöhnliches REST mit JSON. Jede Sprache, die HTTP sprechen kann, kommt damit klar — Java, C#, Go, Ruby, Rust, Swift, ABAP.

Am schnellsten geht es über die Beschreibungsdatei:

Was macht das? Dieser Befehl speichert die Schnittstellenbeschreibung als Datei für andere Entwicklungswerkzeuge.

curl -sg -H "Authorization: Bearer $KEY" "$BASE/openapi.json" -o petworkers.json

Daraus erzeugen die üblichen Werkzeuge (openapi-generator, NSwag, oapi-codegen) einen fertigen Client in der Sprache deiner Wahl.

Die vollständigen Fassungen findest du unter https://deine-adresse.de/api/beispiele/, auch als ZIP-Paket. Ersetze auch dort die Beispieladresse.

Wenn etwas nicht klappt

  • 401 unauthorized: Ersetze den Beispielschlüssel überall durch deinen gültigen Schlüssel.
  • 403 forbidden beim Anlegen: Aktiviere Schreiben erlauben und prüfe das Schreibrecht für contacts.
  • Eine Liste endet nach der ersten Seite: Die kurzen Beispiele zeigen nur eine Seite. Ergänze den auf der Leseseite erklärten Seitenabruf.
  • curl lehnt Filter ab: Verwende -g gegen die Klammerauswertung. Kodiere außerdem Leerzeichen und Sonderzeichen in der URL.

Zeit für den Wechsel

Frage noch offen?

Wenn dir in der Anleitung etwas fehlt — schreib uns. Wir nehmen Themen aus Support-Tickets oft sofort als neue Anleitung auf.

DSGVO-konform Server in Deutschland Persönlicher Support