Ask before the transaction: does this agent belong to the company, and may it do this?
The problem
An agent presents a request or an approval. Signed requests or a person's approval show which software is knocking or that someone agreed. What often stays open: does the agent belong to the company it names, and what is it permanently allowed or not allowed to do? The occasion is this week's news: Who is the agent? Two answers in one week, and a gap.
The solution
Before accepting, the verifier asks ID MANDAT. One lookup, no account, answer in seconds:
- Is the domain registered, and is the agent on its list?
- Is the answer authentic (Ed25519 signature) and fresh (15 minutes)?
- Is the entry still valid, or revoked, suspended or expired?
- Is the planned action listed under “may” (
may) or “may not” (mayNot)?
If in doubt: do not proceed.
The lookup: https://idmandat.com/api/v1/verify?domain=example.com&agent=Name. The public keys are at https://idmandat.com/api/v1/keys. To try it first, use the playground with fictional companies.
Example code
Two small examples that perform exactly these four checks. They are examples: please test them yourself and adapt them to your environment before relying on them.
Python (3.9+, pip install cryptography)
"""ID MANDAT: Vor dem Vorgang nachfragen (Beispiel, Python 3.9+).
Braucht: pip install cryptography
Prüft: ist der Agent bei der Domain eingetragen, ist die Antwort echt signiert
und frisch, ist der Eintrag nicht widerrufen/ausgesetzt/abgelaufen,
und steht die geplante Handlung in "may" bzw. "mayNot".
Wichtig: "may"/"mayNot" sind Angaben des Unternehmens (declared_by_operator).
ID MANDAT bestätigt Identität und Befugnis, nicht Bonität, Vertrauenswürdigkeit
oder Qualität. Im Zweifel gilt: NICHT ausführen (fail closed).
"""
import base64, json, urllib.parse, urllib.request
from datetime import datetime, timezone
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
API = "https://idmandat.com/api/v1"
def _b64u(s):
return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
def _canon(o):
if isinstance(o, dict):
return {k: _canon(o[k]) for k in sorted(o)}
if isinstance(o, list):
return [_canon(x) for x in o]
return o
def _get(url):
with urllib.request.urlopen(url, timeout=10) as r:
return json.load(r)
def signature_ok(answer):
"""Echtheit prüfen: Ed25519 über die kanonische JSON-Antwort ohne 'signature'."""
body = dict(answer)
sig = body.pop("signature", None)
if not answer.get("signed") or not sig or sig.get("alg") != "Ed25519":
return False
keys = {k["key_id"]: k for k in _get(API + "/keys")["keys"]}
key = keys.get(answer.get("key_id"))
if not key or key.get("status") != "active":
return False
msg = json.dumps(_canon(body), separators=(",", ":"), ensure_ascii=False).encode()
try:
Ed25519PublicKey.from_public_bytes(_b64u(key["public_key"])).verify(_b64u(sig["value"]), msg)
return True
except Exception:
return False
def darf_ich(domain, agent_name, handlung):
"""Gibt (erlaubt, grund) zurück. erlaubt ist nur True, wenn alles klar ist."""
q = urllib.parse.urlencode({"domain": domain, "agent": agent_name})
try:
a = _get(API + "/verify?" + q)
except Exception as e:
return False, "Abfrage fehlgeschlagen: %s" % e
if not signature_ok(a):
return False, "Antwort nicht (gültig) signiert"
if datetime.fromisoformat(a["expires_at"].replace("Z", "+00:00")) < datetime.now(timezone.utc):
return False, "Antwort abgelaufen, neu abfragen"
if not a.get("registered"):
return False, "nicht registriert (%s)" % a.get("entry_state", "unbekannt")
if a.get("entry_state") or a.get("revoked"):
return False, "Eintrag gilt nicht: %s" % a.get("entry_state", "widerrufen")
treffer = [x for x in a.get("agents", []) if x.get("name") == agent_name]
if not treffer:
return False, "Agent steht nicht in der Liste dieser Domain"
ag = treffer[0]
if handlung in ag.get("mayNot", []):
return False, "Handlung ist ausdrücklich ausgeschlossen (mayNot)"
if handlung not in ag.get("may", []):
return False, "Handlung ist nicht ausdrücklich erlaubt (may)"
return True, "erlaubt laut Erklärung des Unternehmens (declared_by_operator)"
if __name__ == "__main__":
print(darf_ich("idmandat.com", "Grok (xAI)", "draft_social_posts"))
print(darf_ich("idmandat.com", "Grok (xAI)", "place_order"))
Node (18+, no packages)
// ID MANDAT: Vor dem Vorgang nachfragen (Beispiel, Node 18+, keine Pakete nötig).
// may/mayNot sind Angaben des Unternehmens. Im Zweifel: NICHT ausführen.
import { createPublicKey, verify } from "node:crypto";
const API = "https://idmandat.com/api/v1";
const get = async (u) => { const r = await fetch(u); if (!r.ok) throw new Error(r.status); return r.json(); };
const canon = (o) => Array.isArray(o) ? o.map(canon)
: (o && typeof o === "object") ? Object.fromEntries(Object.keys(o).sort().map(k => [k, canon(o[k])])) : o;
const b64u = (s) => Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/"), "base64");
async function signaturOk(a) {
const { signature, ...body } = a;
if (!a.signed || !signature || signature.alg !== "Ed25519") return false;
const key = (await get(API + "/keys")).keys.find(k => k.key_id === a.key_id && k.status === "active");
if (!key) return false;
const pub = createPublicKey({ key: { kty: "OKP", crv: "Ed25519", x: key.public_key }, format: "jwk" });
return verify(null, Buffer.from(JSON.stringify(canon(body))), pub, b64u(signature.value));
}
export async function darfIch(domain, agent, handlung) {
let a;
try { a = await get(`${API}/verify?domain=${encodeURIComponent(domain)}&agent=${encodeURIComponent(agent)}`); }
catch (e) { return [false, "Abfrage fehlgeschlagen: " + e.message]; }
if (!(await signaturOk(a))) return [false, "Antwort nicht (gültig) signiert"];
if (new Date(a.expires_at) < new Date()) return [false, "Antwort abgelaufen, neu abfragen"];
if (!a.registered) return [false, "nicht registriert (" + (a.entry_state ?? "unbekannt") + ")"];
if (a.entry_state || a.revoked) return [false, "Eintrag gilt nicht: " + (a.entry_state ?? "widerrufen")];
const ag = (a.agents ?? []).find(x => x.name === agent);
if (!ag) return [false, "Agent steht nicht in der Liste dieser Domain"];
if ((ag.mayNot ?? []).includes(handlung)) return [false, "Handlung ausdrücklich ausgeschlossen (mayNot)"];
if (!(ag.may ?? []).includes(handlung)) return [false, "Handlung nicht ausdrücklich erlaubt (may)"];
return [true, "erlaubt laut Erklärung des Unternehmens (declared_by_operator)"];
}
if (import.meta.url === `file://${process.argv[1]}`) {
console.log(await darfIch("idmandat.com", "Grok (xAI)", "draft_social_posts"));
console.log(await darfIch("idmandat.com", "Grok (xAI)", "place_order"));
}
The answer is valid for 15 minutes (expires_at); do not cache it longer.
What this does not do
- Statements about agents and permissions come from the company itself. ID MANDAT confirms identity and authority, not creditworthiness, trustworthiness or quality.
- It does not show that a person approved this specific transaction.
- It enforces nothing: whether a shop or system requires or respects the answer is its own decision.
For shops · Check a domain · Technical standard · Register your company