# API-Anleitung - Payload CMS ## Übersicht Das Payload CMS stellt eine REST-API und eine GraphQL-API bereit. Diese Anleitung beschreibt die Nutzung der REST-API für alle Collections. **Base URL:** `https://pl.porwoll.tech/api` --- ## Authentifizierung ### Login ```bash curl -X POST "https://pl.porwoll.tech/api/users/login" \ -H "Content-Type: application/json" \ -d '{ "email": "admin@example.com", "password": "your-password" }' ``` **Response:** ```json { "message": "Auth Passed", "user": { "id": 1, "email": "admin@example.com", "isSuperAdmin": true, "tenants": [...] }, "token": "eyJhbGciOiJIUzI1NiIs..." } ``` ### Token verwenden ```bash curl "https://pl.porwoll.tech/api/posts" \ -H "Authorization: JWT eyJhbGciOiJIUzI1NiIs..." ``` --- ## Mehrsprachigkeit (Localization) Das CMS unterstützt Deutsch (de) und Englisch (en). Lokalisierte Felder können über den `locale` Parameter abgerufen werden. ```bash # Deutsche Inhalte (Standard) curl "https://pl.porwoll.tech/api/posts?locale=de" # Englische Inhalte curl "https://pl.porwoll.tech/api/posts?locale=en" # Alle Sprachen gleichzeitig curl "https://pl.porwoll.tech/api/posts?locale=all" ``` --- ## Users API ### Aktuellen User abrufen ```bash curl "https://pl.porwoll.tech/api/users/me" \ -H "Authorization: JWT your-token" ``` ### User-Felder | Feld | Typ | Beschreibung | |------|-----|--------------| | `email` | string | E-Mail-Adresse (eindeutig) | | `isSuperAdmin` | boolean | Super Admin hat Zugriff auf alle Tenants | | `tenants` | array | Zugewiesene Tenants | --- ## Tenants API ### Alle Tenants abrufen (Auth + SuperAdmin erforderlich) ```bash curl "https://pl.porwoll.tech/api/tenants" \ -H "Authorization: JWT your-token" ``` ### Tenant erstellen (Auth + SuperAdmin erforderlich) ```bash curl -X POST "https://pl.porwoll.tech/api/tenants" \ -H "Authorization: JWT your-token" \ -H "Content-Type: application/json" \ -d '{ "name": "Meine Firma GmbH", "slug": "meine-firma", "domains": [ { "domain": "meine-firma.de" }, { "domain": "www.meine-firma.de" } ] }' ``` ### Tenant-Felder | Feld | Typ | Beschreibung | |------|-----|--------------| | `name` | string | Anzeigename des Tenants | | `slug` | string | URL-freundlicher Identifier (eindeutig) | | `domains` | array | Zugeordnete Domains | --- ## Posts API ### Alle Posts abrufen ```bash # Alle Posts curl "https://pl.porwoll.tech/api/posts" # Nur Blog-Artikel curl "https://pl.porwoll.tech/api/posts?where[type][equals]=blog" # Nur News curl "https://pl.porwoll.tech/api/posts?where[type][equals]=news" # Nur veröffentlichte Posts curl "https://pl.porwoll.tech/api/posts?where[status][equals]=published" # Nur hervorgehobene Posts curl "https://pl.porwoll.tech/api/posts?where[isFeatured][equals]=true" # Mit Sortierung (neueste zuerst) curl "https://pl.porwoll.tech/api/posts?sort=-publishedAt" # Limitiert auf 10 Einträge curl "https://pl.porwoll.tech/api/posts?limit=10" # Pagination (Seite 2) curl "https://pl.porwoll.tech/api/posts?limit=10&page=2" # Mit Locale curl "https://pl.porwoll.tech/api/posts?locale=de" ``` ### Einzelnen Post abrufen ```bash # Nach ID curl "https://pl.porwoll.tech/api/posts/1" # Nach Slug (über Query) curl "https://pl.porwoll.tech/api/posts?where[slug][equals]=mein-erster-artikel" ``` ### Post erstellen (Auth erforderlich) ```bash curl -X POST "https://pl.porwoll.tech/api/posts" \ -H "Authorization: JWT your-token" \ -H "Content-Type: application/json" \ -d '{ "tenant": 1, "title": "Mein neuer Artikel", "slug": "mein-neuer-artikel", "type": "blog", "isFeatured": false, "excerpt": "Eine kurze Zusammenfassung des Artikels...", "content": { "root": { "type": "root", "children": [ { "type": "paragraph", "children": [{ "text": "Der Artikelinhalt..." }] } ] } }, "status": "draft" }' ``` ### Post aktualisieren (Auth erforderlich) ```bash curl -X PATCH "https://pl.porwoll.tech/api/posts/1" \ -H "Authorization: JWT your-token" \ -H "Content-Type: application/json" \ -d '{ "status": "published", "publishedAt": "2025-11-30T12:00:00.000Z" }' ``` ### Post löschen (Auth erforderlich) ```bash curl -X DELETE "https://pl.porwoll.tech/api/posts/1" \ -H "Authorization: JWT your-token" ``` --- ## Testimonials API ### Alle Testimonials abrufen ```bash # Alle Testimonials curl "https://pl.porwoll.tech/api/testimonials" # Nur aktive Testimonials curl "https://pl.porwoll.tech/api/testimonials?where[isActive][equals]=true" # Sortiert nach Bewertung (beste zuerst) curl "https://pl.porwoll.tech/api/testimonials?sort=-rating" # Sortiert nach eigener Reihenfolge curl "https://pl.porwoll.tech/api/testimonials?sort=order" ``` ### Testimonial erstellen (Auth erforderlich) ```bash curl -X POST "https://pl.porwoll.tech/api/testimonials" \ -H "Authorization: JWT your-token" \ -H "Content-Type: application/json" \ -d '{ "tenant": 1, "quote": "Hervorragender Service! Ich bin sehr zufrieden.", "author": "Max Mustermann", "role": "Geschäftsführer", "company": "Musterfirma GmbH", "rating": 5, "source": "Google Reviews", "isActive": true, "order": 1 }' ``` --- ## Newsletter Subscribers API ### Newsletter-Anmeldung (Öffentlich) ```bash # Einfache Anmeldung curl -X POST "https://pl.porwoll.tech/api/newsletter-subscribers" \ -H "Content-Type: application/json" \ -d '{ "tenant": 1, "email": "kunde@example.com", "source": "website-footer" }' # Mit Namen und Interessen curl -X POST "https://pl.porwoll.tech/api/newsletter-subscribers" \ -H "Content-Type: application/json" \ -d '{ "tenant": 1, "email": "kunde@example.com", "firstName": "Max", "lastName": "Mustermann", "interests": ["blog", "products"], "source": "blog-sidebar" }' ``` **Response (Erfolg):** ```json { "doc": { "id": 1, "tenant": 1, "email": "kunde@example.com", "status": "pending", "confirmationToken": "uuid-token-here", "subscribedAt": "2025-11-30T14:23:41.012Z" }, "message": "Newsletter Subscriber successfully created." } ``` ### Subscribers abrufen (Auth erforderlich) ```bash # Alle Subscribers curl "https://pl.porwoll.tech/api/newsletter-subscribers" \ -H "Authorization: JWT your-token" # Nur bestätigte Subscribers curl "https://pl.porwoll.tech/api/newsletter-subscribers?where[status][equals]=confirmed" \ -H "Authorization: JWT your-token" # Nach E-Mail suchen curl "https://pl.porwoll.tech/api/newsletter-subscribers?where[email][equals]=kunde@example.com" \ -H "Authorization: JWT your-token" ``` ### Subscriber bestätigen (Double Opt-In) ```bash # Über Token bestätigen curl -X PATCH "https://pl.porwoll.tech/api/newsletter-subscribers/1" \ -H "Authorization: JWT your-token" \ -H "Content-Type: application/json" \ -d '{ "status": "confirmed" }' ``` ### Subscriber abmelden ```bash curl -X PATCH "https://pl.porwoll.tech/api/newsletter-subscribers/1" \ -H "Authorization: JWT your-token" \ -H "Content-Type: application/json" \ -d '{ "status": "unsubscribed" }' ``` --- ## Pages API ### Seiten abrufen ```bash # Alle Seiten curl "https://pl.porwoll.tech/api/pages" # Seite nach Slug curl "https://pl.porwoll.tech/api/pages?where[slug][equals]=startseite" # Nur veröffentlichte Seiten curl "https://pl.porwoll.tech/api/pages?where[status][equals]=published" # Mit Locale curl "https://pl.porwoll.tech/api/pages?locale=de&depth=2" ``` ### Seite mit Blocks Die Blocks werden im `layout`-Array zurückgegeben: ```json { "docs": [{ "id": 1, "title": "Startseite", "slug": "startseite", "layout": [ { "blockType": "hero-block", "title": "Willkommen", "subtitle": "..." }, { "blockType": "posts-list-block", "title": "Aktuelle News", "postType": "news", "limit": 3 }, { "blockType": "testimonials-block", "title": "Das sagen unsere Kunden", "layout": "slider" } ] }] } ``` --- ## Consent Management APIs ### Cookie-Konfiguration abrufen (Öffentlich, Tenant-isoliert) Die Cookie-Konfiguration wird automatisch nach Domain gefiltert. ```bash # Für Frontend Cookie-Banner curl "https://pl.porwoll.tech/api/cookie-configurations?where[tenant][equals]=1" ``` **Response:** ```json { "docs": [{ "id": 1, "tenant": 1, "title": "Cookie-Einstellungen", "revision": 1, "enabledCategories": ["necessary", "analytics"], "translations": { "de": { "bannerTitle": "Wir respektieren Ihre Privatsphäre", "bannerDescription": "Diese Website verwendet Cookies...", "acceptAllButton": "Alle akzeptieren", "acceptNecessaryButton": "Nur notwendige", "settingsButton": "Einstellungen", "saveButton": "Auswahl speichern", "privacyPolicyUrl": "/datenschutz", "categoryLabels": { "necessary": { "title": "Notwendig", "description": "..." }, "functional": { "title": "Funktional", "description": "..." }, "analytics": { "title": "Statistik", "description": "..." }, "marketing": { "title": "Marketing", "description": "..." } } } }, "styling": { "position": "bottom", "theme": "dark" } }] } ``` ### Cookie-Inventory abrufen (Öffentlich, Tenant-isoliert) Dokumentation aller verwendeten Cookies für die Datenschutzerklärung. ```bash # Alle Cookies eines Tenants curl "https://pl.porwoll.tech/api/cookie-inventory?where[tenant][equals]=1" # Nur aktive Cookies curl "https://pl.porwoll.tech/api/cookie-inventory?where[tenant][equals]=1&where[isActive][equals]=true" # Nach Kategorie filtern curl "https://pl.porwoll.tech/api/cookie-inventory?where[tenant][equals]=1&where[category][equals]=analytics" ``` **Response:** ```json { "docs": [{ "id": 1, "tenant": 1, "name": "_ga", "provider": "Google LLC", "category": "analytics", "duration": "2 Jahre", "description": "Wird verwendet, um Benutzer zu unterscheiden.", "isActive": true }] } ``` ### Cookie-Inventory Felder | Feld | Typ | Beschreibung | |------|-----|--------------| | `name` | string | Technischer Cookie-Name (z.B. "_ga") | | `provider` | string | Anbieter (z.B. "Google LLC") | | `category` | enum | necessary, functional, analytics, marketing | | `duration` | string | Speicherdauer (z.B. "2 Jahre") | | `description` | string | Beschreibung für Endnutzer | | `isActive` | boolean | Cookie aktiv? | ### Consent-Log erstellen (API-Key erforderlich) Consent-Logs sind ein WORM (Write-Once-Read-Many) Audit-Trail für DSGVO-Nachweise. ```bash curl -X POST "https://pl.porwoll.tech/api/consent-logs" \ -H "Content-Type: application/json" \ -H "X-API-Key: your-consent-api-key" \ -d '{ "tenant": 1, "clientRef": "uuid-from-cookie", "categories": { "necessary": true, "functional": false, "analytics": true, "marketing": false }, "revision": 1, "userAgent": "Mozilla/5.0..." }' ``` **Response:** ```json { "doc": { "id": 1, "consentId": "550e8400-e29b-41d4-a716-446655440000", "tenant": 1, "clientRef": "uuid-from-cookie", "categories": { "necessary": true, "analytics": true, ... }, "revision": 1, "anonymizedIp": "a1b2c3d4e5f6...", "expiresAt": "2028-12-02T00:00:00.000Z", "createdAt": "2025-12-02T10:00:00.000Z" } } ``` **Wichtig:** - CREATE: Nur mit gültigem `X-API-Key` Header - READ: Nur authentifizierte Admin-User - UPDATE: **Nicht erlaubt** (WORM-Prinzip) - DELETE: **Nicht erlaubt** (nur via Retention-Job) ### Consent-Log Felder | Feld | Typ | Beschreibung | |------|-----|--------------| | `consentId` | string | Server-generierte UUID (read-only) | | `clientRef` | string | Client-Cookie-Referenz für Traceability | | `tenant` | relation | Zugehöriger Tenant | | `categories` | json | Akzeptierte Kategorien | | `revision` | number | Konfigurationsversion zum Zeitpunkt der Zustimmung | | `userAgent` | string | Browser-Information | | `anonymizedIp` | string | HMAC-Hash der IP (täglich rotierend) | | `expiresAt` | date | Automatische Löschung nach 3 Jahren | ### Privacy Policy Settings abrufen (Öffentlich, Tenant-isoliert) Konfiguration für die Datenschutzerklärungs-Seite (z.B. Alfright Integration). ```bash curl "https://pl.porwoll.tech/api/privacy-policy-settings?where[tenant][equals]=1" ``` **Response:** ```json { "docs": [{ "id": 1, "tenant": 1, "title": "Datenschutzerklärung", "provider": "alfright", "alfright": { "tenantId": "alfright_schutzteam", "apiKey": "9f315103c43245bcb0806dd56c2be757", "language": "de-de", "iframeHeight": 4000 }, "styling": { "headerColor": "#ca8a04", "backgroundColor": "#111827", ... }, "showCookieTable": true, "cookieTableTitle": "Übersicht der verwendeten Cookies" }] } ``` --- ## Query-Parameter ### Filterung (where) ```bash # Equals ?where[field][equals]=value # Not equals ?where[field][not_equals]=value # Greater than ?where[field][greater_than]=10 # Less than ?where[field][less_than]=100 # Contains (Text) ?where[field][contains]=suchtext # In (Array) ?where[field][in]=value1,value2 # AND-Verknüpfung ?where[and][0][field1][equals]=value1&where[and][1][field2][equals]=value2 # OR-Verknüpfung ?where[or][0][field1][equals]=value1&where[or][1][field2][equals]=value2 ``` ### Sortierung (sort) ```bash # Aufsteigend ?sort=fieldName # Absteigend ?sort=-fieldName # Mehrere Felder ?sort=-publishedAt,title ``` ### Pagination ```bash # Limit ?limit=10 # Seite ?page=2 # Beide kombiniert ?limit=10&page=2 ``` ### Depth (Relations laden) ```bash # Keine Relations laden ?depth=0 # Eine Ebene ?depth=1 # Zwei Ebenen ?depth=2 ``` ### Locale (Mehrsprachigkeit) ```bash # Deutsch (Standard) ?locale=de # Englisch ?locale=en # Alle Sprachen ?locale=all ``` --- ## Fehlerbehandlung ### Häufige Fehlercodes | Code | Bedeutung | |------|-----------| | 200 | Erfolg | | 201 | Erstellt | | 400 | Ungültige Anfrage | | 401 | Nicht authentifiziert | | 403 | Nicht autorisiert | | 404 | Nicht gefunden | | 500 | Server-Fehler | ### Fehler-Response ```json { "errors": [ { "message": "Du hast keine Berechtigung, diese Aktion auszuführen." } ] } ``` ### Validierungsfehler ```json { "errors": [ { "message": "The following field is invalid: email", "field": "email" } ] } ``` --- ## Multi-Tenant Hinweise ### Tenant-ID ermitteln 1. **Admin Panel:** Unter "Settings → Tenants" die ID ablesen 2. **API:** ```bash curl "https://pl.porwoll.tech/api/tenants" \ -H "Authorization: JWT your-token" ``` ### Domain-basierter Zugriff Wenn ein Frontend über eine Tenant-Domain (z.B. `porwoll.de`) zugreift, wird der Tenant automatisch erkannt und nur dessen Daten zurückgegeben. ### Manueller Tenant-Filter Für direkte API-Zugriffe: ```bash curl "https://pl.porwoll.tech/api/posts?where[tenant][equals]=1" ``` ### Super Admin User mit `isSuperAdmin: true` haben Zugriff auf alle Tenants und können neue Tenants erstellen/bearbeiten. --- ## Beispiel: Frontend-Integration ### Next.js Beispiel ```typescript // lib/api.ts const API_BASE = 'https://pl.porwoll.tech/api' const TENANT_ID = 1 export async function getPosts(type?: string, limit = 10, locale = 'de') { const params = new URLSearchParams({ 'where[tenant][equals]': String(TENANT_ID), 'where[status][equals]': 'published', limit: String(limit), sort: '-publishedAt', depth: '1', locale, }) if (type && type !== 'all') { params.append('where[type][equals]', type) } const res = await fetch(`${API_BASE}/posts?${params}`) return res.json() } export async function getTestimonials(limit = 6) { const params = new URLSearchParams({ 'where[tenant][equals]': String(TENANT_ID), 'where[isActive][equals]': 'true', limit: String(limit), sort: 'order', depth: '1', }) const res = await fetch(`${API_BASE}/testimonials?${params}`) return res.json() } export async function getCookieConfig() { const params = new URLSearchParams({ 'where[tenant][equals]': String(TENANT_ID), }) const res = await fetch(`${API_BASE}/cookie-configurations?${params}`) const data = await res.json() return data.docs[0] || null } export async function getCookieInventory() { const params = new URLSearchParams({ 'where[tenant][equals]': String(TENANT_ID), 'where[isActive][equals]': 'true', sort: 'category', }) const res = await fetch(`${API_BASE}/cookie-inventory?${params}`) return res.json() } export async function logConsent( categories: Record, revision: number, clientRef: string, apiKey: string ) { const res = await fetch(`${API_BASE}/consent-logs`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey, }, body: JSON.stringify({ tenant: TENANT_ID, clientRef, categories, revision, userAgent: navigator.userAgent, }), }) return res.json() } export async function subscribeNewsletter(email: string, source: string) { const res = await fetch(`${API_BASE}/newsletter-subscribers`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tenant: TENANT_ID, email, source, }), }) return res.json() } ``` ### React Component ```tsx // components/NewsletterForm.tsx import { useState } from 'react' import { subscribeNewsletter } from '@/lib/api' export function NewsletterForm() { const [email, setEmail] = useState('') const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle') const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setStatus('loading') try { const result = await subscribeNewsletter(email, 'website-footer') if (result.doc) { setStatus('success') setEmail('') } else { setStatus('error') } } catch { setStatus('error') } } return (
setEmail(e.target.value)} placeholder="Ihre E-Mail-Adresse" required /> {status === 'success' &&

Vielen Dank! Bitte bestätigen Sie Ihre E-Mail.

} {status === 'error' &&

Es ist ein Fehler aufgetreten.

}
) } ``` --- ## Rate Limiting Aktuell gibt es kein Rate Limiting. Für Production-Umgebungen sollte ein Reverse Proxy (z.B. Caddy, nginx) mit Rate Limiting konfiguriert werden. --- ## Alle Collections Übersicht | Collection | Slug | Öffentlich | Beschreibung | |------------|------|------------|--------------| | Users | `users` | Nein | Benutzer-Verwaltung | | Tenants | `tenants` | Nein | Mandanten | | Media | `media` | Ja | Bilder und Dateien | | Pages | `pages` | Ja | Seiten mit Blocks | | Posts | `posts` | Ja | Blog-Artikel und News | | Categories | `categories` | Ja | Kategorien für Posts | | Social Links | `social-links` | Ja | Social Media Links | | Testimonials | `testimonials` | Ja | Kundenbewertungen | | Newsletter Subscribers | `newsletter-subscribers` | Create: Ja | Newsletter-Anmeldungen | | Cookie Configurations | `cookie-configurations` | Ja (Tenant-isoliert) | Cookie-Banner Konfiguration | | Cookie Inventory | `cookie-inventory` | Ja (Tenant-isoliert) | Cookie-Dokumentation | | Consent Logs | `consent-logs` | Nein (API-Key) | WORM Audit-Trail | | Privacy Policy Settings | `privacy-policy-settings` | Ja (Tenant-isoliert) | Datenschutzerklärungs-Konfiguration | --- ## Weitere Ressourcen - **Admin Panel:** https://pl.porwoll.tech/admin - **Payload CMS Docs:** https://payloadcms.com/docs - **GraphQL Playground:** https://pl.porwoll.tech/api/graphql (wenn aktiviert)