neofire Core API

Base-URL: https://<host>/admin/api/   EIGENES System

Dies ist ein eigenes REST-System — Routen, Felder und Verhalten gelten ausschließlich für dieses Admin und folgen keiner fremden Schnittstelle.

Authentifizierung

Jeder Request braucht einen gültigen API-Key (64 Hex-Zeichen):

Keys werden im Admin unter API-Keys erzeugt. Der Klartext-Key wird nur einmal direkt nach dem Anlegen angezeigt — danach nie wieder.

Fehlt der Key oder ist er inaktiv/ungültig:

401 {"success":false,"error":"unauthorized"}

Routen

MethodeRouteZweck
GET/admin/api/<entity>?limit=&offset=Liste (default limit=50, max 200, offset=0)
GET/admin/api/<entity>?format=csvExport: ALLE Datensätze als CSV, uuid als erste Spalte
GET/admin/api/<entity>/<uuid>Einzelner Datensatz per UUID
POST/admin/api/<entity>Upsert; Body JSON-Objekt, uuid Pflicht
POST/admin/api/<entity>Massen-Upsert: JSON-Array bzw. {"data":[…]}
POST/admin/api/<entity> (Content-Type: text/csv)Massen-Upsert per CSV — die exportierte Datei unverändert zurückschicken

Export und Rückspielen

Der Export liefert standardmäßig alle Zeilen (mit limit/offset lässt es sich eingrenzen), sortiert aufsteigend nach interner id — so stehen übergeordnete Einträge vor ihren untergeordneten, was beim Zurückspielen von Kategorien zählt. Format: Trennzeichen ;, UTF-8 mit BOM (öffnet direkt in Excel).

Dieselbe Datei geht unverändert wieder rein. Leere Zellen werden zu NULL, nicht zu "" — sonst scheitert jede Zahlenspalte, weil der Export für NULL eine leere Zelle schreibt. Spalten, die nicht in der write-Liste stehen (created_at u. a.), werden beim Rückspielen ignoriert, nicht abgelehnt.

# Export
curl -H "X-API-Key: $KEY" "https://host/admin/api/articles?format=csv" -o artikel.csv

# unverändert zurückspielen (Upsert per uuid)
curl -H "X-API-Key: $KEY" -H "Content-Type: text/csv" \
     --data-binary @artikel.csv "https://host/admin/api/articles"

Massen-Upsert: Verhalten bei Fehlern

Standard ist zeilenweise: jede Zeile zählt für sich, am Ende kommt ein Bericht. Gibt es Fehler, ist die Antwort 207 mit success:false, und results nennt Zeilennummer, uuid und Grund.

{"success":true,"total":13,"created":0,"updated":13,"failed":0,
 "results":[{"row":1,"uuid":"…","action":"updated"}, …]}

Mit ?atomic=1 gilt alles oder nichts: die erste fehlerhafte Zeile rollt den ganzen Vorgang zurück, Antwort 422.

Obergrenze: 5000 Zeilen pro Aufruf (darüber 413). Ein einzelnes JSON-Objekt verhält sich unverändert wie bisher (201 created / 200 updated).

UUID = einziger externer Identifier. POST ist ein Upsert per UUID: existiert die UUID → UPDATE (nur write-Felder), sonst INSERT mit der übergebenen Client-UUID. Die interne id wird nie als Schlüssel verlangt (nur informativ bei Create zurückgegeben).

Ohne mod_rewrite funktioniert alles identisch über den Fallback index.php?entity=<e>&uuid=<u>.

Entitäten

Pro Entität sind nur die gelisteten Felder les- bzw. schreibbar (Whitelist).

Beim Verteilen zwischen Shops beachten: Verweise laufen über interne Zahlen-IDs (manufacturer_id, parent_id, unit_id, group_id, shop_id). Die sind pro Shop verschieden — unverändert übernommen zeigen sie im Zielshop auf etwas anderes oder ins Leere. Vor dem Rückspielen umschlüsseln.
created_at füllt sich bei articles, categories und orders automatisch; bei customers und units gibt es keinen Standardwert — neu angelegte Datensätze bleiben dort ohne Datum.

articles (Tabelle articles)

read: uuid, name, variant_name, article_number, ean, net_price, price, other_price, tax_class, status, type, typ, root, parent_id, stock, manufacturer_id, unit_id, unit, product_unit, sales_unit, description_short, description_long, weight, buy_active, book_active, rent_active, rent_price, rent_interval, book_price, abo_active, abo_weekly, abo_monthly, abo_yearly, abo_discount_weekly, abo_discount_monthly, abo_discount_yearly, product_interval, interval_unit, requires_appointment, appointment_duration, appointment_type, images, created_at, updated_at

write: wie read, ohne uuid, images, created_at, updated_at

Hinweis: article_number u.ä. sind NOT-NULL ohne Default — beim ersten INSERT mitschicken, sonst kommt ein 400 db: ….

images ist nur lesbar (JSON mit Medien-Verweisen). stock ist der GESAMT-Bestand; die Aufteilung auf einzelne Lager lässt sich hierüber nicht setzen.

categories (Tabelle categories)

read: uuid, name, parent_id, type, status, sort, groups, system, layout_id, description_short, description_long, images, created_at, updated_at

write: name, parent_id, type, status, sort, groups, layout_id, description_short, description_long

Hinweis: type ist NOT-NULL ohne Default — beim ersten INSERT mitschicken. system und images sind nur lesbar.

Diese Entität deckt drei Arten ab, unterschieden über type: category (Kategorie), page (Seite) und manufacturer (Hersteller). Hersteller lagen früher in einer eigenen Tabelle; sie liegen jetzt hier, damit alle drei dieselben Felder, dieselben Layouts und dieselben Platzhalter haben. Zum Export nur der Hersteller nach type filtern.

layout_id verweist auf ein Layout (Menü Inhalte → Layouts). Leer bedeutet: es gilt das Standard-Layout.

orders (Tabelle orders)

read: uuid, number, email, status, payment_status, shipping_status, sub_total, tax_total, total, shipping_cost, billing_company, billing_firstname, billing_lastname, billing_street, billing_zipcode, billing_city, billing_country, shipping_company, shipping_firstname, shipping_lastname, shipping_street, shipping_zipcode, shipping_city, shipping_country, shipping_method_id, payment_method, transaction, tracking_numbers, comment, confirmation_sent, group_calendar_id, group_appointment, group_appointment_end, customer_account_id, customer_group_id, partner_id, shop_id, created_at, updated_at

write: status, payment_status, shipping_status, tracking_numbers, comment — Stammdaten der Bestellung bleiben schreibgeschützt

Nicht enthalten: stripe_* (Zahlungs-Identifikatoren) und die neofire_*-Sync-Marker der Plugins.

customers (Tabelle customer_accounts_customers)

read: uuid, email, salutation, firstname, lastname, company, phone, group_id, shop_id, status, last_login_at, created_at, updated_at

write: email, salutation, firstname, lastname, company, phone, group_id, shop_id, status kein password_hash in read/write

units (Tabelle units)

read: uuid, name, short_name, sort, created_at

write: name, short_name, sort

Was die API (noch) NICHT kann

Die Registry bildet je Entität genau EINE Tabelle ab, adressiert über uuid. Verknüpfte Daten ohne eigene uuid brauchen eigene Endpunkte und sind derzeit nicht übertragbar: Lagerbestände je Lager (article_warehouse_stock), Preise je Preisliste (article_price_list_prices), Shop-Zuweisungen (shop_articles), Kategorie- und Eigenschafts-Zuordnungen, Medien und Bestellpositionen.

Fehlercodes

CodeBedeutung
400invalid json body / db: <fehler>
401unauthorized (Key fehlt/ungültig/inaktiv)
404unknown entity / not found
405method not allowed
413too many rows (Massen-Upsert > 5000 Zeilen)
422uuid is required / no writable fields / atomic abgebrochen
207Massen-Upsert teilweise fehlgeschlagen — Details in results

curl-Beispiele

# Liste
curl -H "X-API-Key: $KEY" https://host/admin/api/articles?limit=10

# Einzeln per UUID
curl -H "X-API-Key: $KEY" https://host/admin/api/articles/2b1c...e9

# Anlegen/Aktualisieren (uuid PFLICHT)
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
     -d '{"uuid":"2b1c...e9","name":"Neuer Artikel","article_number":"A-100","price":19.9}' \
     https://host/admin/api/articles

Beispiel-Responses

# Liste
{"success":true,"total":42,"limit":10,"offset":0,"data":[ ... ]}

# Einzeln
{"success":true,"data":{ "uuid":"2b1c...e9", ... }}

# Create
{"success":true,"action":"created","uuid":"2b1c...e9","id":123}

# Update
{"success":true,"action":"updated","uuid":"2b1c...e9"}

# Fehler (uuid fehlt)
{"success":false,"error":"uuid is required"}