Feed API — Technische Referenz
[api] [feed]
REST-Endpoints für Produktfeed-Export. Token-basierte Authentifizierung für Server-zu-Server-Imports oder Session-basiert im Browser.
Basis-URL
https://<tenant>.licensio.io/api/portal/feed
oder mit Custom-Domain:
https://portal.deinedomain.de/api/portal/feed
Endpoints
GET /api/portal/feed
Exportiert den Produktkatalog im gewählten Format.
Query-Parameter:
| Param | Werte | Pflicht | Default |
|---|---|---|---|
format | csv | shopify | woocommerce | json | nein | csv |
token | string | nein* | — |
fields | kommagetrennte Liste | nein | gespeicherte Auswahl, sonst Default |
*Pflicht wenn keine Session-Auth. Token gilt als Auth (siehe Authentifizierung).
Verfügbare fields:
name, description, category, compatibilities, preview_image, additional_images, price, tags, file_formats, created_at, updated_at
Response Headers:
Content-Type:text/csv; charset=utf-8/application/json/application/xmlContent-Disposition:attachment; filename="produkte.{ext}"Cache-Control:private, no-store
Response 401 Unauthorized:
- Kein Token UND keine Session
- Token ungültig (kein Match in DB)
- Customer-Status ≠
active
GET /api/portal/feed/token
Status des Feed-Tokens.
Auth: Session-Cookie nötig.
Response 200 OK:
{
"has_token": true,
"created_at": "2026-04-25T14:30:00Z"
}
Token-Klartext wird nicht zurückgegeben (Security).
POST /api/portal/feed/token
Generiert oder erneuert Feed-Token.
Auth: Session-Cookie nötig.
Response 200 OK:
{
"token": "f9a1b2c3...64-char-hex"
}
Token = crypto.randomBytes(32).toString('hex'). Wird customers.feed_token zugeordnet (UNIQUE, alter Token wird überschrieben).
DELETE /api/portal/feed/token
Token invalidieren.
Response 200 OK:
{ "success": true }
feed_token und feed_token_created_at werden auf NULL gesetzt.
GET /api/portal/feed/settings
Auth: Session-Cookie nötig.
Response 200 OK:
{
"fields": ["name", "description", "category", "price"],
"mapping": {
"name": "Title",
"price": "Variant Price"
}
}
POST /api/portal/feed/settings
Speichert Felder + Mapping.
Body:
{
"fields": ["name", "description"],
"mapping": { "name": "Title" }
}
Beide Felder optional — können einzeln upgedatet werden.
Authentifizierung
Token (für Server-zu-Server)
Query-Parameter ?token=<64-char-hex>. Token muss in customers.feed_token existieren UND customers.status = 'active'.
Vorteil: keine Cookies nötig, ideal für Cron-Jobs / Shop-Importer.
Risk: Wer den Token hat, sieht den Katalog. Read-only, keine personenbezogenen Daten.
Session (für Browser)
Standard-Supabase-Auth-Cookies. Funktioniert wenn Endpoint Same-Origin aufgerufen wird.
Format-Spezifikationen
CSV (default)
Name,Beschreibung,Kategorie,Kompatibilitaeten,Vorschaubild,Preis,Tags
"Produkt 1","Beschreibung...",Werkzeug,"Bambu Lab, AC Infinity",https://...,49.99,"Bambu Lab, AC Infinity, Werkzeug"
- Header-Zeile mit Spaltennamen (durch
mappingüberschreibbar) - CSV-Escape: Werte mit
,,",\nwerden in Anführungszeichen gewrapped, interne"verdoppelt
Shopify CSV
Feste Spalten (Shopify Product Import Format):
Handle,Title,Body (HTML),Vendor,Type,Tags,Published,Image Src,Image Position,Variant Price,Variant SKU,Variant Inventory Policy
licensio-<uuid>,"Produkt 1","Beschreibung...","Plant Technology",Werkzeug,"Bambu Lab",true,https://...,1,49.99,licensio-<uuid>,continue
Spalten-Mapping wird hier ignoriert (Shopify-spezifisch).
WooCommerce XML
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product>
<id>uuid</id>
<name>Produkt 1</name>
<description>Beschreibung...</description>
<categories>Werkzeug</categories>
<tags>Bambu Lab, AC Infinity</tags>
<images>
<image>https://...</image>
</images>
<price>49.99</price>
</product>
</products>
XML-Escape: &, <, >, ", ' korrekt encodiert.
JSON
[
{
"name": "Produkt 1",
"description": "Beschreibung...",
"category": "Werkzeug",
"compatibilities": "Bambu Lab, AC Infinity",
"preview_image": "https://...",
"price": "49.99",
"tags": "Bambu Lab, AC Infinity, Werkzeug"
}
]
Pretty-printed (2-Space-Indent). Mapping wird auf JSON-Keys angewandt.
Paket-Einschränkungen
Der Feed wendet die identischen Filter wie das Portal-/dashboard/products an:
items.filter(item => {
// excluded_categories gewinnt
if (excludedCats.includes(category)) return false
// allowed_categories Whitelist
if (allowedCats.length && !allowedCats.includes(category)) return false
// excluded_compatibilities gewinnt
if (excludedCompats.some(c => item.compatibility.includes(c))) return false
// allowed_compatibilities (Items ohne Compats sind sichtbar)
if (allowedCompats.length && item.compatibility.length &&
!item.compatibility.some(c => allowedCompats.includes(c))) return false
return true
})
Quelle: apps/portal/app/api/portal/feed/route.ts.
Felder-Mapping zur DB
| Feed-Feld | DB-Pfad |
|---|---|
name | catalog_items.name |
description | catalog_items.metadata.description (JSONB) |
category | catalog_items.metadata.category (JSONB) |
compatibilities | catalog_items.compatibility[] joined , |
preview_image | catalog_items.images[0] |
additional_images | catalog_items.images[1..] joined , |
price | catalog_items.recommended_price (text) |
tags | compatibility + metadata.category joined |
file_formats | distinct production_files.file_type joined |
created_at | catalog_items.created_at |
updated_at | catalog_items.updated_at |
Beispiel-Aufrufe
# Standard-CSV mit Token
curl "https://planttechnology.licensio.io/api/portal/feed?format=csv&token=abc..."
# JSON mit nur 3 Feldern
curl "https://planttechnology.licensio.io/api/portal/feed?format=json&token=abc...&fields=name,price,preview_image"
# Shopify-Direkt-Import
curl -O -J "https://planttechnology.licensio.io/api/portal/feed?format=shopify&token=abc..."
# WooCommerce XML
curl "https://planttechnology.licensio.io/api/portal/feed?format=woocommerce&token=abc..."
Verwandt
- Feed-Export-Feature — Creator-fokussierte Anleitung
- Paket-Einschränkungen — Filter-Logik