API-Referenz
Fragen Sie ID MANDAT, wer hinter einer Domain steht und welche KI-Agenten für sie handeln. Die Antwort ist JSON, auf Wunsch mit digitaler Signatur (Ed25519). Diese Seite beschreibt, was die Schnittstelle heute tut. Die Angaben des Betreibers („darf“, „darf nicht“, Grenzen, Länder) werden von ID MANDAT nicht geprüft und nicht erzwungen. ID MANDAT bestätigt Identität und Befugnis, nicht Bonität oder Qualität.
Adressen
| Anfrage | Zweck |
|---|---|
GET /api/v1/verify?domain=beispiel.de | Eintrag und Stufe zu einer Domain. Mit &agent=Name nur dieser Agent. |
GET /api/v1/verify?signature_agent=https://signer.example.com | Sucht, wer dieses Schlüsselverzeichnis erklärt hat (Web Bot Auth, Kopfzeile Signature-Agent). Der Wert der Kopfzeile geht auch. |
GET /api/v1/keys | Öffentliche Schlüssel zum Prüfen der Signatur (Ed25519, base64url ohne Auffüllzeichen). Felder: key_id, public_key, status (active / retired). |
Methoden: GET, HEAD, OPTIONS. CORS ist offen (Access-Control-Allow-Origin: *). Basisadresse: https://idmandat.com.
Felder der Antwort
| Feld | Bedeutung |
|---|---|
level | verified, registered, listed_unverified oder none. Siehe unten. |
level_text | Die Stufe in einem Satz (englisch). |
entry_state | Nur wenn ein früherer Eintrag nicht mehr gültig ist: revoked, suspended oder expired. Dann ist level gleich none. Nicht auf frühere Antworten verlassen. |
organization, country, city, industry | Angaben zum Unternehmen wie eingetragen. |
agents[] | Agenten des Unternehmens mit may, mayNot, optional limits (je Vorgang / je Tag, mit Währung) und validIn (Länder). Alles vom Betreiber erklärt. |
agent_found | Mit &agent=: ob es einen Agenten dieses Namens beim Eintrag gibt. |
mandate_source | Immer declared_by_operator. |
registered_at, last_checked | Eintragsdatum und letzte automatische Nachprüfung. |
signed, key_id, issued_at, expires_at, signature | Daten zur Signatur. Ist signed gleich false, darf die Antwort nicht als Nachweis gelten. |
api, version, beta | Name und Version der Schnittstelle. |
Stufen
- verified: Domain, Impressum und Handelsregister bzw. USt-IdNr. automatisch geprüft und regelmäßig wiederholt.
- registered: Ausweis-Datei und DNS-Eintrag auf der Domain des Unternehmens bestätigt. Die Angaben sind Erklärungen des Betreibers.
- listed_unverified: selbst eingetragen, Angaben nicht geprüft. Das gilt auch für Einträge mit dem Siegel „Basis“ (Domain per DNS bestätigt); die Siegelstufe zeigt die Prüfseite der Domain.
- none: kein Eintrag. Das ist für sich allein kein Hinweis auf etwas Unseriöses.
Fehler
| Status | error | Ursache |
|---|---|---|
| 400 | invalid_domain | domain fehlt oder ist keine gültige Domain. |
| 400 | invalid_signature_agent | signature_agent ist keine brauchbare Adresse. |
| 405 | method_not_allowed | Alles außer GET, HEAD, OPTIONS. |
Fehlerantworten sind JSON mit error und message. Eine unbekannte Domain ist kein Fehler: Sie bekommen Status 200 mit "registered": false.
Grenzen, Zwischenspeicher und Gültigkeit
- Abruf-Grenzen: In dieser Version gibt es keine feste Grenze je Aufrufer. Bitte nehmen Sie Rücksicht: Der Dienst läuft auf einem geteilten Hosting. Wer viele Abfragen braucht, speichert Antworten zwischen.
- Zwischenspeicher: Antworten tragen
Cache-Control: public, max-age=60. Eine Minute zwischenspeichern ist in Ordnung. - Gültigkeit der Signatur: Eine signierte Antwort gilt 15 Minuten ab
issued_at(sieheexpires_at). Danach neu abfragen. Nachexpires_atnicht mehr danach handeln. - Zählung: Wir zählen nur Abfragen pro Tag und Kanal. Keine IP-Adresse, keine Domain, keine Person wird gespeichert.
Signatur prüfen
- Die Antwort ohne das Feld
signaturenehmen. - Alle Objektschlüssel rekursiv alphabetisch sortieren und das JSON kompakt schreiben (ohne Leerzeichen, Unicode und „/“ nicht escapen).
- Die UTF-8-Bytes dieses Textes mit dem öffentlichen Schlüssel prüfen, dessen
key_idpasst, gegensignature.value(Ed25519, base64url ohne Auffüllzeichen). - Prüfen, dass
expires_atnicht überschritten ist.
Beispiel in Python (braucht das Paket cryptography):
import base64, json
from datetime import datetime, timezone
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
def b64u(s):
return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
def kanonisch(x):
# Objektschlüssel rekursiv sortieren, kompakt, UTF-8, "/" und Unicode nicht escapen
return json.dumps(x, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
def pruefen(antwort, schluessel):
if not antwort.get("signed"):
return False # nicht signiert: kein Nachweis
ablauf = datetime.strptime(antwort["expires_at"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
if datetime.now(timezone.utc) > ablauf:
return False # abgelaufen: neu abrufen
key = next(k for k in schluessel["keys"] if k["key_id"] == antwort["key_id"])
ohne = {k: v for k, v in antwort.items() if k != "signature"}
try:
Ed25519PublicKey.from_public_bytes(b64u(key["public_key"])).verify(
b64u(antwort["signature"]["value"]), kanonisch(ohne).encode("utf-8"))
return True
except Exception:
return False
Versionen
Jede Antwort nennt die Version der Schnittstelle im Feld version (derzeit 0.3.0-beta). Die Schnittstelle ist in der Betaphase. Felder bitte über ihren Namen lesen und unbekannte Felder ignorieren.
Dazu
Fragen · Der Standard · MCP-Server: https://idmandat.com/mcp · A2A-Karte: https://idmandat.com/.well-known/agent-card.json