API-Referenz
Stand: 18. August 2026
Basis-URL: https://api.valyda.ch
Es gibt zwei Klassen von Endpunkten. Unter /v1 liegt alles, was deinem
Server gehört; es verlangt den API-Schlüssel. Unter /public liegt, was der
Browser deiner Kundin aufrufen darf; es verlangt das Einweg-Token einer
einzelnen Verifikation.
Authentifizierung
Authorization: Bearer <api-key>
Fehlt der Kopfzeileneintrag oder ist der Schlüssel unbekannt oder
zurückgezogen, antwortet die API mit 401 und {"error": "unauthorized"}.
Jeder Schlüssel gehört genau einem Konto. Eine Verifikation ist nur mit dem Schlüssel lesbar, mit dem sie erstellt wurde – die Zuordnung steckt in der Datenbankabfrage, nicht in einer Prüfung im Anwendungscode.
Verifikation erstellen
POST /v1/verifications
| Feld | Typ | Pflicht | Bedeutung |
|---|---|---|---|
profile |
Text | ja | age-over-18, age-over-16 oder identity-full |
client_reference |
Text | nein | Deine eigene Referenz, kommt unverändert zurück |
{"profile": "age-over-18", "client_reference": "bestellung-4711"}
Antwort 201:
| Feld | Bedeutung |
|---|---|
id |
Kennung der Verifikation, für die Statusabfrage |
status |
Immer PENDING |
profile |
Das angeforderte Profil |
deeplink |
URL, die die Wallet öffnet – Ziel des QR-Codes |
expires_at |
Ablaufzeitpunkt, ISO 8601 |
public_token |
Einweg-Token für den Browser |
public_token ist das Einzige aus dieser Antwort, das den Browser erreichen
darf – zusammen mit id und, wenn du den QR selbst zeichnest, deeplink.
Status abfragen
GET /v1/verifications/<id>
| Feld | Bedeutung |
|---|---|
id |
Kennung |
profile |
Verwendetes Profil |
status |
PENDING, SUCCESS, FAILED oder EXPIRED |
satisfied |
true, false oder null – das eigentliche Ergebnis |
failure_reason |
Grund bei FAILED, sonst null |
channel |
eid oder external – womit geprüft wurde |
assurance |
high bei der E-ID, external beim eigenen Verfahren |
client_reference |
Deine Referenz |
created_at |
Erstellt |
completed_at |
Abgeschlossen, sonst null |
expires_at |
Ablauf |
Freigeben nur bei status === "SUCCESS" && satisfied === true. Warum beides,
steht unter Ablauf einer Verifikation.
Eine unbekannte ID – oder eine, die einem anderen Konto gehört – ergibt 404
mit {"error": "not_found"}. Beides sieht gleich aus, und das ist Absicht.
Eigenes Verfahren bestätigen
POST /v1/verifications/<id>/confirm
Der Ausweichkanal. Wenn dein eigenes Prüfverfahren zum Schluss kommt, dass die Kundin alt genug ist, meldest du das hier – und die Verifikation gilt als bestanden, so als hätte die E-ID geantwortet.
{ "channel": "external" }
channel ist optional und derzeit nur external. Es geht nichts über die
Prüfung selbst mit: keine Ausweisnummer, kein Geburtsdatum, nicht einmal eine
Notiz. Gespeichert wird, dass ein Verfahren ausserhalb der E-ID geantwortet
hat – mehr braucht dein Nachweis nicht, und mehr wollen wir nicht halten.
Die Antwort ist die übliche Statusform, ergänzt um channel und assurance.
Das Ergebnis lautet assurance: "external" und nicht high: dein
Verfahren ist nicht die staatliche E-ID, und der Nachweis muss das weiterhin
sagen können.
Warum das nur mit dem API-Key geht und nicht mit dem Einweg-Token des Widgets: Eine Kundschaft, die ihre eigene Prüfung bestätigen kann, hat keine Prüfung. Der Aufruf gehört deshalb in dein Backend.
| Antwort | Bedeutung |
|---|---|
200 |
Übernommen |
404 |
Unbekannte ID – oder eine, die einem anderen Konto gehört |
409 already_settled |
Es gibt bereits ein Ergebnis |
409 expired |
Die Sitzung ist abgelaufen; erstelle eine neue |
already_settled gilt ausdrücklich auch dann, wenn die E-ID zuvor mit
FAILED geantwortet hat. Ein abgelehntes Ergebnis lässt sich nicht
nachträglich überschreiben. Wer einer Kundschaft nach einem Fehlschlag ein
anderes Verfahren anbieten will, startet eine neue Verifikation – das ist eine
Entscheidung vorher, keine Korrektur hinterher.
Das Widget merkt die Bestätigung von selbst: Es fragt den öffentlichen Endpunkt weiter ab, auch wenn der Dialog geschlossen ist, und wechselt auf „bestätigt", sobald die Antwort da ist.
Öffentliche Endpunkte
Diese beiden sind für den Browser gedacht. Sie authentifizieren über das
Einweg-Token der jeweiligen Verifikation, erlauben jede Herkunft
(access-control-allow-origin: *) und werden nicht zwischengespeichert.
GET /public/verifications/<id>?token=<public_token>
Antwortet mit id, profile, status, satisfied, channel und
expires_at – eine verkürzte Fassung ohne deine Referenz und ohne
Zeitstempel, die den Browser nichts angehen.
GET /public/verifications/<id>/qr.svg?token=<public_token>
Liefert den QR-Code zum Deeplink als SVG. Damit musst du keine QR-Bibliothek einbinden.
Das Token steht bewusst in der Abfragezeichenkette und nicht in einer
Kopfzeile: so bleibt jede Anfrage eine einfache CORS-Anfrage, und der Browser
spart sich die Vorabanfrage. Fehlt das Token oder passt es nicht zur ID,
antworten beide Endpunkte mit 404.
Weitere Endpunkte
GET /health
Antwortet mit {"status": "ok"}. Für deine Überwachung.
GET /widget/v1/valyda.js
Das Widget-Skript. Siehe Widget.
Fehler
| Code | Körper | Ursache |
|---|---|---|
400 |
{"error": "invalid_request", "details": […]} |
Feld fehlt oder hat den falschen Typ; details benennt die Stelle |
401 |
{"error": "unauthorized"} |
Schlüssel fehlt, ist unbekannt oder zurückgezogen |
404 |
{"error": "not_found"} |
Unbekannte ID, fremdes Konto oder falsches Token |
Statt Abfragen: Webhooks
Wer im Sekundentakt abfragt, belastet beide Seiten. Für die serverseitige Benachrichtigung siehe Webhooks. Ein Rückfall auf Abfragen bleibt trotzdem sinnvoll – warum, steht dort.