Diil Docs
  1. Doku
  2. Erste Schritte

Schnellstart: deine erste Anfrage in fünf Minuten

Aktualisiert:

Fünf Minuten, eine Seite, null Admin-Panels. Am Ende dieser Anleitung kommt eine Überschrift auf deiner Website aus dem Diil CRM — und deine Redaktion kann sie ändern, indem sie einfach draufklickt. Hol dir einen Kaffee; vielleicht wirst du ihn nicht mal austrinken.

Du brauchst:

  • Zugriff auf eine Website im Diil CRM (für Experimente kannst du einfach eine neue anlegen);
  • ein Terminal mit curl oder irgendein Tool, das HTTP-Requests verschicken kann;
  • ein beliebiges Frontend: eine einfache HTML-Datei, React, Next.js, Nuxt — du hast die Wahl.

Inhalte und Key einrichten

  1. Leg Inhalte im CRM an

    Öffne deine Website im CRM und prüf, ob die Sprachen, die du brauchst, angelegt sind (sagen wir, Englisch). Dann erstelle:

    • eine Seite mit dem Marker home;
    • darin eine Section mit dem Marker hero;
    • darin einen Text-Block mit dem Marker hero_title — tipp eine Überschrift ein.

    Marker sind die Namen, über die dein Code Inhalte findet: lateinische Buchstaben, Ziffern und Unterstriche, 2–40 Zeichen, Groß- und Kleinschreibung zählt. Wähl sie wie Variablennamen — sie werden lange in deinem Code wohnen. Hier gibt's das Gesamtbild von Seiten, Sections und Blöcken.

  2. Hol dir einen Site-Key

    Geh zu Einstellungen → Content-API-Keys und erstelle einen Key. Er sieht aus wie pk_ plus 32 Hex-Zeichen und ist an genau diese eine Website gebunden.

    Ein neuer Key funktioniert meistens sofort; im schlimmsten Fall gib ihm ein paar Minuten. Beim Widerrufen ist es genauso — widerrufene Keys bekommen 401 invalid_key.

  3. Schick deinen ersten Request

    Frag die Seite home auf Englisch an:

    curl "https://back.sitecog.com/content/v1/pages/home?lang=en" \
      -H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"
    200 OKjson
    {
      "id": 26,
      "marker": "home",
      "name": "Home",
      "href": "/",
      "index": 0,
      "params": {},
      "content": {
        "hero": {
          "id": 41,
          "marker": "hero",
          "name": "Hero",
          "index": 0,
          "show": true,
          "content": {
            "hero_title": {
              "id": 95,
              "marker": "hero_title",
              "name": "Hero title",
              "type": "text",
              "multilang": true,
              "updatedAt": "2026-09-20T16:33:23.000Z",
              "content": { "en": "Earbuds that mute the city" }
            }
          }
        }
      }
    }

    Siehst du den Pfad zu deiner Überschrift? content.hero.content.hero_title.content.en — Seite → Section → Block → Sprache. Jede Seite hat genau diese Form; die Details findest du unter Seiten.

Auf deiner Seite rendern

Derselbe Request in vier Geschmacksrichtungen. Such dir deine aus — alle machen dasselbe: Seite laden und die Überschrift in ein <h1> packen.

<h1 id="hero-title"></h1>

<script type="module">
  const res = await fetch('https://back.sitecog.com/content/v1/pages/home?lang=en', {
    headers: { 'x-crm-key': 'pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40' },
  });
  const page = await res.json();
  const hero = page.content.hero.content;

  document.getElementById('hero-title').textContent = hero.hero_title.content.en;
</script>

Wohin mit dem Key

Leg den Key in eine Umgebungsvariable statt in den Code — nicht weil er geheim wäre, sondern weil dann der Wechsel der Website oder das Rotieren des Keys eine Änderung in einer einzigen Zeile ist.

.env.localbash
# .env.local (Next.js) — nur auf dem Server, landet nie im Browser
CRM_KEY=pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40

# Browser-Code braucht eine öffentliche Variable. Kein Problem: Der Key ist read-only.
NEXT_PUBLIC_CRM_KEY=pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40
# Vite: VITE_CRM_KEY=…   Nuxt: NUXT_PUBLIC_CRM_KEY=…

In einer Server Component (wie im Next.js-Tab oben) nimm die reine Server-Variable: Dann verlässt der Key nie deinen Server. Für Browser-Code ist eine öffentliche Variable völlig in Ordnung.

Einen Sprach-Helper einbauen

Text-Blöcke kommen als Sprach-Maps: { "en": "…", "de": "…" }. Die API tauscht nie eine Sprache gegen eine andere aus: Fehlt eine Übersetzung, ist sie einfach nicht da (oder ein leerer String, wenn das Feld leer gelassen wurde). Welcher Fallback greift, entscheidest du — und mit diesem winzigen Helper ist das ein Einzeiler:

// lib/t.js
// Übersetzung wählen: gewünschte Sprache → Fallback-Sprache → erste nicht leere → ''
export function t(map, lang, fallback = 'en') {
  if (!map) return '';
  return map[lang] || map[fallback] || Object.values(map).find(Boolean) || '';
}
Verwendungjs
// Sprache des Besuchers und Fallback in einem Request anfragen
const res = await fetch('https://back.sitecog.com/content/v1/pages/home?lang=de,en', {
  headers: { 'x-crm-key': 'pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40' },
});
const page = await res.json();
const hero = page.content.hero.content;

t(hero.hero_title.content, 'de');       // "Kopfhörer, die die Stadt stummschalten"
t(hero.hero_title.content, 'de', 'en'); // Deutsch noch nicht ausgefüllt? → der englische Text

Jeder Code in ?lang= muss eine aktive Sprache der Website sein, sonst bekommst du 400 unknown_lang samt Liste der verfügbaren. Lässt du lang weg, bekommst du alle aktiven Sprachen auf einmal. Die ganze Geschichte steht unter Sprachen & Fallbacks.

Live-Bearbeitung einschalten

Jetzt kommt der spaßige Teil. Deine Seite zeigt schon Inhalte aus dem CRM; jetzt lassen wir die Redaktion sie direkt auf der Seite ändern — ohne in einem Formular nach dem richtigen Feld zu suchen.

  1. Das Widget einbinden

    Ein Script, einmal pro Seite, direkt vor </body>. Es lädt den Editor nur, wenn deine Website im CRM geöffnet ist — normale Besucher laden davon kein einziges Byte.

    <script src="https://widget.sitecog.com/widget.js" defer></script>
  2. Sag dem Editor, welches Element welchen Block zeigt

    Häng data-crm-text mit dem Block-Marker an das Element, das ihn rendert. Den Text renderst du weiterhin wie bisher aus der API — das Attribut verbindet nur das Element mit dem Block.

    <body>
      <h1 data-crm-text="hero_title">Earbuds that mute the city</h1>
    
      <!-- einmal pro Seite, direkt vor </body> -->
      <script src="https://widget.sitecog.com/widget.js" defer></script>
    </body>

    Bilder, Videos, Objekte und Arrays haben eigene Attribute (data-crm-image, data-crm-video, data-crm-object, data-crm-array) — siehe Widget & Markup. Für Besucher sind die Attribute harmlos, lass sie also ruhig in Production drin.

  3. Erlaub dem CRM, deine Website einzubetten

    Der Live-Modus öffnet deine Website im CRM, in einem iframe. Dein Server muss das per frame-ancestors erlauben und darf kein X-Frame-Options: DENY oder SAMEORIGIN schicken. Sonst sagt dir das CRM, dass die Website das Einbetten verbietet.

    // next.config.js
    module.exports = {
      async headers() {
        return [{
          source: '/:path*',
          headers: [
            { key: 'Content-Security-Policy', value: "frame-ancestors 'self' https://sitecog.com" },
          ],
        }];
      },
    };
  4. Klicken, tippen, speichern

    Öffne deine Website im CRM im Live-Modus, klick auf die Überschrift, änder sie und speichere. Das CRM schreibt den Block, die Content-Version zählt hoch, deine Seite holt frische Inhalte — und die neue Überschrift ist da. 🎉

    Bonus: Setzt du data-crm-text="promo_note" auf ein Element, bevor es so einen Block gibt, kann die Redaktion den Block direkt von der Website aus anlegen.

Den Cache für die Redaktion überspringen

Wir liefern im Live-Modus nie veraltete Inhalte aus. Aber dein Cache kann das: Mit revalidate: 60 speichert jemand und sieht bis zu einer Minute lang noch den alten Text. Im CRM-Frame bekommt die URL ?crm_live=1 — nutz das, um mit cache: 'no-store' zu laden:

// app/page.tsx — jeden Cache überspringen, solange jemand aus der Redaktion zuschaut
const API = 'https://back.sitecog.com/content';

export default async function Home({ searchParams }: { searchParams: Promise<{ crm_live?: string }> }) {
  const live = (await searchParams).crm_live === '1';

  const res = await fetch(API + '/v1/pages/home?lang=en', {
    headers: { 'x-crm-key': process.env.CRM_KEY! },
    ...(live ? { cache: 'no-store' as const } : { next: { revalidate: 60 } }),
  });
  const page = await res.json();
  const hero = page.content.hero.content;

  return <h1 data-crm-text="hero_title">{hero.hero_title.content.en}</h1>;
}

Wie geht's weiter

Die Inhalte fließen, die Redaktion klickt. Hier geht's in die Tiefe: