Arbeitsteilung: Die Oberfläche ist für Administratoren, die einen Einzelfall geradeziehen. Die API ist für Techniker und angeschlossene Systeme, die dasselbe im Durchlauf erledigen. Beide sprechen dieselbe Datenbank, es gibt keinen zweiten Zustand.
Alle Beispiele auf dieser Seite stammen aus einer laufenden Instanz mit Beispieldaten,
erzeugt von tools/demo_pages.py. Sie sind nicht abgetippt.
1. Schlüssel ausstellen
Im Portal unter API. Ein Schlüssel hat einen Bereich, und der entscheidet, was mit ihm geht.
| Bereich | Erlaubt | Für wen |
|---|---|---|
read | Alle GET-Aufrufe | Systeme, die nur Zahlen abholen |
write | Zusätzlich POST, PATCH, DELETE | Onboarding, Cloud-Portal |
Übergeben wird er als Bearer-Token:
curl -H "Authorization: Bearer esm_xxxxxxxx_..." \
https://entra-portal.example.com/api/v1/
Die Wurzel listet Version, Instanz und alle Endpunkte mit ihrem Bereich. Sie eignet sich als erster Test, ob der Schlüssel funktioniert:
{
"version": "1.0",
"instance": "Entra Credential Portal",
"scope": "write",
"endpoints": [
{ "method": "GET", "path": "/api/v1/customers", "scope": "read", "purpose": "..." },
{ "method": "POST", "path": "/api/v1/customers", "scope": "write", "purpose": "..." },
{ "method": "GET", "path": "/api/v1/problems", "scope": "read", "purpose": "..." }
]
}
Die maschinenlesbare Beschreibung liegt unter /api/v1/openapi.json und
lässt sich direkt in einen Client-Generator geben.
2. Kunde anlegen
Ein POST mit Tenant-ID, Client-ID und dem Zugangsdatum. Der Schlüssel muss kleingeschrieben sein.
curl -X POST https://entra-portal.example.com/api/v1/customers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"key": "musterag",
"display_name": "Muster AG",
"tenant_id": "99999999-1111-2222-3333-444444444444",
"client_id": "88888888-5555-6666-7777-888888888888",
"auth_type": "secret",
"client_secret": "beispiel-secret-aus-der-app"
}'
Antwort 201 Created, gekürzt auf das Wesentliche:
{
"key": "musterag",
"display_name": "Muster AG",
"auth_type": "secret",
"has_credential": true,
"is_active": true,
"state": "unknown",
"scan": {
"status": "pending",
"last_check_at": null,
"slot_minute": 1200
},
"summary": { "count_total": 0, "count_critical": 0, "min_days": null },
"problems": [
{
"code": "never_checked",
"severity": "warn",
"message": "Dieser Kunde wurde noch nie erfolgreich geprüft.",
"action": "Eine Prüfung auslösen oder den nächsten Tageslauf abwarten."
}
]
}
Der Kunde ist angelegt, aber noch nie geprüft. slot_minute ist die
Tagesminute, zu der er künftig automatisch geprüft wird; das Portal verteilt neue Kunden
in die grösste Lücke.
Mit Zertifikat statt Secret
{
"key": "contoso",
"display_name": "Contoso AG",
"tenant_id": "8f3a1c22-9b41-4d7e-a512-6ce0f9b27d44",
"client_id": "2d9f4b18-77ac-4e63-9f1a-b58d3c07e912",
"auth_type": "certificate",
"cert_pem": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
"key_pem": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
}
Sofort prüfen statt warten
curl -X POST https://entra-portal.example.com/api/v1/customers/musterag/check \
-H "Authorization: Bearer $TOKEN"
Die Antwort enthält das Ergebnis des Laufs, nicht nur eine Bestätigung. Ein Onboarding-Script kann damit direkt melden, ob die Zugangsdaten stimmen.
3. Laufzeiten auslesen
Secret-Werte gibt es nirgends. Weder die API noch Microsoft Graph geben den Wert eines Client Secrets heraus. Ausgelesen werden Metadaten: welche Anwendung, welcher Typ, wann läuft es ab. Genau das ist der Zweck.
curl -H "Authorization: Bearer $TOKEN" \
https://entra-portal.example.com/api/v1/customers/contoso/credentials
{
"customer": "contoso",
"count": 3,
"last_check_at": "2026-09-08T17:05:28+00:00",
"credentials": [
{
"app_name": "SVC-Backup Veeam",
"app_id": "00000001-aaaa-bbbb-cccc-000000000000",
"type": "secret",
"credential_name": "prod",
"days_left": 5,
"end_date": "2026-09-13T18:05:28+00:00",
"object_type": "application",
"sibling_count": 1
},
{
"app_name": "ConnectSyncProvisioning",
"type": "cert",
"days_left": 86,
"end_date": "2026-12-03T18:05:28+00:00",
"sibling_count": 2
}
]
}
Sortiert ist nach kürzester Restlaufzeit. sibling_count sagt, wie viele
Credentials derselben Anwendung und desselben Typs zusammengefasst wurden; gezählt wird
immer die längste Laufzeit, weil ein frisch gerolltes Secret das alte irrelevant macht.
Nur das, was Aufmerksamkeit braucht
Wer nicht alle Kunden durchgehen und selbst bewerten will, nimmt /problems.
Die Reihenfolge ist die der Dringlichkeit.
curl -H "Authorization: Bearer $TOKEN" \
https://entra-portal.example.com/api/v1/problems
{
"checked_customers": 5,
"count": 3,
"customers": [
{
"key": "contoso",
"display_name": "Contoso AG",
"state": "error",
"min_days": 5,
"problems": [
{
"code": "credential_critical",
"severity": "error",
"message": "SVC-Backup Veeam (prod) läuft in weniger als 14 Tagen ab.",
"action": "Erneuerung einplanen.",
"applications": ["SVC-Backup Veeam (prod)"],
"min_days_left": 5,
"count": 1
}
]
},
{
"key": "seewerk",
"display_name": "Seewerk Industrie",
"state": "error",
"problems": [{ "code": "scan_failed", "severity": "error", "message": "..." }]
}
]
}
Von fünf Kunden tauchen hier nur die drei auf, bei denen etwas ist. Jeder Befund hat
einen code zum Auswerten und einen Satz Klartext zum Anzeigen.
| Code | Bedeutung |
|---|---|
credential_critical | Restlaufzeit unter der Fehlerschwelle |
credential_warning | Restlaufzeit unter der Warnschwelle |
credential_expired | bereits abgelaufen |
scan_failed | letzter Lauf ist gescheitert, Details in scan.error |
never_checked | noch nie erfolgreich geprüft |
no_credential | kein Zugangsdatum der gewählten Anmeldeart hinterlegt |
inactive | Überwachung für diesen Kunden abgeschaltet |
4. XML auslesen
Das PRTG-XML hängt nicht am API-Schlüssel, sondern an einem eigenen Token pro Kunde. So kann eine PRTG-Instanz die Sensordaten holen, ohne einen Schlüssel zu besitzen, der Kunden anlegen könnte.
curl -H "Authorization: Bearer $TOKEN" \
https://entra-portal.example.com/api/v1/customers/contoso/urls
{
"prtg_xml": "https://entra-portal.example.com/prtg/SENSOR-TOKEN-AUS-DER-ANTWORT",
"json": "https://entra-portal.example.com/json/SENSOR-TOKEN-AUS-DER-ANTWORT",
"hinweis": "Anhängbar sind ?app=, ?filter=, ?exclude=, ?type=secret|cert, ?warn= und ?error= für eigene Schwellen."
}
Die prtg_xml-URL kommt in einen Sensor vom Typ HTTP Data Advanced.
Sie braucht keinen weiteren Kopf, der Token steht im Pfad:
curl https://entra-portal.example.com/prtg/SENSOR-TOKEN-AUS-DER-ANTWORT
<?xml version="1.0" encoding="UTF-8" ?>
<prtg>
<result>
<channel>Minimale Restlaufzeit</channel>
<value>5</value>
<unit>Custom</unit>
<customunit>Tage</customunit>
<limitmode>1</limitmode>
<limitminwarning>30</limitminwarning>
<limitminerror>14</limitminerror>
</result>
<result>
<channel>SVC-Backup Veeam (Secret)</channel>
<value>5</value>
...
</result>
</prtg>
Ein Abruf dieser URL löst keine Graph-Anfrage aus. Geliefert wird der gespeicherte Stand des letzten Laufs. PRTG darf also häufig fragen, ohne dass Microsoft häufiger gefragt wird.
Ein kompromittierter Token wird durch einen neuen ersetzt. Die bisherige URL liefert danach nichts mehr:
curl -X POST https://entra-portal.example.com/api/v1/customers/contoso/token \
-H "Authorization: Bearer $TOKEN"
5. Fehler auslesen
Fehler kommen immer im selben Umschlag: ein Objekt error mit
code, message und bei Eingabefehlern zusätzlich
fields.
Ungültige Eingabe, 422
Ein Schlüssel mit Grossbuchstaben, der häufigste Fall beim Onboarding:
{
"error": {
"code": "validation_failed",
"message": "Eingaben unvollständig.",
"fields": {
"key": "Grossbuchstaben sind im Schlüssel nicht erlaubt. Verwende musterag."
}
}
}
fields ist nach Feldnamen aufgeschlüsselt und lässt sich direkt an ein
Formular zurückgeben. Die Meldung ist wörtlich dieselbe wie in der Oberfläche, damit
Techniker und Administrator dieselbe Anweisung lesen.
Zugangsdatum passt nicht zur Anmeldeart, 422
Wichtig für ein Rotations-Script: ein client_secret für einen Kunden,
der auf Zertifikat steht, wird abgelehnt statt stillschweigend verworfen.
{
"error": {
"code": "validation_failed",
"message": "Eingaben unvollständig.",
"fields": {
"client_secret": "Gehört nicht zu auth_type 'certificate'. Feld weglassen oder leer senden."
}
}
}
Ein echter Wechsel der Anmeldeart geht weiterhin. Dabei muss auth_type
mitkommen und das Feld der alten Methode weggelassen oder leer gesendet werden:
curl -X PATCH https://entra-portal.example.com/api/v1/customers/contoso -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"auth_type": "secret", "client_secret": "neuer-wert"}'
Unbekannter Kunde, 404
{
"error": {
"code": "not_found",
"message": "Kein Kunde mit diesem Schlüssel."
}
}
Statuscodes
| Status | error.code | Wann | Was tun |
|---|---|---|---|
| 200 | - | Abfrage erfolgreich | - |
| 201 | - | Kunde angelegt | - |
| 401 | unauthenticated | Kopf Authorization fehlt | Bearer-Token mitgeben |
| 401 | invalid_key | Schlüssel falsch oder widerrufen | Neuen Schlüssel ausstellen |
| 403 | read_only | Schlüssel ist read, Aufruf braucht write | Schreibenden Schlüssel verwenden |
| 404 | not_found | Kunde existiert nicht | Kundenschlüssel prüfen |
| 409 | duplicate | Kundenschlüssel bereits vergeben | Anderen Schlüssel wählen oder PATCH benutzen |
| 422 | validation_failed | Eingabe unvollständig oder ungültig | fields auswerten |
| 429 | rate_limited | Zu viele Aufrufe | Kopf Retry-After abwarten |
Die Codes in dieser Tabelle sind keine Behauptung: tools/demo_pages.py
löst jeden davon bei jedem Lauf einmal aus und schreibt die Antwort nach
api-statuscodes.json.
Fehler eines Prüflaufs
Ein gescheiterter Lauf ist kein HTTP-Fehler. Der Kunde antwortet mit 200, der Fehler
steht im Objekt scan:
{
"key": "seewerk",
"state": "error",
"scan": {
"status": "error",
"last_check_at": "2026-09-08T12:05:28+00:00",
"last_success_at": null,
"error": "AADSTS7000215: Ungültiges Client Secret. Secret abgelaufen oder falsch hinterlegt."
}
}
Der AADSTS-Code von Entra bleibt erhalten. Damit steht die Ursache im überwachenden System, ohne dass jemand ins Portal schauen muss.
Rate Limits
Schreibende Aufrufe und Prüfungen sind begrenzt, damit ein fehlerhaftes Script weder die Instanz noch Microsoft Graph flutet. Bei Überschreitung kommt 429 mit der Wartezeit in der Antwort. Ein Onboarding, das Kunden nacheinander anlegt, läuft nie hinein.