Appearance
API-Referenz: Überblick
Die matev-API liefert Katalog- und Mediendaten aus dem PIM („Skynox PIM“, Laravel + Filament) an andere Systeme: an die Website (Nuxt), an das CMS (Kirby) und an das Händlerportal. Sie ist nur lesend. Es gibt keine Endpunkte, mit denen man Daten anlegt, ändert oder löscht.
Diese Seite erklärt die Regeln, die für alle Endpunkte gelten. Die einzelnen Endpunkte stehen unter Endpunkte, das Fehlerformat unter Fehler.
Basis-URL
| Umgebung | Basis-URL |
|---|---|
| Produktion | https://admin.matev.eu/api/v1 |
| Lokal (DDEV) | https://matev.ddev.site:33001/api/v1 (siehe Lokale Entwicklung) |
Alle Pfade in dieser Referenz sind relativ zur Basis-URL. GET /products heißt also GET https://admin.matev.eu/api/v1/products.
Versionierung
Die Version steht im Pfad: /api/v1/…. Eine andere Version gibt es derzeit nicht.
Innerhalb von v1 gilt:
- Neue Felder in Antworten können jederzeit hinzukommen. Clients sollen unbekannte Felder ignorieren.
- Felder werden nicht umbenannt oder entfernt, ohne dass das angekündigt wird. Eine solche Änderung käme in eine neue Version (
/api/v2). - Manche Felder erscheinen nur, wenn der Endpunkt die zugehörigen Daten mitlädt (zum Beispiel
articlesnur in der Produkt-Detailansicht). Das steht bei jedem Endpunkt dabei.
Öffentlich oder mit Token
Es gibt zwei Arten von Endpunkten:
| Art | Pfade | Anmeldung |
|---|---|---|
| Öffentlich | /media, /media-folders, /image-formats, /taxonomies, /products, /articles, /manufacturers, /tractor-models, /tractor-series | keine |
| Geschützt | /user, /search/suggestions, alles unter /content/… | Bearer-Token |
Die öffentlichen Endpunkte liefern nur, was für die Website freigegeben ist:
- Medien: nur Assets mit Freigabestatus „Freigegeben“ oder „Freigegeben für Kanäle“ mit dem Kanal Website, die nicht abgelaufen sind (
valid_until) und in einem aktiven Ordner liegen (oder in keinem Ordner). Gelöschte Assets (Papierkorb) nie. - Medienordner: nur aktive Ordner ohne Rollenbeschränkung.
- Taxonomien: nur Dimensionen, die im PIM als „im Frontend nutzbar“ markiert sind. Dimensionen wie Personen, Orte oder Wetter sind ausgeschlossen.
- Produkte, Artikel, Hersteller: nur aktive Datensätze (
is_active). Artikel werden nicht nach Lebensende (end_of_life) gefiltert, weil Ersatzteile für ausgelaufene Maschinen gerade gesucht werden. - Preise, Preislisten und Lagerbestand liefert die API nie aus.
Authentifizierung
Geschützte Endpunkte erwarten ein Laravel-Sanctum-Token im Header:
http
Authorization: Bearer <token>
Accept: application/jsonImmer Accept: application/json mitschicken
Fehlt dieser Header und fehlt auch das Token, versucht Laravel bei geschützten Endpunkten, auf eine Login-Seite umzuleiten. Eine Route mit dem Namen login gibt es im PIM nicht. Statt 401 unauthenticated kann dann ein 500 server_error zurückkommen. Mit dem Header bekommen Sie zuverlässig die richtige Antwort.
Token anlegen
Tokens legt ein Administrator im PIM an: Systemverwaltung → API-Tokens. Der Token wird genau einmal im Klartext angezeigt und danach nur noch als Hash gespeichert. Wer ihn verliert, legt einen neuen an und widerruft den alten auf derselben Seite.
Ein Token gehört immer zu dem Benutzer, der ihn angelegt hat. Er läuft nicht von selbst ab (expiration ist in config/sanctum.php nicht gesetzt). Widerrufen Sie Tokens, die nicht mehr gebraucht werden.
Beim Anlegen wählen Sie ein Zugriffsprofil. Daraus ergeben sich die Rechte (Sanctum nennt sie abilities):
| Profil im PIM | Abilities |
|---|---|
| „Content-CMS (Katalog und freigegebene Medien)“ (Standard) | content:catalog:read, content:media:read |
| „Nur freigegebene Medien“ | content:media:read |
| „Vollzugriff (bestehende Integrationen)“ | * (alle) |
Welche Ability braucht welcher Endpunkt?
| Ability | Öffnet |
|---|---|
content:media:read | /content/media, /content/media/{media}, /content/media/{media}/preview, /content/media/{media}/download, /content/media-folders |
content:catalog:read | /content/products, /content/products/{slug}, /content/articles, /content/articles/{ordernumber}, /content/taxonomies, /content/taxonomies/{slug} |
| (jedes gültige Token) | /user, /search/suggestions |
Ability search
Die Ability search ist vorgesehen, wird aber derzeit von keiner Route geprüft. /search/suggestions nimmt jedes gültige Token an. Kein Profil im PIM vergibt search einzeln.
Fehlt das Token oder ist es ungültig, antwortet die API mit 401 unauthenticated. Hat das Token nicht die nötige Ability, kommt 403 forbidden. Details unter Fehler.
Tokens gehören nicht in den Browser
Ein Token darf nie in Frontend-Code, in Kirby-Inhalten oder im Git-Repository stehen. Kirby und Nuxt lesen es serverseitig aus ihrer privaten Konfiguration. Siehe Medien aus dem PIM.
Antwortformat
Alle Antworten sind JSON. Einzelne Datensätze und Listen stecken in data:
json
{ "data": { "id": 12, "slug": "schneepflug-sp-250" } }Einzige Ausnahme: /content/media/{media}/preview und /content/media/{media}/download liefern die Datei selbst, kein JSON.
Datumswerte sind ISO 8601 mit Zeitzone, zum Beispiel 2026-03-20T14:22:00+00:00. Fehlt ein Datum, steht null da.
Paginierung
Einige Listen sind seitenweise aufgeteilt. Sie steuern das mit zwei Query-Parametern:
| Parameter | Typ | Bedeutung |
|---|---|---|
page | Integer | Seite, beginnt bei 1 |
per_page | Integer | Einträge pro Seite |
Standardwerte und Obergrenzen sind je Endpunkt verschieden:
| Endpunkt | Standard per_page | Obergrenze |
|---|---|---|
/media | 24 | 200 |
/content/media | 24 | 100 |
/products, /content/products | 15 | 100 |
/articles, /content/articles | 15 | 100 |
/tractor-models | 50 | 200 |
Größere Werte werden auf die Obergrenze gesetzt, kleinere als 1 auf 1.
Bitte fordern Sie nicht mehr als 100 Einträge pro Seite an, auch wo der Code keine Grenze setzt.
Alle anderen Listen (/media-folders, /image-formats, /taxonomies, /manufacturers, /tractor-series, /content/media-folders) sind nicht paginiert und liefern alles auf einmal.
Eine paginierte Antwort sieht so aus (Laravel-Standard):
json
{
"data": [ … ],
"links": {
"first": "https://admin.matev.eu/api/v1/products?page=1",
"last": "https://admin.matev.eu/api/v1/products?page=5",
"prev": null,
"next": "https://admin.matev.eu/api/v1/products?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 5,
"links": [
{ "url": null, "label": "« Previous", "active": false },
{ "url": "https://admin.matev.eu/api/v1/products?page=1", "label": "1", "active": true },
{ "url": "https://admin.matev.eu/api/v1/products?page=2", "label": "Next »", "active": false }
],
"path": "https://admin.matev.eu/api/v1/products",
"per_page": 15,
"to": 15,
"total": 73
}
}Zum Durchblättern folgen Sie einfach links.next, bis es null ist.
Filter
Filter sind einfache Query-Parameter. Welche es gibt, steht beim jeweiligen Endpunkt. Ein paar Regeln gelten überall:
- Ein leerer Parameter (
?search=) wird ignoriert. - Unbekannte Parameter werden ignoriert, nicht abgelehnt.
- Listen von IDs übergeben Sie als PHP-Array:
?ids[]=12&ids[]=15.?ids=12geht auch für eine einzelne ID. - Der Taxonomie-Filter
taxonsbei Produkten versteht zusätzlich eine Komma-Liste (?taxons=a,b). Details unter Produkte.
Suche
search ist bei /products und /media eine richtige Katalogsuche:
- Jedes Wort muss irgendwo vorkommen („Räumschild 250“ findet „Räumschild RS 250“).
- Durchsucht werden die genannten Spalten und die im PIM gepflegten Schlagwörter.
- Synonyme aus dem PIM werden mitgesucht (siehe Synonyme und Suchanalyse).
- Die Suche wird anonym protokolliert, damit die Synonymliste besser wird. Optional können Sie einen Header
X-Search-Sessionmitschicken, damit zusammengehörige Suchen einer Sitzung erkannt werden.
Bei /articles ist search einfacher: Teiltreffer in Bestellnummer oder deutschem Namen, ohne Synonyme.
Sprachen
Mehrsprachige Felder kommen als Objekt mit Sprachcodes:
json
"names": { "de": "Schneepflug", "en": "Snow plough", "fr": "Chasse-neige" }Das gilt für names, short_names und descriptions bei Produkten, Artikeln und Herstellern. Es gibt keinen Sprach-Parameter und keine Auswertung von Accept-Language. Der Client wählt die Sprache selbst und sollte auf de zurückfallen, wenn eine Übersetzung fehlt.
Ausnahmen in der aktuellen Version
Einige Felder kommen als einfacher Text in der Standardsprache des Servers statt als Sprach-Objekt:
namesbei Taxonomie-Termen (taxons[])labelsbei Taxonomiennamesbei Traktor-Serienaltundtitlebei Medien (bewusst: das Feld landet direkt imalt-Attribut)
Grund ist, dass die Resource-Klassen hier den übersetzten Wert statt aller Übersetzungen ausgeben. Auf Produktion ist die Standardsprache Deutsch (APP_LOCALE: de im Server-Stack; der Laravel-Standard wäre en). Verlassen Sie sich nicht darauf, dass das so bleibt: eine spätere Version kann diese Felder auf Sprach-Objekte umstellen.
CORS
Das Projekt hat keine eigene config/cors.php. Es gilt der Laravel-Standard: Für Pfade unter api/* sind Anfragen von allen Ursprüngen erlaubt. Für die öffentlichen Endpunkte heißt das, dass auch Browser-Code sie direkt aufrufen kann.
Geschützte Endpunkte rufen Sie trotzdem nur serverseitig auf, weil das Token nicht in den Browser gehört.
Es werden keine Header für Browser-Skripte freigegeben (exposed_headers ist leer). Browser-Code auf einer fremden Domain kann den Header X-Request-Id deshalb nicht lesen. Die Request-ID steht aber auch im JSON-Body unter error.request_id.
Rate Limit
Die API begrenzt die Zahl der Anfragen pro Minute:
| Wer | Grenze | Gezählt je |
|---|---|---|
| mit Token | 1.200 Anfragen/Minute | Token |
| ohne Token | 600 Anfragen/Minute | Client-IP |
| eigene Server im privaten Netz (Website-SSR, Kirby) | keine Grenze | – |
Bei Überschreitung antwortet die API mit 429 too_many_requests und einem Retry-After-Header (Sekunden). Die Antworten tragen außerdem X-RateLimit-Limit und X-RateLimit-Remaining. Die Werte lassen sich auf dem Server über API_RATE_LIMIT_TOKEN und API_RATE_LIMIT_GUEST einstellen. Siehe Fehler.
Intern wird nur das Protokollieren von Suchen gedrosselt (höchstens 30 protokollierte Suchen pro Minute und IP). Das bremst keine Anfrage, es landet nur nicht jede Suche in der Statistik.
Request-ID
Jede Fehlerantwort trägt einen Header X-Request-Id. Schicken Sie selbst einen mit, wird er übernommen, sofern er gültig ist (8 bis 64 Zeichen aus A–Z a–z 0–9 . _ -). So finden Client und Server denselben Vorgang in ihren Logs. Details unter Fehler.
OpenAPI-Spezifikation
Das PIM erzeugt die Spezifikation automatisch aus Routen und Resources (Paket dedoc/scramble):
- Spezifikation:
/docs/api.json - Lesbare Oberfläche (Redoc):
/api/docs
Die Redoc-Seite /api/docs ist öffentlich erreichbar, sie lädt aber die Spezifikation von /docs/api.json. Diese ist geschützt: lokal (APP_ENV=local) ist sie frei, auf dem Server nur für angemeldete PIM-Benutzer, denen das Recht viewApiDocs gewährt ist. Dieses Recht wird von keinem Seeder angelegt, und Super-Admins haben keinen automatischen Durchgriff. Auf dem Server bekommt man die Spezifikation deshalb derzeit nur, wenn jemand das Recht eigens anlegt und vergibt. Mehr dazu unter OpenAPI (live).
Diese Referenz ist von Hand geschrieben und erklärt mehr als die Spezifikation. Bei Widersprüchen gilt der Code.