Deutsch · English

Öffentliche APIs

Basis-URL für alle Routen: https://www.ostheimer.at. Maschinenlesbare Kurzübersicht zusätzlich unter /llms.txt.

1. API-Keys und Berechtigungen

Viele Endpunkte erfordern einen API-Key. Keys werden im Dashboard unter Einstellungen → Public API Clients angelegt; der Klartext-Key wird nur einmal angezeigt.

Übertragung:

  • Authorization: Bearer <API_KEY>
  • oder x-api-key: <API_KEY>

Optional kann ein einzelner Umgebungs-Key (PUBLIC_AGENT_API_KEY) mit konfigurierbaren Scopes genutzt werden – für Integrationen ohne Datenbank-Client.

Legacy Blog: BLOG_API_KEY wurde zugunsten der Scope-basierten API-Keys abgelöst und ist standardmäßig deaktiviert (BLOG_API_LEGACY_ENABLED=false).

2. Scopes (Public Agent API)

Jeder Datenbank-Client erhält eine Liste erlaubter Scopes. Zuordnung zu Routen:

ScopeEndpunkte
chat:writePOST /api/public/agent/v1/chat
tasks:writePOST /api/public/agent/v1/tasks
tasks:readGET /api/public/agent/v1/tasks/{id}
blog:readGET /api/blog/publish
blog:writePOST, PUT /api/blog/publish
blog:deleteDELETE /api/blog/publish
blog:generatePOST /api/public/agent/v1/blog/generate
media:writePOST /api/public/agent/v1/media/upload (multipart file)
emoji:readGET /api/emojis/export
emoji:lintPOST /api/emojis/lint
emoji:suggestPOST /api/emojis/suggest
emoji:mcpPOST /api/emojis/mcp

Weitere Scopes (z. B. Emoji-Plattform) können im Dashboard zugewiesen werden, sofern die zugehörigen Routen im Einsatz sind.

3. Agent API (API-Key)

POST /api/public/agent/v1/chat

Scope: chat:write. Streaming-Antwort des Agenten.

{
  "messages": [{ "role": "user", "content": "…" }],
  "contact": { "name": "…", "email": "…", "company": "…" }
}

POST /api/public/agent/v1/tasks

Scope: tasks:write. Legt eine interne Aufgabe an.

{
  "title": "…",
  "details": "…",
  "priority": "low" | "normal" | "high",
  "contactName": "…",
  "contactEmail": "…",
  "contactCompany": "…"
}

GET /api/public/agent/v1/tasks/{id}

Scope: tasks:read. Liest eine angelegte Aufgabe.

POST /api/public/agent/v1/blog/generate

Scope: blog:generate. Generiert einen Blogartikel aus einem Thema (KI + optional Bild).

{
  "topic": "…",
  "keywords": [],
  "generateImage": true,
  "imageStyle": "corporate",
  "published": false,
  "publishAt": "2026-03-20T10:00:00.000Z",
  "categories": ["KI"],
  "tags": []
}

POST /api/public/agent/v1/media/upload

Scope: media:write. multipart/form-data mit Pflichtfeld file (JPEG/PNG/WebP/GIF). Optional: filename, title, altText. Antwort 201 mit media.id und öffentlicher media.url. Erfordert konfiguriertes Object Storage (R2).

curl -X POST "https://www.ostheimer.at/api/public/agent/v1/media/upload" \
  -H "Authorization: Bearer <API_KEY>" \
  -F "file=@cover.jpg" \
  -F "altText=Beschreibung des Bildes" \
  -F "title=Artikelbild"

Antwort 201:

{
  "success": true,
  "media": {
    "id": "clxyz123…",
    "url": "https://…/uploads/generated/cover-17300000-abc12.jpg",
    "mimeType": "image/jpeg",
    "fileSize": 245000
  }
}

3a. Workflow: Bild hochladen → Blog veröffentlichen

Der empfohlene Ablauf, damit ein Bild im Object Storage (R2) landet und korrekt mit dem Blogartikel verknüpft wird:

  1. Bild hochladen per POST /api/public/agent/v1/media/upload (siehe oben). Aus der Antwort media.id und media.url merken.
  2. Blog veröffentlichen per POST /api/blog/publish mit featuredMediaId:
curl -X POST "https://www.ostheimer.at/api/blog/publish" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  --data '{
    "title": "Mein Artikel",
    "slug": "mein-artikel",
    "content": "## Einleitung\n\nArtikeltext…",
    "excerpt": "Kurzfassung des Artikels",
    "featuredMediaId": "<media.id aus Schritt 1>",
    "keywords": ["Beispiel"],
    "categories": ["Allgemein"],
    "published": true
  }'

Alternative: Statt manuell hochzuladen, kann auch imageUrl mit einer externen HTTPS-URL übergeben werden – der Server lädt das Bild automatisch herunter, speichert es in R2 und verknüpft es. Mit imageUrlPassthrough: true wird stattdessen nur die URL als Hotlink gespeichert.

4. Blog Publishing API (API-Key oder BLOG_API_KEY)

  • GET /api/blog/publish – Einzelpost per ?slug=… oder ?id=…; ohne diese Parameter: Liste mit optional published=true|false und limit (max. 200, Scope blog:read).
  • POST – Anlegen/Upsert (Scope blog:write oder Legacy-Key).
  • PUT – Teilaktualisierung (blog:write).
  • DELETE – Löschen per Query slug oder id (blog:delete).

Typische Felder im JSON-Body: title, slug, content (Markdown), excerpt, keywords, categories, tags, imageUrl, optional imageUrlPassthrough (kein Upload nach R2; URL nur als coverImage), published, publishAt. Ist Object Storage (R2) konfiguriert, werden geeignete externe imageUrl-Werte automatisch in den Speicher übernommen und mit featuredMediaId verknüpft – außer es wird gleichzeitig ein eigenes featuredMediaId gesetzt.

5. Rate-Limit und Antworten

Bei geschützten Public-API-Routen: Limit pro Client und Endpunkt (Rolling-Fenster 60 Sekunden, Zähler in der Datenbank für konsistente Limits über Serverless-Instanzen). Legacy BLOG_API_KEY kann mit BLOG_API_LEGACY_ENABLED=false abgeschaltet werden; optional Tageslimit für Blog-Generate (PUBLIC_API_BLOG_GENERATE_DAILY_CAP). Öffentliche Routen (Emoji, Redirects, Kontakt) sind pro IP begrenzt — siehe docs/AGENT_API.md.

Header bei geschützten Routen:

  • X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
  • Retry-After bei HTTP 429
  • X-Request-Id bei mehreren Routen

Häufige Fehler: 401 (fehlender/ungültiger Key), 403 (missing_scope), 429 (Limit), 400 (invalid_payload).

6. Öffentliche Endpunkte ohne API-Key

Diese Routen sind für Website, Widgets und Crawler gedacht – bitte schonend nutzen (kein Scraping gegen Fair-Use).

Website-Chat

  • POST /api/chat – Streaming-Chat für das eingebettete Widget. Body: messages (Rollen user/assistant/system), optional contact.
  • GET /api/chatbot-config – Modus (internal / external / disabled) und optionaler externer Script-Code.

Kontaktformular (Kalkulator)

  • POST /api/contact – Sendet eine Anfrage inkl. calculatorData (Paket, Summen, Zusammenfassung). Schema ist an die Leistungsseiten gebunden; nur über offizielle Formulare verwenden.

Emoji-Referenz

  • Website-API: /api/emojis, /api/emojis/{slug}, /api/emojis/suggest sowie Copy/View-Zähler bleiben für die Emoji-Website bestehen.
  • Developer-Plattform: /emojis/developers dokumentiert Resolve, Search, Normalize, Lint, Suggest, Fixtures, Intents, Compare, Export, CLI, Tarballs und den Hosted-MCP-Endpunkt als kanonische Emoji-Dev-Doku.

Redirects (Edge / Middleware)

  • GET /api/redirects/lookup – Aktive Redirects und öffentliche Blog-Slugs (gecacht). Für Infrastruktur, nicht für massenhafte Abfragen von außen ohne Absprache.
  • POST /api/redirects/hit – JSON-Body mit Feld source (Pfad der Quelle) – Hit-Zähler (wird von der Site genutzt).

7. Nicht öffentlich

Alle Routen unter /api/admin/*, /api/agent/* (Dashboard-Agenten), /api/auth/* (außer dokumentierten OAuth-Flows), WordPress-Import, Bildgenerierung für Admins usw. erfordern Anmeldung bzw. sind nicht für externe Integration ohne Vertrag gedacht.

8. MCP (lokal / Desktop)

Für Claude Desktop & Co. existiert ein stdio-MCP-Server im Repository (npm run mcp:server). Details und Umgebungsvariablen siehe docs/AGENT_API.md im Quellcode-Repository.


Public APIs (English)

Base URL: https://www.ostheimer.at. Machine-readable index: /llms.txt.

Authentication

Use Authorization: Bearer <API_KEY> or x-api-key. Create keys in the dashboard (Settings → Public API Clients). Optional env key: PUBLIC_AGENT_API_KEY. The legacy BLOG_API_KEY is deprecated and disabled by default.

Scoped endpoints

chat:write → POST /api/public/agent/v1/chat · tasks:write → POST …/tasks · tasks:read → GET …/tasks/{id} · blog:read|write|delete → /api/blog/publish (GET/POST/PUT/DELETE) · blog:generate → POST /api/public/agent/v1/blog/generate · media:write → POST /api/public/agent/v1/media/upload

Emoji scopes: emoji:read → GET /api/emojis/export · emoji:lint → POST /api/emojis/lint · emoji:suggest → POST /api/emojis/suggest · emoji:mcp → POST /api/emojis/mcp

Blog publish body: typical fields include title, slug, content (Markdown), excerpt, keywords, categories, tags, imageUrl, optional imageUrlPassthrough (skip R2 copy; keep URL as coverImage only), published, publishAt. When object storage (R2) is configured, eligible external imageUrl values are copied into the bucket and linked as featured media, unless you also set featuredMediaId (which disables automatic ingest for that request).

Emoji developer docs

Canonical docs for the Emoji Intelligence API, CLI, tarballs, and hosted MCP bridge live at /emojis/developers.

Workflow: Upload image → Publish blog

Recommended two-step flow so the image lands in R2 and is linked to the post:

  1. Upload image via POST /api/public/agent/v1/media/upload (media:write scope, multipart/form-data with file field). Note the returned media.id.
  2. Publish blog via POST /api/blog/publish with featuredMediaId set to the media ID from step 1.

Alternatively, pass an external imageUrl (the server downloads and stores it in R2 automatically), or use imageUrlPassthrough: true to hotlink without copying.

Rate limits

Per client and route; counters are stored in Postgres (60s rolling window) so limits hold across serverless instances. Headers X-RateLimit-*, Retry-After on 429. Optional daily cap for blog generate: PUBLIC_API_BLOG_GENERATE_DAILY_CAP (DB API clients only). Disable legacy BLOG_API_KEY with BLOG_API_LEGACY_ENABLED=false. Unauthenticated emoji, redirect, and contact routes are rate-limited per IP — see docs/AGENT_API.md.

Errors: 401 unauthorized, 403 missing_scope, 400 invalid_payload.

Endpoints without an API key

  • POST /api/chat – website chat stream
  • GET /api/chatbot-config – widget configuration
  • POST /api/contact – contact + calculator payload (official forms only)
  • GET /api/emojis, GET /api/emojis/{slug}, GET /api/emojis/suggest
  • POST /api/emojis/{slug}/view, POST /api/emojis/{slug}/copy
  • GET /api/redirects/lookup, POST /api/redirects/hit

Not public

/api/admin/*, dashboard /api/agent/*, and similar routes require authentication and are not documented here for external use.