cms.c2sgmbh/docs/anleitungen/API_ANLEITUNG.md
Martin Porwoll dbe36ad381 feat: add super admin role and update documentation
- Add isSuperAdmin field to Users collection with migration
- Update API documentation with analytics examples
- Add analytics implementation guide
- Update TODO with completed tasks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 14:26:08 +00:00

20 KiB

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.c2sgmbh.de/api


Authentifizierung

Login

curl -X POST "https://pl.c2sgmbh.de/api/users/login" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@example.com",
    "password": "your-password"
  }'

Response:

{
  "message": "Auth Passed",
  "user": {
    "id": 1,
    "email": "admin@example.com",
    "isSuperAdmin": true,
    "tenants": [...]
  },
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

Token verwenden

curl "https://pl.c2sgmbh.de/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.

# Deutsche Inhalte (Standard)
curl "https://pl.c2sgmbh.de/api/posts?locale=de"

# Englische Inhalte
curl "https://pl.c2sgmbh.de/api/posts?locale=en"

# Alle Sprachen gleichzeitig
curl "https://pl.c2sgmbh.de/api/posts?locale=all"

Users API

Aktuellen User abrufen

curl "https://pl.c2sgmbh.de/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)

curl "https://pl.c2sgmbh.de/api/tenants" \
  -H "Authorization: JWT your-token"

Tenant erstellen (Auth + SuperAdmin erforderlich)

curl -X POST "https://pl.c2sgmbh.de/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

# Alle Posts
curl "https://pl.c2sgmbh.de/api/posts"

# Nur Blog-Artikel
curl "https://pl.c2sgmbh.de/api/posts?where[type][equals]=blog"

# Nur News
curl "https://pl.c2sgmbh.de/api/posts?where[type][equals]=news"

# Nur veröffentlichte Posts
curl "https://pl.c2sgmbh.de/api/posts?where[status][equals]=published"

# Nur hervorgehobene Posts
curl "https://pl.c2sgmbh.de/api/posts?where[isFeatured][equals]=true"

# Mit Sortierung (neueste zuerst)
curl "https://pl.c2sgmbh.de/api/posts?sort=-publishedAt"

# Limitiert auf 10 Einträge
curl "https://pl.c2sgmbh.de/api/posts?limit=10"

# Pagination (Seite 2)
curl "https://pl.c2sgmbh.de/api/posts?limit=10&page=2"

# Mit Locale
curl "https://pl.c2sgmbh.de/api/posts?locale=de"

Einzelnen Post abrufen

# Nach ID
curl "https://pl.c2sgmbh.de/api/posts/1"

# Nach Slug (über Query)
curl "https://pl.c2sgmbh.de/api/posts?where[slug][equals]=mein-erster-artikel"

Post erstellen (Auth erforderlich)

curl -X POST "https://pl.c2sgmbh.de/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)

curl -X PATCH "https://pl.c2sgmbh.de/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)

curl -X DELETE "https://pl.c2sgmbh.de/api/posts/1" \
  -H "Authorization: JWT your-token"

Testimonials API

Alle Testimonials abrufen

# Alle Testimonials
curl "https://pl.c2sgmbh.de/api/testimonials"

# Nur aktive Testimonials
curl "https://pl.c2sgmbh.de/api/testimonials?where[isActive][equals]=true"

# Sortiert nach Bewertung (beste zuerst)
curl "https://pl.c2sgmbh.de/api/testimonials?sort=-rating"

# Sortiert nach eigener Reihenfolge
curl "https://pl.c2sgmbh.de/api/testimonials?sort=order"

Testimonial erstellen (Auth erforderlich)

curl -X POST "https://pl.c2sgmbh.de/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)

# Einfache Anmeldung
curl -X POST "https://pl.c2sgmbh.de/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.c2sgmbh.de/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):

{
  "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)

# Alle Subscribers
curl "https://pl.c2sgmbh.de/api/newsletter-subscribers" \
  -H "Authorization: JWT your-token"

# Nur bestätigte Subscribers
curl "https://pl.c2sgmbh.de/api/newsletter-subscribers?where[status][equals]=confirmed" \
  -H "Authorization: JWT your-token"

# Nach E-Mail suchen
curl "https://pl.c2sgmbh.de/api/newsletter-subscribers?where[email][equals]=kunde@example.com" \
  -H "Authorization: JWT your-token"

Subscriber bestätigen (Double Opt-In)

# Über Token bestätigen
curl -X PATCH "https://pl.c2sgmbh.de/api/newsletter-subscribers/1" \
  -H "Authorization: JWT your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "confirmed"
  }'

Subscriber abmelden

curl -X PATCH "https://pl.c2sgmbh.de/api/newsletter-subscribers/1" \
  -H "Authorization: JWT your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "unsubscribed"
  }'

Pages API

Seiten abrufen

# Alle Seiten
curl "https://pl.c2sgmbh.de/api/pages"

# Seite nach Slug
curl "https://pl.c2sgmbh.de/api/pages?where[slug][equals]=startseite"

# Nur veröffentlichte Seiten
curl "https://pl.c2sgmbh.de/api/pages?where[status][equals]=published"

# Mit Locale
curl "https://pl.c2sgmbh.de/api/pages?locale=de&depth=2"

Seite mit Blocks

Die Blocks werden im layout-Array zurückgegeben:

{
  "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"
      }
    ]
  }]
}

Die Cookie-Konfiguration wird automatisch nach Domain gefiltert.

# Für Frontend Cookie-Banner
curl "https://pl.c2sgmbh.de/api/cookie-configurations?where[tenant][equals]=1"

Response:

{
  "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"
    }
  }]
}

Dokumentation aller verwendeten Cookies für die Datenschutzerklärung.

# Alle Cookies eines Tenants
curl "https://pl.c2sgmbh.de/api/cookie-inventory?where[tenant][equals]=1"

# Nur aktive Cookies
curl "https://pl.c2sgmbh.de/api/cookie-inventory?where[tenant][equals]=1&where[isActive][equals]=true"

# Nach Kategorie filtern
curl "https://pl.c2sgmbh.de/api/cookie-inventory?where[tenant][equals]=1&where[category][equals]=analytics"

Response:

{
  "docs": [{
    "id": 1,
    "tenant": 1,
    "name": "_ga",
    "provider": "Google LLC",
    "category": "analytics",
    "duration": "2 Jahre",
    "description": "Wird verwendet, um Benutzer zu unterscheiden.",
    "isActive": true
  }]
}
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-Logs sind ein WORM (Write-Once-Read-Many) Audit-Trail für DSGVO-Nachweise.

curl -X POST "https://pl.c2sgmbh.de/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:

{
  "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)
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).

curl "https://pl.c2sgmbh.de/api/privacy-policy-settings?where[tenant][equals]=1"

Response:

{
  "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)

# 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)

# Aufsteigend
?sort=fieldName

# Absteigend
?sort=-fieldName

# Mehrere Felder
?sort=-publishedAt,title

Pagination

# Limit
?limit=10

# Seite
?page=2

# Beide kombiniert
?limit=10&page=2

Depth (Relations laden)

# Keine Relations laden
?depth=0

# Eine Ebene
?depth=1

# Zwei Ebenen
?depth=2

Locale (Mehrsprachigkeit)

# 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

{
  "errors": [
    {
      "message": "Du hast keine Berechtigung, diese Aktion auszuführen."
    }
  ]
}

Validierungsfehler

{
  "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:
curl "https://pl.c2sgmbh.de/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:

curl "https://pl.c2sgmbh.de/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

// lib/api.ts
const API_BASE = 'https://pl.c2sgmbh.de/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<string, boolean>,
  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

// 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 (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Ihre E-Mail-Adresse"
        required
      />
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Wird gesendet...' : 'Anmelden'}
      </button>
      {status === 'success' && <p>Vielen Dank! Bitte bestätigen Sie Ihre E-Mail.</p>}
      {status === 'error' && <p>Es ist ein Fehler aufgetreten.</p>}
    </form>
  )
}

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