⚡ You're viewing a live demo of ChimerAI. Data resets daily at midnight UTC.Get the CLI →

ChimerAI Chat Widget — Developer Integration Manual

Version: 1.1
Stand: April 2026
Zielgruppe: Entwickler, die den ChimerAI-Chat in eine bestehende externe Applikation einbetten möchten (React, Vue, Angular, Blazor, WordPress, statisches HTML, etc.)


Inhaltsverzeichnis

  1. Übersicht
  2. Voraussetzungen
  3. API-Key erstellen
  4. Integration: Script-Tag (einfachste Variante)
  5. Integration: Programmatisch mit ChimerAI.mount()
  6. Integration: Async Loader (Performance)
  7. Integration: React / Next.js
  8. Konfiguration & Optionen
  9. Programmatische Steuerung
  10. Theming (Dark / Light / Auto)
  11. CORS & Netzwerk-Setup
  12. Sicherheit & Best Practices
  13. Backend-Proxy-Pattern (Key verstecken)
  14. Troubleshooting
  15. API-Referenz

1. Übersicht

Das ChimerAI Chat Widget ist ein selbstständiges JavaScript-Bundle (Web Component mit Shadow DOM), das in jede beliebige Webseite eingebettet werden kann — unabhängig vom verwendeten Framework.

Eigenschaften:

  • ✅ Kein React, kein Tailwind, keine externen Abhängigkeiten nötig
  • ✅ Shadow DOM — kein CSS-Leak in deine bestehende App
  • ✅ SSE-Streaming (Server-Sent Events) für Echtzeit-Antworten
  • ✅ Dark/Light/Auto Theme
  • ✅ API-Key-basierte Authentifizierung
  • ✅ Programmatische Steuerung (Nachrichten senden, Chat leeren, Model wechseln)
  • ✅ Callbacks für Events (onReady, onError, onMessageSent, onResponseReceived)

Architektur:

┌──────────────────────────┐         ┌──────────────────────────────┐
│  Deine externe App       │         │  ChimerAI App (Next.js)      │
│  (beliebiges Framework)  │         │  http://your-chimerai.com    │
│                          │         │                              │
│  ┌────────────────────┐  │  HTTPS  │  /widget/chat.js  (Bundle)   │
│  │  <chimerai-chat>   │──┼────────►│  /api/v1/chat/stream (SSE)   │
│  │  Web Component     │  │  API    │  /api/v1/api-keys  (Mgmt)    │
│  │  Shadow DOM        │  │  Key    │                              │
│  └────────────────────┘  │         │  Auth: x-api-key Header      │
│                          │         │  CORS: CORS_ALLOWED_ORIGINS │
└──────────────────────────┘         └──────────────────────────────┘

Provider-Routing: Die Widget-Route /api/v1/chat/stream ruft LLM-Provider direkt an — ohne Umweg über den Python AI-Service. Nur für Provider, die Next.js nicht direkt kennt, wird der Python-Service als Fallback genutzt:

Widget-Request → Next.js /api/v1/chat/stream
                         │
                    provider.type?
                         │
                    ├── "openai" / "custom"  → direkt api.openai.com (oder custom baseUrl)
                    ├── "anthropic"          → direkt api.anthropic.com
                    └── alles andere         → Python AI-Service (AI_SERVICE_URL)
ProviderTyp im SystemDirekter Call?Python-Service nötig?
OpenAIopenai✅ JaNein
Claudeanthropic✅ JaNein
DeepSeekcustom✅ JaNein (OpenAI-kompatible API)
Groqgroq❌ NeinJa, muss laufen
Ollamaollama❌ NeinJa, muss laufen
Googlegoogle❌ NeinJa, muss laufen

💡 DeepSeek hat eine OpenAI-kompatible API. Einfach als "Custom"-Provider mit baseUrl: https://api.deepseek.com/v1 einrichten — funktioniert ohne Python-Service.


2. Voraussetzungen

ChimerAI-App (Backend-Seite)

  • Eine laufende ChimerAI-App (erstellt mit chimerai create)
  • Datenbank: SQLite (Default für Entwicklung) oder PostgreSQL (Production)
    • SQLite: chimerai create my-app --sqlite — kein Docker nötig, setzt automatisch DATABASE_URL=file:./dev.db + provider = "sqlite"
    • PostgreSQL: chimerai create my-app (Default) — benötigt Docker oder lokale PostgreSQL-Instanz
  • Mindestens ein konfigurierter AI-Provider (z.B. OpenAI, Anthropic)
  • Widget installiert: chimerai add chat-widget (oder bei chimerai create automatisch dabei)
    • Erzeugt automatisch: middleware.ts (CORS-Headers), CORS_ALLOWED_ORIGINS in .env
    • Wichtig: Nach Installation pnpm build ausführen, damit die Middleware aktiv wird

Externe App (Client-Seite)

  • Beliebige Webseite/App (HTML, React, Vue, Angular, Blazor, WordPress, etc.)
  • Zugriff auf die ChimerAI-App-URL (Netzwerk/Internet)

3. API-Key erstellen

Über die Web-UI

  1. Öffne deine ChimerAI-App im Browser
  2. Navigiere zu Einstellungen → API Keys (/settings/api-keys)
  3. Klicke + Create Key
  4. Konfiguriere:
    • Name: z.B. "Blog Chat Widget"
    • Scope: "Chat Widget" (empfohlen) oder Custom-Scopes
    • Expiration: 90 Tage (empfohlen)
  5. Kopiere den Key sofort — er wird nur einmal angezeigt!

Über die API

# API-Key erstellen (als eingeloggter User)
curl -X POST https://your-chimerai-app.com/api/v1/api-keys \
  -H "Content-Type: application/json" \
  -H "Cookie: next-auth.session-token=YOUR_SESSION" \
  -d '{
    "name": "Blog Widget",
    "scopes": ["chat"],
    "expiresInDays": 90
  }'

# Response:
# { "key": "sk_live_a1b2c3d4...", "message": "Key created..." }

⚠️ Wichtig: Der vollständige Key (sk_live_...) wird nur einmal bei Erstellung angezeigt. Danach ist nur noch der Prefix sichtbar. Verloren = neuen Key erstellen.

💡 Hinweis: Der sk_live_... Key ist ein interner ChimerAI API-Key — er authentifiziert das Widget gegenüber dem Next.js Backend. Er hat nichts mit dem Provider-Key (z.B. OpenAI sk-proj-...) zu tun. Der Provider-Key bleibt sicher im Backend, verschlüsselt in der Datenbank.

Widget (sk_live_...) → Next.js Backend (sk-proj-...) → OpenAI/Anthropic/etc.

4. Integration: Script-Tag

Die einfachste Variante — funktioniert in jeder HTML-Seite:

<!DOCTYPE html>
<html>
  <head>
    <title>Meine Webseite</title>
  </head>
  <body>
    <h1>Willkommen auf meiner Seite</h1>

    <!-- 1. Container für den Chat -->
    <div id="chimerai-chat" style="width: 400px; height: 600px;"></div>

    <!-- 2. Widget-Script laden -->
    <script src="https://your-chimerai-app.com/widget/chat.js"></script>

    <!-- 3. Widget initialisieren -->
    <script>
      ChimerAI.mount('#chimerai-chat', {
        apiKey: 'sk_live_DEIN_API_KEY',
        endpoint: 'https://your-chimerai-app.com',
        theme: 'dark',
        title: 'AI Assistant',
      });
    </script>
  </body>
</html>

Auto-Mount mit Data-Attributen

Alternativ ohne JavaScript — rein deklarativ:

<div
  data-chimerai-chat
  data-api-key="sk_live_DEIN_API_KEY"
  data-endpoint="https://your-chimerai-app.com"
  data-theme="dark"
  data-title="AI Assistant"
  style="width: 400px; height: 600px;"
></div>

<script src="https://your-chimerai-app.com/widget/chat.js"></script>

Das Widget erkennt data-chimerai-chat automatisch und mountet sich selbst.


5. Integration: Programmatisch

ChimerAI.mount() gibt ein Control-Handle zurück:

<div id="chat" style="width: 400px; height: 600px;"></div>

<script src="https://your-chimerai-app.com/widget/chat.js"></script>
<script>
  const chat = ChimerAI.mount('#chat', {
    apiKey: 'sk_live_DEIN_API_KEY',
    endpoint: 'https://your-chimerai-app.com',
    theme: 'auto',
    title: 'Support Chat',
    model: 'gpt-4',
    placeholder: 'Frag mich etwas...',

    // Callbacks
    onReady: () => {
      console.log('Widget geladen');
    },
    onError: (error) => {
      console.error('Widget-Fehler:', error.message);
    },
    onMessageSent: (msg) => {
      console.log('User schrieb:', msg.content);
    },
    onResponseReceived: (msg) => {
      console.log('AI antwortete:', msg.content);
    },
  });

  // Programmatische Steuerung
  chat.sendMessage('Hallo!');
  chat.setModel('claude-3-haiku');
  chat.clearMessages();
  chat.destroy(); // Widget entfernen
</script>

6. Integration: Async Loader

Für optimale Performance — das Widget wird asynchron nachgeladen und erscheint als Floating-Button unten rechts:

<!-- Ein einziger Script-Tag — das war's -->
<script
  src="https://your-chimerai-app.com/widget/loader.js"
  data-api-key="sk_live_DEIN_API_KEY"
  data-theme="dark"
  data-title="Hilfe"
></script>

Der Loader:

  1. Lädt chat.js asynchron nach
  2. Erstellt einen Container (fixed, bottom-right, 380×520px)
  3. Mountet das Widget automatisch

Ideal für: Blogs, Landing Pages, Marketing-Seiten.


7. Integration: React / Next.js

Next.js (App Router)

// app/page.tsx
import Script from 'next/script';

export default function Home() {
  return (
    <div>
      <h1>Meine Next.js App</h1>

      {/* Chat Container */}
      <div id="chimerai-chat" style={{ width: 400, height: 600 }} />

      {/* Widget laden */}
      <Script
        src="http://localhost:3000/widget/chat.js"
        strategy="afterInteractive"
        onLoad={() => {
          (window as any).ChimerAI.mount('#chimerai-chat', {
            apiKey: 'sk_live_DEIN_API_KEY',
            endpoint: 'http://localhost:3000',
            theme: 'dark',
            title: 'AI Chat',
          });
        }}
      />
    </div>
  );
}

React (Vite / CRA)

// src/ChatWidget.tsx
import { useEffect, useRef } from 'react';

declare global {
  interface Window {
    ChimerAI: {
      mount: (
        selector: string | Element,
        config: Record<string, any>
      ) => {
        sendMessage: (msg: string) => void;
        clearMessages: () => void;
        setModel: (model: string) => void;
        destroy: () => void;
      };
      version: string;
    };
  }
}

export function ChatWidget() {
  const containerRef = useRef<HTMLDivElement>(null);
  const chatRef = useRef<ReturnType<typeof window.ChimerAI.mount> | null>(null);

  useEffect(() => {
    // Script laden
    const script = document.createElement('script');
    script.src = 'https://your-chimerai-app.com/widget/chat.js';
    script.onload = () => {
      if (containerRef.current && window.ChimerAI) {
        chatRef.current = window.ChimerAI.mount(containerRef.current, {
          apiKey: 'sk_live_DEIN_API_KEY',
          endpoint: 'https://your-chimerai-app.com',
          theme: 'auto',
          title: 'AI Chat',
        });
      }
    };
    document.head.appendChild(script);

    return () => {
      chatRef.current?.destroy();
      script.remove();
    };
  }, []);

  return <div ref={containerRef} style={{ width: 400, height: 600 }} />;
}

Vue 3

<!-- ChatWidget.vue -->
<template>
  <div ref="chatContainer" style="width: 400px; height: 600px;" />
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';

const chatContainer = ref<HTMLElement | null>(null);
let chatHandle: any = null;

onMounted(() => {
  const script = document.createElement('script');
  script.src = 'https://your-chimerai-app.com/widget/chat.js';
  script.onload = () => {
    if (chatContainer.value && (window as any).ChimerAI) {
      chatHandle = (window as any).ChimerAI.mount(chatContainer.value, {
        apiKey: 'sk_live_DEIN_API_KEY',
        endpoint: 'https://your-chimerai-app.com',
        theme: 'dark',
      });
    }
  };
  document.head.appendChild(script);
});

onUnmounted(() => {
  chatHandle?.destroy();
});
</script>

Angular

// chat-widget.component.ts
import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-chat-widget',
  template: '<div #chatContainer style="width: 400px; height: 600px;"></div>',
})
export class ChatWidgetComponent implements AfterViewInit, OnDestroy {
  @ViewChild('chatContainer') container!: ElementRef;
  private chatHandle: any;
  private script!: HTMLScriptElement;

  ngAfterViewInit() {
    this.script = document.createElement('script');
    this.script.src = 'https://your-chimerai-app.com/widget/chat.js';
    this.script.onload = () => {
      this.chatHandle = (window as any).ChimerAI.mount(this.container.nativeElement, {
        apiKey: 'sk_live_DEIN_API_KEY',
        endpoint: 'https://your-chimerai-app.com',
        theme: 'auto',
      });
    };
    document.head.appendChild(this.script);
  }

  ngOnDestroy() {
    this.chatHandle?.destroy();
    this.script?.remove();
  }
}

Blazor (C# / .NET)

Getestet mit: .NET 8 Blazor Server (Interactive Server Rendering), Port 3002 → ChimerAI auf Port 3001

@* Components/Pages/Chat.razor *@
@page "/chat"
@inject IJSRuntime JSRuntime

<h3>ChimerAI Chat</h3>
<div id="chimerai-chat" style="width: 100%; max-width: 500px; height: 600px;"></div>

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JSRuntime.InvokeVoidAsync("loadChimerAI");
        }
    }
}
<!-- App.razor oder _Host.cshtml (Blazor Server) bzw. wwwroot/index.html (Blazor WASM) -->
<script>
  function loadChimerAI() {
    if (window.ChimerAI) {
      ChimerAI.mount('#chimerai-chat', {
        apiKey: 'sk_live_DEIN_API_KEY',
        endpoint: 'http://localhost:3001', // ChimerAI Next.js App
        theme: 'dark',
      });
      return;
    }
    var s = document.createElement('script');
    s.src = 'http://localhost:3001/widget/chat.js'; // Widget-Bundle von ChimerAI laden
    s.onload = function () {
      ChimerAI.mount('#chimerai-chat', {
        apiKey: 'sk_live_DEIN_API_KEY',
        endpoint: 'http://localhost:3001',
        theme: 'dark',
      });
    };
    document.head.appendChild(s);
  }
</script>

Blazor-Setup Checkliste:

  1. ChimerAI-App (helloworld) auf Port 3001 starten: pnpm build && pnpm start
  2. In der ChimerAI-App unter /settings/api-keys einen API-Key erstellen
  3. CORS_ALLOWED_ORIGINS=http://localhost:3002 in .env der ChimerAI-App setzen
  4. ChimerAI-App neu starten (nach .env-Änderung!)
  5. Blazor-App auf Port 3002 starten: dotnet run
  6. Im Browser http://localhost:3002/chat öffnen

8. Konfiguration & Optionen

mount() Optionen

OptionTypDefaultBeschreibung
apiKeystringPflicht. API-Key (sk_live_... oder sk_test_...)
endpointstring'' (same-origin)URL der ChimerAI-App. Leer = same-origin Requests
theme'light' | 'dark' | 'auto''auto'Theme. auto folgt dem System (prefers-color-scheme)
modelstring''AI-Model (z.B. 'gpt-4o', 'claude-sonnet-4-20250514'). Optional — leer = Provider-Default (s.u.)
titlestring'AI Chat'Titel in der Header-Leiste
placeholderstring'Type a message...'Placeholder-Text im Input-Feld
widthstringCSS-Breite des Widgets (z.B. '400px', '100%')
heightstringCSS-Höhe des Widgets (z.B. '600px', '100vh')
onReady() => voidCallback wenn Widget geladen ist
onError(error: Error) => voidCallback bei Fehlern
onMessageSent(msg: {role, content}) => voidCallback wenn User Nachricht sendet
onResponseReceived(msg: {role, content}) => voidCallback wenn AI-Antwort komplett

Data-Attribute (für Auto-Mount)

AttributEntspricht
data-chimerai-chatAktiviert Auto-Mount (Pflicht)
data-api-keyapiKey
data-endpointendpoint
data-themetheme
data-modelmodel
data-titletitle
data-placeholderplaceholder

9. Programmatische Steuerung

const chat = ChimerAI.mount('#container', { ... });

// Nachricht senden (löst Chat-Streaming aus)
chat.sendMessage('Erkläre mir Quantencomputing');

// Alle Nachrichten löschen
chat.clearMessages();

// Model wechseln (für nächste Nachricht)
chat.setModel('claude-3-haiku');

// Widget komplett entfernen
chat.destroy();

// Version prüfen
console.log(ChimerAI.version); // "1.0.0"

10. Theming

Dark Theme

ChimerAI.mount('#chat', { theme: 'dark', ... });

Light Theme

ChimerAI.mount('#chat', { theme: 'light', ... });

Auto (System-Preference)

ChimerAI.mount('#chat', { theme: 'auto', ... });

Folgt prefers-color-scheme — schaltet automatisch um wenn der User sein OS-Theme ändert.

CSS-Isolation

Das Widget nutzt Shadow DOM — deine CSS-Styles beeinflussen das Widget nicht und umgekehrt. Es gibt kein CSS-Leak in beide Richtungen.


11. CORS & Netzwerk-Setup

Middleware & CORS (automatisch)

chimerai add chat-widget generiert automatisch:

  • middleware.ts im Projekt-Root — setzt CORS-Headers für /api/v1/* Routen + Security-Headers für alle anderen Routen
  • CORS_ALLOWED_ORIGINS in .env — komma-getrennte Liste erlaubter Origins (Default: *)

⚠️ Wichtig: Nach chimerai add chat-widget muss die App neu gebaut werden (pnpm build), damit die Middleware aktiv wird. pnpm dev (Dev-Server) kompiliert on-the-fly, pnpm start (Production) braucht ein vorheriges pnpm build.

ChimerAI-App konfigurieren

Damit das Widget von einer externen Domain API-Requests machen kann, muss die ChimerAI-App die Domain erlauben.

In .env der ChimerAI-App:

# Komma-getrennte Liste erlaubter Widget-Origins (primäre Variable)
CORS_ALLOWED_ORIGINS=https://my-blog.com,https://my-shop.com

# Legacy-Alias (wird ebenfalls gelesen, beide werden gemergt)
# WIDGET_ALLOWED_ORIGINS=http://localhost:3002

💡 Die Middleware liest beide Variablen (CORS_ALLOWED_ORIGINS + WIDGET_ALLOWED_ORIGINS) und mergt sie. Für neue Projekte empfehlen wir CORS_ALLOWED_ORIGINS.

Nach Änderung: ChimerAI-App neu starten (pnpm build && pnpm start oder pnpm dev neustarten).

Was passiert bei fehlender CORS-Konfiguration?

Browser blockiert den Request mit:

Access to fetch at 'https://chimerai-app.com/api/v1/chat/stream'
from origin 'https://my-blog.com' has been blocked by CORS policy

Lösung: Domain zu CORS_ALLOWED_ORIGINS in .env hinzufügen und App neustarten.

Lokale Entwicklung

Für lokales Testen:

CORS_ALLOWED_ORIGINS=http://localhost:3002,http://localhost:5173,http://localhost:4200
PortTypisches Framework
3002Blazor (getestet ✅)
5173Vite (React/Vue/Svelte)
4200Angular
5000Blazor (alternativ)
8080WordPress (Local)

Provider-Kompatibilität & Besonderheiten

Die Widget-Route /api/v1/chat/stream unterstützt verschiedene LLM-Provider mit unterschiedlichen Eigenheiten:

ProviderEinrichtungBesonderheiten
OpenAI (gpt-4, gpt-4o-mini, ...)Provider-Typ: openai, API-Key: sk-proj-...Standard-Referenz, keine Besonderheiten
Anthropic/Claude (claude-3.5-sonnet, ...)Provider-Typ: anthropic, API-Key: sk-ant-...max_tokens ist Pflichtfeld (intern auf 4096 gesetzt); System-Messages werden separat behandelt
DeepSeek (deepseek-chat, deepseek-coder)Provider-Typ: custom, baseUrl: https://api.deepseek.com/v1OpenAI-kompatible API; höhere Latenz (Server in Asien); manche Features wie json_mode eingeschränkt
Groq, Ollama, Google, etc.Provider-Typ: groq/ollama/googleBenötigen den Python AI-Service (AI_SERVICE_URL), der über LiteLLM alle Provider abstrahiert

⚠️ Wichtig: Für OpenAI, Claude und DeepSeek wird der Python AI-Service nicht benötigt. Die Next.js-App kommuniziert direkt mit dem Provider. Nur für andere Provider-Typen muss der Python-Service laufen (http://localhost:8001).

💡 ChatGPT ≠ OpenAI API: Ein ChatGPT Plus/Pro-Abo gibt dir keinen API-Key. Du brauchst einen separaten OpenAI Platform API-Key (sk-proj-...) von platform.openai.com mit eigener Abrechnung.

Model-Auflösung (Prioritätskette)

Wenn das Widget einen Chat-Request sendet, wird das Model wie folgt bestimmt:

1. model aus Widget-Config      → Widget-Einbetter hat explizit gewählt
                                   z.B. ChimerAI.mount({ model: 'gpt-4o' })
     ↓ (falls leer)
2. provider.config.defaultModel → Provider-Admin hat Default konfiguriert
                                   z.B. "gpt-4o-mini" in Provider-Settings
     ↓ (falls leer)
3. 400 Error                    → Klare Fehlermeldung:
                                   "No model specified and no default model
                                    configured for this provider"

💡 Kein hardcodierter Modellname — da der Provider OpenAI, Claude, DeepSeek, Ollama oder ein beliebiges LLM sein kann, gibt es keinen universellen Default. Der Provider-Admin legt das Default-Model in den Provider-Settings fest.

Default-Model im Provider setzen:

  1. Öffne deine ChimerAI-App im Browser
  2. Navigiere zu Providers (/providers)
  3. Klicke auf Edit bei deinem Provider (z.B. OpenAI)
  4. Trage im Feld Default Model den Modellnamen ein (z.B. gpt-4o-mini)
  5. Save klicken

💡 Beim Erstellen eines Projekts mit chimerai create wird das Default-Model automatisch gesetzt: OpenAI → gpt-4o-mini, Anthropic → claude-sonnet-4-20250514.

Beispiel: Widget ohne Model (nutzt Provider-Default):

// Minimal-Konfiguration — Server wählt das Default-Model des Providers
ChimerAI.mount('#chat', {
  apiKey: 'sk_live_...',
  endpoint: 'http://localhost:3001',
});

Beispiel: Widget mit explizitem Model (überschreibt Provider-Default):

// Explizit — überschreibt den Provider-Default
ChimerAI.mount('#chat', {
  apiKey: 'sk_live_...',
  endpoint: 'http://localhost:3001',
  model: 'gpt-4o',
});

12. Sicherheit & Best Practices

API-Key Sichtbarkeit

⚠️ API-Keys im Client-Side JavaScript sind sichtbar (DevTools, View Source). Das ist ein akzeptiertes Risiko — vergleichbar mit Google Maps Keys oder Firebase Client-Keys.

Empfohlene Schutzmaßnahmen

MaßnahmeWarumWie
Minimale ScopesBegrenzt was der Key kannNur chat Scope, nie *
allowedOriginsBlockiert Nutzung von fremden DomainsIn API-Key-Management setzen
Kurze ExpirationBegrenzt Zeitfenster bei Leak30-90 Tage, dann rotieren
Separate KeysEin Key pro DomainBei Kompromittierung nur eine Domain betroffen
Rate-LimitingBegrenzt MissbrauchAutomatisch: 60 req/min pro API-Key
MonitoringUngewöhnliche Nutzung erkennenlastUsedAt in Key-Management prüfen

Was allowedOrigins schützt (und was nicht)

SzenarioGeschützt?
Fremde Website macht Cross-Origin-Request✅ Ja (Browser setzt Origin-Header)
Script-Kiddie nutzt Key in Postman✅ Teilweise (Standard-Origin wird blockiert)
Angreifer nutzt cURL mit gefälschtem Origin❌ Nein (Origin ist server-seitig frei setzbar)
Key in öffentlichem Git-Repo❌ Nein (Key muss revoked werden)

Für höchste Sicherheit: Backend-Proxy

Wenn der Key komplett unsichtbar sein muss → siehe Kapitel 13.


13. Backend-Proxy-Pattern

Für interne Tools, B2B-Apps oder wenn der API-Key nicht im Client-Code erscheinen darf.

Prinzip: Dein eigenes Backend hält den Key als Environment Variable und leitet Requests weiter.

Browser (KEIN Key) → Dein Backend /api/proxy/chat → ChimerAI API (Key als Env-Var)

Widget-Konfiguration (kein apiKey!)

ChimerAI.mount('#chat', {
  endpoint: '/api/proxy', // Dein eigenes Backend
  theme: 'dark',
  // KEIN apiKey — der liegt auf dem Server
});

Proxy-Implementierung

Node.js / Express

// server.js
const express = require('express');
const app = express();

const CHIMERAI_URL = process.env.CHIMERAI_URL; // z.B. https://my-chimerai.com
const CHIMERAI_KEY = process.env.CHIMERAI_API_KEY; // sk_live_...

app.post('/api/proxy/api/v1/chat/stream', async (req, res) => {
  const response = await fetch(`${CHIMERAI_URL}/api/v1/chat/stream`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': CHIMERAI_KEY,
    },
    body: JSON.stringify(req.body),
  });

  // SSE-Stream weiterleiten
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  response.body.pipeTo(
    new WritableStream({
      write(chunk) {
        res.write(chunk);
      },
      close() {
        res.end();
      },
    })
  );
});

ASP.NET Core (Blazor)

[ApiController]
[Route("api/proxy")]
public class ChatProxyController : ControllerBase
{
    private readonly IConfiguration _config;
    private readonly HttpClient _http;

    public ChatProxyController(IConfiguration config, HttpClient http)
    {
        _config = config;
        _http = http;
    }

    [HttpPost("api/v1/chat/stream")]
    public async Task ProxyChat()
    {
        var apiKey = _config["ChimerAI:ApiKey"];
        var baseUrl = _config["ChimerAI:Url"];

        using var reader = new StreamReader(Request.Body);
        var body = await reader.ReadToEndAsync();

        var request = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}/api/v1/chat/stream")
        {
            Content = new StringContent(body, Encoding.UTF8, "application/json"),
        };
        request.Headers.Add("x-api-key", apiKey);

        var response = await _http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

        Response.ContentType = "text/event-stream";
        await response.Content.CopyToAsync(Response.Body);
    }
}

Python (FastAPI)

import httpx
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse

app = FastAPI()
CHIMERAI_URL = os.environ["CHIMERAI_URL"]
CHIMERAI_KEY = os.environ["CHIMERAI_API_KEY"]

@app.post("/api/proxy/api/v1/chat/stream")
async def proxy_chat(request: Request):
    body = await request.body()
    async with httpx.AsyncClient() as client:
        response = await client.post(
            f"{CHIMERAI_URL}/api/v1/chat/stream",
            content=body,
            headers={
                "Content-Type": "application/json",
                "x-api-key": CHIMERAI_KEY,
            },
            timeout=120,
        )

    async def stream():
        async for chunk in response.aiter_bytes():
            yield chunk

    return StreamingResponse(stream(), media_type="text/event-stream")

14. Troubleshooting

Widget wird nicht angezeigt

SymptomUrsacheLösung
Leerer ContainerScript nicht geladenDevTools → Network → Prüfe ob chat.js geladen wird (200 OK)
ChimerAI is not definedScript noch nicht geladenstrategy="afterInteractive" nutzen oder auf onload warten
Widget ohne InhaltContainer hat keine HöheExplizit height setzen: style="height: 600px"

CORS-Fehler

FehlermeldungLösung
blocked by CORS policyDomain zu CORS_ALLOWED_ORIGINS in .env der ChimerAI-App hinzufügen
No 'Access-Control-Allow-Origin' headerApp neu bauen (pnpm build) und neustarten — nicht nur Hot-Reload!
Preflight (OPTIONS) schlägt fehlMiddleware der ChimerAI-App prüfen — x-api-key muss in Allow-Headers stehen
Middleware wird nicht erkanntpnpm build vergessen? pnpm start braucht ein vorheriges pnpm build
Middleware-Manifest leer.next-Ordner löschen, pnpm build erneut ausführen

Chat-Fehler

SymptomUrsacheLösung
⚠ Request failed (401)Ungültiger oder fehlender API-KeyKey in API-Key-Management prüfen, nicht abgelaufen/revoked?
⚠ Request failed (403)Key hat nicht den richtigen ScopeKey muss chat Scope haben
⚠ Request failed (429)Rate-Limit erreichtWarten (60 req/min pro API-Key)
⚠ Connection failedChimerAI-App nicht erreichbarURL/Port prüfen, ChimerAI-App gestartet?
Keine Antwort (Streaming stoppt)Kein AI-Provider konfiguriertIn ChimerAI-App mindestens einen Provider einrichten

Debug-Modus

Öffne die Browser DevTools:

  1. Console — Fehler-Meldungen des Widgets
  2. Networkchat.js laden (200?) + chat/stream Request (200? Headers?)
  3. Elements<chimerai-chat> suchen → Shadow DOM aufklappen → DOM inspizieren

15. API-Referenz

Globales Objekt: window.ChimerAI

ChimerAI.mount(selector, config)

Mountet ein Chat-Widget in den angegebenen Container.

Parameter:

ParameterTypBeschreibung
selectorstring | ElementCSS-Selector oder DOM-Element
configobjectKonfiguration (siehe Kapitel 8)

Returns: ChatHandle

interface ChatHandle {
  sendMessage(content: string): void;
  clearMessages(): void;
  setModel(model: string): void;
  destroy(): void;
}

ChimerAI.version

Gibt die Widget-Version zurück (string).

SSE-Stream-Format

Das Widget kommuniziert via Server-Sent Events. Jede Zeile:

data: {"type":"token","content":"Hallo"}
data: {"type":"token","content":" Welt"}
data: {"type":"done","conversationId":"abc123","model":"gpt-4","tokens":42}
data: [DONE]
typeBeschreibung
tokenEin Streaming-Token (Textfragment)
doneStream abgeschlossen — enthält Metadaten
errorFehler — enthält message
infoInfo-Nachricht (optional)

HTTP-Endpunkte

EndpunktMethodeHeaderBeschreibung
/widget/chat.jsGETWidget-Bundle (statische Datei)
/api/v1/chat/streamPOSTx-api-key, Content-Type: application/jsonChat-Streaming (SSE)
/api/v1/api-keysGETSession-CookieAPI-Keys auflisten
/api/v1/api-keysPOSTSession-CookieNeuen Key erstellen
/api/v1/api-keys/[id]DELETESession-CookieKey revoken

Request-Body: /api/v1/chat/stream

{
  "messages": [{ "role": "user", "content": "Hallo!" }],
  "model": "gpt-4",
  "conversationId": "optional-existing-id"
}

Rate-Limiting Headers

Bei jedem API-Response:

HeaderBeschreibung
X-RateLimit-RemainingVerbleibende Requests im Fenster
X-RateLimit-ResetUnix-Timestamp wann das Fenster zurückgesetzt wird
Retry-AfterSekunden bis zum nächsten erlaubten Request (nur bei 429)

Schnellstart-Checkliste

  • ChimerAI-App läuft und hat mindestens einen AI-Provider
  • chimerai add chat-widget ausgeführt (Widget-Dateien + middleware.ts vorhanden)
  • pnpm build in der ChimerAI-App ausgeführt (damit Middleware aktiv wird)
  • API-Key erstellt unter /settings/api-keys
  • CORS_ALLOWED_ORIGINS in .env enthält deine externe Domain (z.B. http://localhost:3002)
  • ChimerAI-App nach .env-Änderung neu gestartet (pnpm build && pnpm start)
  • <script src=".../widget/chat.js"> in deine externe App eingebaut
  • ChimerAI.mount() mit echtem API-Key und endpoint konfiguriert
  • Im Browser testen: Widget sichtbar, Chat funktioniert ✅
ChimerAI Docs · Back to Demo