Provenia API
Mit der Provenia-API liest und pflegst du Produkte, Produktpässe, Datenträger und Scan-Statistiken deines Betriebs — z. B. aus deiner Warenwirtschaft heraus. Alle Antworten sind JSON, alle Endpunkte sind strikt auf deinen Betrieb (Tenant) begrenzt und unter /api/v1 versioniert.
Basis-URL https://pass.provenia.at/api/v1
Format JSON (UTF-8) · Content-Type: application/json
Auth Authorization: Bearer pv_… (API-Schlüssel)
Limits max. 200 Einträge pro Seite (limit/offset-Paging)Authentifizierung
Jeder Request braucht einen API-Schlüssel im Authorization-Header. Schlüssel erstellst du in der App unter Einstellungen → API (nur Inhaber:innen). Der Schlüssel wird genau einmal angezeigt — wir speichern nur seinen SHA-256-Hash.
curl https://pass.provenia.at/api/v1/me \
-H "Authorization: Bearer pv_DEIN_SCHLUESSEL"Widerrufene Schlüssel werden sofort ungültig. Behandle Schlüssel wie Passwörter: nie im Frontend einbetten, nie committen.
Fehler & Statuscodes
Fehler kommen immer in derselben Struktur:
{
"error": {
"code": "invalid_api_key",
"message": "API-Schlüssel fehlt, ist ungültig oder wurde widerrufen."
}
}| Parameter | Typ | Beschreibung |
|---|---|---|
invalid_api_key | 401 | Schlüssel fehlt, ist ungültig oder widerrufen. |
not_found | 404 | Produkt/Ressource existiert nicht in deinem Betrieb. |
invalid_json | 400 | Request-Body ist kein gültiges JSON. |
name_required | 400 | Pflichtfeld 'name' fehlt (POST /products). |
unknown_product_group | 400 | product_group ist keiner Vorlage zugeordnet. |
values_must_be_array | 400 | PUT …/passport erwartet ein Array. |
field_key_required | 400 | Jeder Wert braucht ein field_key. |
internal_error | 500 | Unerwarteter Fehler — bitte erneut versuchen. |
/api/v1/meBetrieb abfragen
Liefert den Betrieb, der zum Schlüssel gehört — ideal als Verbindungs-Test.
{
"tenant": { "id": "…", "name": "Leinenweberei Hofer", "slug": "leinenweberei-hofer",
"country": "AT", "plan": "trial" },
"products_total": 12
}/api/v1/productsProdukte auflisten
| Parameter | Typ | Beschreibung |
|---|---|---|
status | string, optional | Filter: draft · active · archived |
search | string, optional | Suche im Produktnamen (enthält, case-insensitiv) |
limit | int, optional | Seitengröße, Standard 50, max. 200 |
offset | int, optional | Versatz fürs Paging, Standard 0 |
curl "…/api/v1/products?status=active&limit=2" -H "Authorization: Bearer pv_…"
{
"items": [
{ "id": "…", "name": "Leinenhemd Mühlviertel", "slug": "leinenhemd-muehlviertel",
"status": "active", "gtin": "9012345678901", "completeness": 92,
"registry_status": "none",
"unique_product_identifier": "urn:provenia:leinenweberei-hofer:leinenhemd-muehlviertel",
"created_at": "…", "updated_at": "…" }
],
"total": 5, "limit": 2, "offset": 0
}/api/v1/productsProdukt anlegen
| Parameter | Typ | Beschreibung |
|---|---|---|
name | string, Pflicht | Produktname |
slug | string, optional | URL-Slug — wird sonst aus dem Namen erzeugt; bei Kollision automatisch Suffix |
product_group | string, optional | Vorlagen-Schlüssel, z. B. textile, battery, smartphone, computer, cable, motor, tyre, packaging, chemical, other — voller Katalog in der App |
gtin | string, optional | GTIN (8/12/13/14 Ziffern) für GS1-konforme Datenträger |
model | string, optional | Modell/Variante |
curl -X POST …/api/v1/products \
-H "Authorization: Bearer pv_…" -H "Content-Type: application/json" \
-d '{ "name": "Tischläufer Leinpfad", "product_group": "textile", "gtin": "9012345678918" }'
→ 200: das angelegte Produkt (gleiche Struktur wie GET /products/{slug})Das Produkt startet als draft mit stabilem unique_product_identifier — der spätere QR-Link bleibt dauerhaft gültig.
/api/v1/products/{slug}Einzelnes Produkt
Vollständige Stammdaten inkl. registry_status/registry_id (EU-Register-Vorbereitung) und Produktgruppe.
curl …/api/v1/products/leinenhemd-muehlviertel -H "Authorization: Bearer pv_…"/api/v1/products/{slug}/passportProduktpass lesen
Alle Pass-Werte (auch restricted/authority — die API ist dein interner Zugriff), inkl. Quelle, Prüf-Status und der letzten Version mit content_hash (Integritätsnachweis).
{
"product": { "id": "…", "name": "Leinenhemd Mühlviertel", "slug": "…",
"status": "active", "completeness": 92 },
"template_version": 1,
"values": [
{ "field_key": "co2_footprint", "value": "2,4", "unit": "kg CO₂e",
"source": "supplier", "is_unknown": false, "needs_review": true,
"access_level": "public", "locale": "de", "updated_at": "…" }
],
"latest_version": { "content_hash": "…", "created_at": "…" }
}/api/v1/products/{slug}/passportPass-Werte schreiben (Upsert)
Body ist ein Array von Werten. Bestehende Felder werden aktualisiert, neue angelegt. Die Vollständigkeit wird serverseitig nachgeführt und jede Änderung versioniert (mit content_hash).
| Parameter | Typ | Beschreibung |
|---|---|---|
field_key | string, Pflicht | Feld-Schlüssel der Vorlage, z. B. co2_footprint |
value | beliebig, Pflicht | Der Wert (String empfohlen) |
unit | string, optional | Einheit, z. B. kg CO₂e |
source | string, optional | manual · import · supplier — Standard: import |
access_level | string, optional | public · restricted · authority — Standard: public |
is_unknown / needs_review | bool, optional | Unbekannt-Markierung / Prüf-Flag |
curl -X PUT …/api/v1/products/leinenhemd-muehlviertel/passport \
-H "Authorization: Bearer pv_…" -H "Content-Type: application/json" \
-d '[
{ "field_key": "co2_footprint", "value": "2,4", "unit": "kg CO₂e" },
{ "field_key": "main_material", "value": "100 % Bio-Leinen" }
]'
→ { "updated": 2, "completeness": 92 }/api/v1/products/{slug}/carriersDatenträger eines Produkts
{
"items": [
{ "id": "…", "type": "qr",
"gs1_digital_link": "https://…/01/09012345678901",
"nfc_tag_uid": null, "status": "active", "created_at": "…" }
]
}/api/v1/analytics/scans?days=30Scan-Statistik (anonym)
| Parameter | Typ | Beschreibung |
|---|---|---|
days | int, optional | Zeitraum in Tagen, Standard 30, max. 365 |
{
"days": 30, "total": 1248,
"per_day": [ { "date": "2026-05-07", "count": 22 }, … ],
"by_view_role": { "consumer": 1190, "inspector": 41, "recycler": 17 },
"top_products": [ { "slug": "tischlaeufer-leinpfad", "name": "Tischläufer Leinpfad", "count": 412 }, … ]
}Es werden keine personenbezogenen Daten erfasst — IPs sind nur als gesalzener Hash gespeichert.
Öffentliche Endpunkte (ohne Schlüssel)
Für aktivierte Pässe — das, was Datenträger und Dritt-Systeme nutzen:
| Parameter | Typ | Beschreibung |
|---|---|---|
GET /p/{tenant}/{produkt} | HTML | Öffentliche Pass-Seite; erweiterte Ansicht nur mit Zugangs-Token (?zugang=…) |
GET /p/{tenant}/{produkt}/jsonld | application/ld+json | Maschinenlesbarer Pass (schema.org + GS1-Vokabular, content-Hash); ?zugang=… wie oben |
GET /01/{gtin} | 302/307 | GS1-Digital-Link-Resolver → leitet zur Pass-Seite |
Öffentlich erscheinen nur Felder mit access_level=public aktivierter Produkte. restricted-Felder (Lieferkette, Demontage, Stoffe) gibt es ausschließlich über Prüf-/Recycling-Links mit gültigem Token — die Rolle steckt im Token, ein bloßer ?view=-Parameter genügt nicht. Token erstellst und widerrufst du im Produktdetail unter „Vorschau“.