API & Integration

agentView API

agentView stellt Displays (TVs, Tablets, Kioske) bereit und macht sie per API steuerbar. Du sendest HTML an ein Display; agentView liefert es in Echtzeit aus.

Drei Integrationswege: REST API mit JWT oder API-Key, MCP mit OAuth 2.1, oder direkt per POST mit tokenisierter URL.

Für KI-Coding-Agenten

Machine-Readable APIs

Diese Seite ist die menschenlesbare Übersicht. Wenn du als KI-Coding-Agent integrierst, lies stattdessen direkt die JSON-Quellen: sie sind die Single Source of Truth und können nicht veralten:

JSON /mcp/manifest Vollständige MCP-Capability-Beschreibung: Tools (mit category, Input/Output-Schemas, Security-Schemes), Prompts, Resources, Auth-Flow.
JSON /swagger/agent/swagger.json OpenAPI 3 für die öffentliche REST-API. Enthält alle Pfade, Parameter, Response-Schemas und tags zur Domänen-Gruppierung.
MD /agent-instructions Markdown-Anleitung speziell für Agenten: Auth-Flow, empfohlene Tool-Reihenfolge, häufige Fehler.
JSON /api/status Health-Check + Discovery-URLs für alle anderen Quellen. Idealer Einstiegspunkt.

Live-Daten werden geladen…

Quick Start

In 60 Sekunden auf dem Display

Du hast bereits einen API-Key (Settings → API-Schlüssel): eine einzige Zeile schickt HTML auf ein Display.

curl -X POST https://agentview.de/api/v1/agent/displays/ABCD1234/content \
  -H "X-API-Key: avk_a1b2c3d4..." \
  -H "Content-Type: text/html; charset=utf-8" \
  -d '<html><body style="margin:0;background:#111;color:#fff;
    display:grid;place-items:center;height:100vh;font-family:sans-serif">
    <h1>Hello</h1></body></html>'

Antwort:

{
  "delivered": true,
  "displayId": "ABCD1234",
  "deliveredAt": "2026-05-07T09:12:34.567Z",
  "contentLength": 184
}

Display-ID herausfinden: curl -H "X-API-Key: avk_..." https://agentview.de/api/v1/agent/displays


Du baust einen Chat-Agenten oder hast keinen Key? → Session-Flow (3 Schritte)

1. Session erstellen (kein Login nötig)

curl -X POST https://agentview.de/api/v1/agent/session/request \
  -H "Content-Type: application/json" \
  -d '{"scope": "content_only"}'

Antwort:

{
  "sessionRequestId": "sr_7f8a3b2c1d",
  "loginUrl": "https://agentview.de/agent.html?session=sr_7f8a3b2c1d",
  "pollUrl": "https://agentview.de/api/v1/agent/session/status?id=sr_7f8a3b2c1d",
  "expiresAt": "2026-05-07T09:25:00Z"
}

Der Nutzer öffnet die loginUrl im Browser und meldet sich an.

2. Token abholen (Polling während der Nutzer einloggt)

curl https://agentview.de/api/v1/agent/session/status?id=sr_7f8a3b2c1d

Antwort nach Approval:

{
  "status": "active",
  "agentToken": "eyJhbGciOiJIUzI1NiIs...",
  "scope": "content_only",
  "userId": "u_a1b2c3",
  "expiresAt": "2026-05-08T09:12:34Z"
}

3. HTML senden (mit dem JWT statt API-Key)

curl -X POST https://agentview.de/api/v1/agent/displays/ABCD1234/content \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: text/html; charset=utf-8" \
  --data-binary '@hello.html'

Fehler-Responses: 401 (Token fehlt/ungültig), 403 (Scope reicht nicht), 404 (Display existiert nicht oder gehört nicht zu dir), 409 (Display ist gesperrt — siehe Governance), 429 (Rate-Limit). Body: { "error": "...", "message": "...", "hint": "..." } (ErrorResponse).

Authentifizierung

3 Wege

  • Session-Flow (interaktiv): Agent erstellt Session, Nutzer loggt sich im Browser ein, Agent bekommt JWT. Für Chat-Agenten und UIs.
  • API-Key (headless): Langlebiger Key (avk_...) ohne Browser-Login. Für Automationen, CI/CD, Home Assistant.
  • MCP OAuth 2.1: Authorization Code + PKCE über /.well-known/oauth-authorization-server. Für MCP-Clients.

API-Key-Beispiel:

# Key erstellen (erfordert admin-Scope)
curl -X POST https://agentview.de/api/v1/agent/api-keys \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Home Assistant", "scope": "content_only"}'

# Key verwenden
curl -H "X-API-Key: avk_a1b2c3d4e5f6..." \
  https://agentview.de/api/v1/agent/displays

Der rohe Key wird nur einmal angezeigt (SHA-256-Hash gespeichert). Max. 10 Keys pro Nutzer.

Scopes

Berechtigungsmodell

content_only

  • Inhalte senden (HTML, URL)
  • Displays auflisten und inspizieren
  • Organisationen anzeigen
  • SSE-Events empfangen

admin

  • Alles aus content_only
  • Displays erstellen, löschen, sperren, konfigurieren
  • Idle-Inhalte setzen
  • Organisationen verwalten
  • API-Keys erstellen und widerrufen
Standard: content_only. Der Nutzer kann bei der Freigabe einen admin-Request auf content_only herabstufen.
REST API

Endpoint-Referenz

Basis-URL: https://agentview.de. Alle Endpoints unter /api/v1/agent/ erfordern Authentifizierung, sofern nicht anders angegeben.

Untenstehender Katalog ist kuratiert. Vollständige Liste aller Endpoints inklusive Parameter und Schemas: /swagger/agent/swagger.json oder interaktiv im Swagger-UI.

Discovery & Status (kein Login)

GET /agent-instructions Maschinenlesbare Anleitung (Markdown)
GET /api/status Health-Check, Version, Discovery-URLs
GET /api/v1/agent/pricing Pläne, Preise, Features
GET /api/v1/agent/public-apis?query=...&category=...&cors_only=...&limit=... 600+ freie APIs durchsuchen (Wetter, News, Bilder, Sport, ...)
GET /api/v1/agent/public-apis/categories Alle API-Kategorien mit Anzahl
GET /api/v1/agent/public-apis/{slug} Details einer API inkl. fetch()-Hint
GET /swagger OpenAPI / Swagger UI

Authentifizierung

POST /api/v1/agent/session/request Login-Session erstellen → loginUrl, pollUrl
GET /api/v1/agent/session/status?id=... Session-Status abfragen → JWT Token
POST /api/v1/agent/api-keys API-Key erstellen (admin)
GET /api/v1/agent/api-keys Eigene API-Keys auflisten
DELETE /api/v1/agent/api-keys/{id} API-Key widerrufen

Account

GET /api/v1/agent/me Name, E-Mail, Plan, Display-Slots

Displays

GET /api/v1/agent/displays Alle Displays auflisten
POST /api/v1/agent/displays Neues Display erstellen (admin)
GET /api/v1/agent/displays/{id} Details inkl. Runtime-Fakten, Hardware-Settings und Verbindungsstatus
PATCH /api/v1/agent/displays/{id} Umbenennen (admin)
DELETE /api/v1/agent/displays/{id} Löschen (admin)
GET /api/v1/agent/displays/{id}/capabilities Aufgelöste Konnektivität: Modus, Whitelist, Herkunft

Content

POST /api/v1/agent/displays/{id}/content HTML oder Base64-HTML senden
POST /api/v1/agent/displays/{id}/url URL per iframe laden
GET /api/v1/agent/displays/{id}/content Aktuellen Content-State abfragen
POST /api/v1/agent/displays/broadcast An mehrere Displays gleichzeitig senden
POST /api/v1/agent/displays/test/content Dry-Run: HTML-Größe + Description validieren ohne echtes Display zu treffen. 1 MB-Limit. Antwort enthält simulated:true.
GET /api/v1/agent/public-apis/categories Liste aller Public-API-Kategorien mit Anzahl pro Kategorie. Vor /public-apis?category=... als Auswahlhilfe.
POST /send/{id}?token=...&duration=300 Direkter Send mit Token (kein Login)

Display-Verwaltung (admin)

POST /api/v1/agent/displays/{id}/lock Display sperren
POST /api/v1/agent/displays/{id}/unlock Sperre aufheben
POST /api/v1/agent/displays/{id}/clear Auf Idle zurücksetzen
POST /api/v1/agent/displays/{id}/default Idle-Content setzen oder löschen (HTML)
POST /api/v1/agent/displays/{id}/configure Kamera, Mikrofon, Geolocation, Cursor, Badge, Watermark
POST /api/v1/agent/displays/{id}/claim Unkonfiguriertes Display uebernehmen

Privatsphäre & Schlüssel-Rotation (GDPR)

PATCH /api/v1/agent/displays/{id}/privacy-mode Privacy-Modus setzen (Private oder Public). Cap für Share-Link-TTL: Private 1h, Public 24h. Audit-geloggt.
POST /api/v1/agent/displays/{id}/managed-secret/rotate Neuen ManagedDeviceSecret ausstellen, alte /m/{secret}-URL sofort tot.
POST /api/v1/agent/displays/{id}/managed-secret/revoke MDM-URL widerrufen ohne Ersatz. Recovery-Failover bleibt aktiv.
POST /api/v1/agent/displays/{id}/recovery-secret/rotate Neuen RecoverySecret ausstellen. Gerät muss einmal online kommen, um neuen Failover-Link zu speichern.
POST /api/v1/agent/account/approval-secret/rotate Account-weiten DisplayApprovalSecret rotieren. Alter /a/{secret}-Link sofort tot.

Display Categories

Hierarchische Gruppen zum Bündeln von Displays. Jede Kategorie hat einen kanonischen Slug; parent_category_id baut Pfade. Displays sind Mehrfach-Mitglieder. Owner-Scope nutzt /api/v1/owner/..., Org-Scope /api/v1/orgs/{orgId}/....

GET /api/v1/owner/display-categories Alle Kategorien des Owners mit Hierarchie
POST /api/v1/owner/display-categories Kategorie anlegen (optional parent_category_id)
PATCH /api/v1/owner/display-categories/{categoryId} Kategorie umbenennen oder umhängen
DELETE /api/v1/owner/display-categories/{categoryId} Kategorie löschen (Mitgliedschaften enden)
PUT /api/v1/owner/displays/{profileId}/categories Vollständige Kategorie-Mitgliedschaft eines Displays setzen
POST /api/v1/owner/display-categories/bulk-assign Eine Kategorie auf viele Displays zuweisen oder entfernen
GET /api/v1/orgs/{orgId}/display-categories Org-Kategorien
POST /api/v1/orgs/{orgId}/display-categories Org-Kategorie anlegen
PATCH /api/v1/orgs/{orgId}/display-categories/{categoryId} Org-Kategorie ändern
DELETE /api/v1/orgs/{orgId}/display-categories/{categoryId} Org-Kategorie löschen
PUT /api/v1/orgs/{orgId}/displays/{profileId}/categories Mitgliedschaft eines Org-Displays setzen
POST /api/v1/orgs/{orgId}/display-categories/bulk-assign Bulk-Zuweisung in der Organisation
POST /api/v1/owner/displays/broadcast-by-category HTML an alle Displays mit den angegebenen Kategorie-Slugs senden
POST /api/v1/orgs/{orgId}/displays/broadcast-by-category Broadcast nach Kategorie auf Org-Ebene

Display Governance (Owner, Cookie-Auth)

Owner-getriebene Kontroll-Layer. Diese Endpoints werden vom Dashboard mit Cookie-Session aufgerufen, nicht vom Agent. Sie sind hier dokumentiert, damit Agenten den umgebenden Vertrag verstehen und bei verwandten 403-Antworten sinnvoll reagieren können.

Approval Mode

PUT /api/v1/owner/displays/{id}/approval-mode Approval-Modus ein- oder ausschalten
GET /api/v1/owner/displays/{id}/approval-state Aktuellen Approval-State und ggf. ausstehende Submission abrufen
POST /api/v1/owner/displays/{id}/pending/accept Ausstehenden Inhalt freigeben (live schalten)
POST /api/v1/owner/displays/{id}/pending/reject Ausstehenden Inhalt verwerfen
POST /api/v1/owner/displays/{id}/rollback Live-Inhalt durch vorherige Version ersetzen

Source-Lock

GET /api/v1/owner/displays/{id}/source-lock Aktuelle Quellbindung lesen
PUT /api/v1/owner/displays/{id}/source-lock Display exklusiv an einen API-Key binden oder Bindung lösen

Activity Audit

Append-only Log: wer hat was über welchen Schlüssel gesendet, plus Approval-, Sperr- und Webhook-Ereignisse. Pro Eintrag: Akteur, Auth-Methode, Aktion, Ziel-Typ und -ID, Org-ID, gekürzte IP, User-Agent, Metadaten.

GET /api/v1/owner/activity-audit Owner-Sicht (eigene Aktionen + Aktionen auf eigenen Ressourcen)
GET /api/v1/admin/activity-audit Admin-Sicht (alle Mandanten)

Webhooks

HMAC-SHA256-signierte ausgehende Benachrichtigungen. Der Signing-Key wird nur einmal beim Anlegen zurückgegeben. Verbindungen zu internen Netz-Adressen sind blockiert. Event-Patterns: display.*, display.content.*, display.approval.*, data.changed, data.deleted oder data.*.

GET /api/v1/owner/webhooks Eigene Webhooks auflisten
POST /api/v1/owner/webhooks Webhook anlegen, Signing-Key wird einmalig zurückgegeben
DELETE /api/v1/owner/webhooks/{id} Webhook entfernen
PATCH /api/v1/owner/webhooks/{id}/active Aktiv-Status togglen
POST /api/v1/owner/webhooks/{id}/test Test-Event auslösen (rate-limited)

Assets

POST /api/v1/assets Assets hochladen (multipart, 1-20 Dateien, max 10 MB)
GET /api/v1/assets Eigene Assets auflisten (paginiert, Filter nach Typ/Suche)
GET /api/v1/assets/{id} Asset-Metadaten und URL abrufen
PATCH /api/v1/assets/{id} Name und/oder Beschreibung aktualisieren
DELETE /api/v1/assets/{id} Einzelnes Asset löschen
DELETE /api/v1/assets Mehrere Assets löschen (Bulk, max 100)

Data Slots

Mutable JSON storage cells readable by displays via public URLs. Two types: value slots (store JSON verbatim) and JSON collections (type=aggregate, shown as 📦 in the dashboard, combining multiple slots into one read response).

Slug stays stable, the URL doesn't expose it. The Slug you supply on PUT is the slot's stable identifier — it's what API-key whitelists, prefixMatch aggregates and follow-up updates address. The public read URL embeds a separate PublicId (8-char random hex) the server generates on CREATE: /data/u/{publicSlug}/{publicId}.json. Knowing the URL grants read access (capability URL); knowing the Slug alone does not. Subsequent PUTs with the same Slug update the same slot in place — ideal for bot heartbeats and self-registering agents. Treat URL contents as publicly published JSON.
GET /api/v1/data List all data slots (metadata only, paginated)
GET /api/v1/data/quota Check storage quota usage
GET /api/v1/data/{slug} Get a single slot by its Slug; response includes the slot's readUrl (the URL embeds publicId, not slug)
PUT /api/v1/data/{slug}?type=aggregate&label=...&groupId=... Create or update a data slot by Slug; body is raw JSON. Same Slug on a second PUT updates the existing slot. type=aggregate creates a JSON collection.
DELETE /api/v1/data/{slug} Delete a data slot by Slug
GET /api/v1/data/{slug}/usage List displays whose IdleHtmlContent embeds {{slot:<slug>}}. Use before delete/structural change to know which displays will 404 or render differently. Same scope as the slot. Capped at 50; truncated:true if more match.
GET /data/u/{publicSlug}/{publicId}.json Public read URL (personal slot, no auth required). The trailing segment is the slot's publicId, not its Slug.
GET /data/g/{groupSlug}/{publicId}.json Public read URL (group slot, no auth required)

JSON Collections (aggregate slots)

A JSON collection merges multiple value slots into one response, resolved on every fetch. Create with ?type=aggregate and a body listing source slot slugs (the Slugs — not PublicIds — because slugs are the stable internal identifier). The dashboard shows these as 📦.

# Create a value slot. The Slug stays exactly as you sent it; the response carries
# the readUrl (with the server-generated publicId) and the slot's publicId field.
curl -X PUT https://agentview.de/api/v1/data/sensor-a \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"temp": 21.4, "unit": "C"}'
# => { "slot": { "slug": "sensor-a", "publicId": "a1b2c3d4",
#                "readUrl": "https://content.agentview.de/data/u/.../a1b2c3d4.json", ... } }

# Update the same slot — same slug, no surprises. PublicId stays stable, readUrl too.
curl -X PUT https://agentview.de/api/v1/data/sensor-a \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"temp": 22.1, "unit": "C"}'

# Create a JSON collection (aggregate slot). Source-slot references use the SLUG —
# the stable internal identifier — not the publicId.
curl -X PUT "https://agentview.de/api/v1/data/all-sensors?type=aggregate&label=All+Sensors" \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sources":[{"slot":"sensor-a"},{"slot":"sensor-b"}]}'

# Public read — use the readUrl from the create response (it embeds publicId, not slug).
# GET https://content.agentview.de/data/u/{publicSlug}/{publicId}.json

Reading from a display template

Display HTML renders inside a sandboxed iframe whose document origin is the opaque null sentinel, so even same-host fetch() is treated as cross-origin. The public read URLs return Access-Control-Allow-Origin: * and Vary: Origin, so a template can poll a slot directly without a proxy. Templates address the slot via the {{slot:<slug>.readUrl}} placeholder; the server substitutes the live URL at send-time.

// Inside a display template
var SLOT_URL = "{{slot:sensor-a.readUrl}}";

setInterval(function () {
  fetch(SLOT_URL, { cache: "no-store" })
    .then(function (r) { return r.json(); })
    .then(function (data) {
      document.getElementById("temp").textContent = data.temp + " " + data.unit;
    });
}, 5000);

Organisationen

GET /api/v1/agent/organizations Eigene Organisationen mit Stats
GET /api/v1/agent/organizations/{orgId} Details mit Mitgliedern und Displays
POST /api/v1/agent/organizations/{orgId}/invite Mitglied einladen (admin)
POST /api/v1/agent/organizations/{orgId}/slots Display-Slots zuweisen (admin)
POST /api/v1/agent/organizations/{orgId}/displays Org-Display erstellen (admin)

Echtzeit

GET /api/v1/agent/events SSE-Stream: display_online, display_offline, content_delivered, content_cleared, data.changed
Server-Sent Events

Echtzeit-Events statt Polling

curl -N -H "Authorization: Bearer TOKEN" \
  https://agentview.de/api/v1/agent/events
event: connected
data: {"displayIds":["ABCD1234","EFGH5678"],"timestamp":"..."}

event: display_online
data: {"displayId":"ABCD1234","timestamp":"..."}

event: content_delivered
data: {"displayId":"ABCD1234","fileName":"abc.html","timestamp":"..."}

event: data.changed
data: {"slug":"menu-of-the-day","groupId":null,"contentVersion":7,"updatedAt":"...","timestamp":"..."}

Display-Events werden auf Displays gefiltert, die dem authentifizierten Nutzer gehören. data.changed erreicht den Besitzer des Slots (persönlicher Scope) oder alle Gruppenmitglieder (Gruppen-Scope). Bei JWT-Ablauf wird der Stream geschlossen. Der Client reconnected mit frischem Token.

data.changed feuert bei Create, Update und Delete eines Data-Slots. Der mitgesendete contentVersion-Wert ist die neue Version (oder die letzte vor dem Delete). Subscriber, die mit GET /api/v1/data/{slug} nachladen und 404 erhalten, wissen damit, dass der Slot gelöscht wurde.

Glossar

Glossar

Kernbegriffe der agentView-API und ihre Beziehungen zueinander.

Beziehungen zwischen den Kern-Entitäten:

   Account ─ owns ─ Organization(s)
      │                    │
      └─ has ─ Display(s) ─┘─ assigned to ─ Category
                   │
                   │ receives
                   ▼
               Content ─ may reference ─ Data Slot (via {{slot:X.readUrl}})
                                               │
                                               └─ contains JSON (publicly readable)

   Account ─ owns ─ Asset(s) ─ accessible via stable URL on content.agentview.de
Account
Der einzelne Benutzer mit Login. Besitzt Organizations, Displays und Assets. → Account-Endpoint
Organization
Lose Gruppe ohne Tenant-Isolation. Bündelt Displays und teilt License-Slots. → Orgs-Endpoint
Display
Bildschirm (TV, Tablet, Kiosk), der HTML rendert. Hat Categories und gegebenenfalls Embeddable Origins. → Displays-Endpoint
Category
Tag zum Bündeln mehrerer Displays für Broadcast. → Categories-Endpoint
Data Slot
Public-readable JSON-Blob, identifiziert per Slug und PublicId. Referenziert aus Content via {{slot:X.readUrl}}. → Data-Slots-Endpoint
Asset
Hochgeladene Datei (Bild, Video, PDF). Zugänglich über stabile URL auf content.agentview.de. → Assets-Endpoint
Content
Aktiv ausgespielter HTML-Body auf einem Display. → Content-Endpoint
Slot.PublicId
Zufällige ID des Slots (nicht der Slug). Steht in der ReadUrl und vermeidet so ein Slug-Leak in öffentlichen URLs.
ReadUrl
Stabile öffentliche URL eines Slots: https://agentview.de/data/u/{publicSlug}/{publicId}.json. → Data-Slots-Endpoint
Idle Content
Was ein Display zeigt, wenn keine aktive Sendung läuft. Pro Display konfigurierbar. → Mgmt-Endpoint
Default Content
Initialer Content beim Display-Onboarding. Wird durch aktiven Content überschrieben, sobald gesendet wird. → Content-Endpoint
Embeddable Origins
CORS-Liste der Domains, die ein Display als iframe einbetten dürfen. → Mgmt-Endpoint
Recipes

Recipes

Fertige Codebausteine für die häufigsten Integrationsmuster.

Slot-gesteuerte Slideshow

Das Display lädt display.html; der Server ersetzt {{slot:menu.readUrl}} zur Sendezeit durch die stabile Slot-URL. Ein kleiner JS-Loop holt alle 30 Sekunden frische Daten und wechselt das angezeigte Bild, ohne die Seite neu zu laden.

<!doctype html>
<html lang="de">
<head>
  <meta charset="utf-8">
  <style>body{margin:0;font:24px sans-serif} img{width:100vw;height:100vh;object-fit:contain}</style>
</head>
<body>
  <img id="slide" alt="">
  <script>
    const READ_URL = "{{slot:menu.readUrl}}"; // resolved by agentView at send-time
    const slide = document.getElementById("slide");
    let i = 0;
    async function refresh() {
      const r = await fetch(READ_URL, {cache: "no-store"});
      const data = await r.json(); // shape: { images: ["https://content.agentview.de/..."] }
      if (Array.isArray(data.images) && data.images.length) {
        slide.src = data.images[i % data.images.length];
        i++;
      }
    }
    refresh();
    setInterval(refresh, 30_000);
  </script>
</body>
</html>

Slot via PUT /api/v1/data/{slug} aktualisieren und der Player zieht die nächste Iteration innerhalb von 30 Sekunden.

SSE-Echtzeit mit Reconnect

/api/v1/agent/events streamt SSE-Events für Display-Status. Naive EventSource-Nutzung bricht hinter Corporate-Proxies lautlos ab. Diese Variante ergänzt Exponential-Backoff-Reconnect.

const BASE = "https://agentview.de";
const TOKEN = "..."; // Bearer JWT from /api/v1/agent/session/status

let backoff = 1000; // start at 1s
function connect() {
  const url = new URL(BASE + "/api/v1/agent/events");
  url.searchParams.set("access_token", TOKEN);
  const es = new EventSource(url.toString());

  es.addEventListener("display_online", e => {
    const d = JSON.parse(e.data);
    console.log("Display online:", d.displayId);
  });
  es.addEventListener("display_offline", e => {
    const d = JSON.parse(e.data);
    console.log("Display offline:", d.displayId);
  });
  es.addEventListener("content_delivered", e => {
    const d = JSON.parse(e.data);
    console.log("Content delivered to:", d.displayId);
  });
  es.addEventListener("data.changed", e => {
    const d = JSON.parse(e.data);
    // d.slug, d.groupId, d.contentVersion, d.updatedAt
    // Push-Pendant zu PUT /api/v1/data/{slug} — Slot wurde angelegt,
    // aktualisiert oder geloescht. Re-Fetch der Quelle statt Polling.
    fetch(`${BASE}/api/v1/data/${d.slug}`)
      .then(r => r.ok ? r.json() : null)
      .then(slot => console.log("Slot now:", slot));
  });

  es.onopen = () => { backoff = 1000; };
  es.onerror = () => {
    es.close();
    setTimeout(connect, backoff);
    backoff = Math.min(backoff * 2, 30_000); // cap at 30s
  };
}
connect();

Exponential Backoff verhindert, dass ein Hub-Restart hunderte Clients gleichzeitig zurückverbindet.

Broadcast vs. einzelnes Display

Ein einzelnes Display: POST /api/v1/agent/displays/{id}/content. Mehrere Displays auf einmal: POST /api/v1/agent/displays/broadcast mit einem Array von IDs oder "all": true. Für Category-Targeting: POST /api/v1/owner/displays/broadcast-by-category.

Einzelnes Display:

curl -X POST https://agentview.de/api/v1/agent/displays/dpl_lobby/content \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"html":"<h1>Geschlossen bis 14:00</h1>"}'

Mehrere Displays (IDs):

curl -X POST https://agentview.de/api/v1/agent/displays/broadcast \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "displayIds": ["dpl_lobby", "dpl_kitchen"],
    "html": "<h1>Mittagspause bis 14:00</h1>"
  }'

Alle Displays einer Category:

curl -X POST https://agentview.de/api/v1/owner/displays/broadcast-by-category \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "category_slugs": ["lobby"],
    "html": "<h1>Mittagspause bis 14:00</h1>"
  }'

Broadcast trifft alle angegebenen Displays; Category-Broadcast trifft alle Displays mit dem Tag. Beide überschreiben den aktiven Content. Idle Content bleibt unberührt.

Content-Laufzeit

Wo und wie dein Content läuft

Vorschau == Live

Das Gerät rendert deinen Content, indem es die signierte contentUrl aus GET /api/v1/agent/displays/{id}/content in ein gesandboxtes iframe lädt. Die URL enthält aus historischen Gründen das Wort preview, ist aber der echte Geräte-Render-Pfad: Dashboard-Vorschau und physisches Gerät laden dieselbe URL unter derselben Policy. Leite die Policy NICHT aus einem curl auf den Content-Host-Root ab, das ist die App-Shell und liefert nie Content.

CSP, Sandbox und Origin

Live-CSP: script-src * 'unsafe-inline' 'unsafe-eval' blob: data:, connect-src * data: blob:, img-src/media-src * data: blob:, frame-src *. Erlaubt sind inline-Scripts, eval, externe CDN-Scripts (https), dynamisches import() von blob:/data:-Modulen, fetch zu beliebigen Origins und eingebettete iframes. Das iframe ist ohne allow-same-origin gesandboxt, der Content hat also eine opake (null) Origin: localStorage, sessionStorage, Cookies und IndexedDB sind nicht verfügbar, und jeder fetch ist cross-origin mit Origin: null. Externe APIs müssen CORS erlauben (am einfachsten Access-Control-Allow-Origin: *). Schreibe Content selbst-enthaltend und ohne Client-seitige Persistenz.

duration

Optionale Ganzzahl in Sekunden (1 bis 86400) auf den content-, url- und broadcast-Aufrufen. Weglassen, null oder 0 senden bedeutet unbegrenzt (Inhalt bleibt bis ersetzt oder gecleared). Ein positiver Wert lässt das Display nach so vielen Sekunden zur Idle-Ansicht zurückkehren. Wird auf Live-Displays durchgesetzt.

Live-Daten ohne HTML-Neuversand

Schreibe einen Data Slot mit PUT /api/v1/data/{slug} und polle die zurückgegebene öffentliche readUrl aus deinem Content. Die readUrl liefert Access-Control-Allow-Origin: * und funktioniert daher unter der opaken Origin. Nutze dieses Player-plus-Slot-Muster statt blob-basierter Modul-Loader oder kompletter HTML-Neuversände.

Fehler & Header

Fehlerfälle und Antwort-Header

401 / 400 / 403 Body-Shape

Alle Fehlercodes (400, 401, 403, 404, 409) liefern eine einheitliche ErrorResponse: { "error": "...", "message": "...", "hint": "..." }. Das Feld error ist immer gesetzt; message und hint sind optional.

// Beispiel: ungültiger Token
{
  "error": "unauthorized",
  "message": "Invalid or expired token.",
  "hint": "Create a new session with POST /api/v1/agent/session/request"
}

429 Rate-Limit

Rate-limitierte Endpoints (Login: 10/min, Uploads: 100/min, Cancellations: 5/10 min) liefern bei Überschreitung HTTP 429 mit den Headers X-RateLimit-Limit, X-RateLimit-Window (Fenster in Sekunden) und X-RateLimit-Policy sowie einer ErrorResponse mit "error": "rate_limited".

CORS: Data Slots (public)

Public Read-URLs (/data/u/{publicSlug}/{publicId}.json) antworten mit Access-Control-Allow-Origin: * und Cross-Origin-Resource-Policy: cross-origin. Display-Templates können Slots direkt per fetch() abfragen, ohne Proxy.

CORS-Probe: /api/v1/agent/public-apis?cors_only=true

cors_only=true (Alias: corsOk=true) filtert den Public-API-Katalog auf Einträge, die ein nächtlicher serverseitiger Probe aus dem Content-Origin (https://content.agentview.de) als browser-tauglich verifiziert hat. Der Filter ist UND-verknüpft mit Erreichbarkeit (HTTP 2xx–3xx), damit Einträge, die aktuell 500er liefern, gar nicht erst im Resultat landen. Jeder Eintrag trägt zusätzlich die Felder corsOk, corsAllowOrigin, reachable, httpStatus, contentType, responseMs, probedAt, redirectedTo und error. Wenn noch keine Probe-Zeile existiert (frisch deployter Eintrag), fällt corsOk auf das Upstream-Flag aus public-apis/public-apis zurück und probedAt ist null.

Der Probe sendet den User-Agent agentView-CORS-Probe/1.0 (+https://agentview.de/developers.html#cors-probe), einen Origin-Header passend zum konfigurierten Content-Origin und folgt höchstens einem Redirect. Private-/Loopback-/Cloud-Metadata-IPs werden auf Socket-Ebene blockiert. API-Betreiber, die unseren Traffic nicht wollen, müssen nichts speziell konfigurieren: sie liefern einfach kein Access-Control-Allow-Origin und der Eintrag wandert von selbst aus dem cors_only=true-Resultat. http://-Einträge werden vor dem Probe automatisch auf https:// umgeschrieben; gelingt das nicht, markiert die Probe sie als error: "http_only", weil Mixed-Content-Regeln den Aufruf aus einem HTTPS-Display ohnehin blockieren.

Asset-CDN: content.agentview.de

Asset-URLs sind ohne Authentifizierung abrufbar. CORS ist offen (Access-Control-Allow-Origin: *). Cache-Control: public, max-age=31536000, immutable. Der Wert immutable ist korrekt, da jeder Inhalt eine neue, content-adressierte URL erhält.

Token-Ablauf

JWT-Tokens (Session-Flow) leben 2 Stunden ab Ausstellung. Nach Ablauf liefern Endpoints HTTP 401 mit "error": "unauthorized". Lösung: neue Session via POST /api/v1/agent/session/request starten und auf GET /api/v1/agent/session/status?id=... pollen, bis "status": "active".

MCP

Model Context Protocol

Für KI-Clients die MCP sprechen (Claude Desktop, ChatGPT, etc.). Streamable HTTP mit OAuth 2.1 Authorization Code + PKCE.

Du suchst die End-User-Anleitung für Smartphone (Claude und ChatGPT als Connector)? Die liegt unter /anleitung/mcp-mobile-einrichten.

OAuth 2.1 + PKCE Flow

# 1. Discovery
GET /.well-known/oauth-protected-resource/mcp
GET /.well-known/oauth-authorization-server

# 2. Authorization
GET /authorize?response_type=code&code_challenge=...&code_challenge_method=S256
# User login + consent

# 3. Token
POST /token  (grant_type=authorization_code, code_verifier=...)

# 4. MCP-Calls
POST /mcp
Authorization: Bearer TOKEN
Mcp-Session-Id: SESSION_ID

Tools, Prompts und Resources sind maschinenlesbar unter GET /mcp/manifest verfügbar; nutze diesen Endpoint zur Capability-Discovery anstatt eine statische Liste zu pflegen. Kurzanleitung und Integrationshinweise: /agent-instructions.

Empfohlener Agent-Workflow

  1. Discovery: Lade GET /mcp/manifest (oder lies agentview://public/instructions via fetch) um Tools, Prompts und Resources zu kennen.
  2. Auth: Hat dein Client OAuth 2.1 mit PKCE? Nutze ihn. Sonst: ruf create_auth_session, zeige dem Nutzer die loginUrl, polle get_auth_session, der Server bindet das Token automatisch an die MCP-Session.
  3. Kontext lesen: get_account für Plan/Quota, list_displays für verfügbare Displays, get_display_capabilities für Browser-/Runtime-Limits eines Ziels.
  4. Inhalt finden oder erzeugen: Nutzer-Wunsch → entweder search_store_templates + send_store_template_to_display (vorgefertigte Designs), oder generiere HTML auf Basis des render_premium_display_html-Prompts und sende mit send_html.
  5. Live-Daten: Für mutierbare JSON-Daten nutze set_data_slot; Displays lesen sie über die öffentliche readUrl. Für externe Quellen siehe search_public_apis.

Tool-Katalog

Übersicht der MCP-Tools nach Domäne. Vollständige Schemas, Argumente und Output-Strukturen unter GET /mcp/manifest. GET = read-only, WRITE = mutiert, EDIT = idempotenter Update, DEL = destruktiv/löschend.

Auth & Identität

GET create_auth_session Browser-Login starten → loginUrl, sessionRequestId. Kein Bearer-Header nötig.
GET get_auth_session Login-Status pollen; bei active wird die MCP-Session automatisch authentifiziert.
GET authenticate Vorhandenes JWT auf der Session cachen (Fallback wenn der Client keinen Authorization-Header sendet).
GET logout Cached-Identity der MCP-Session löschen (revoked das JWT nicht).

Discovery, Suche & Public

GET get_public_status Öffentlicher Health-Check, Version, Discovery-URLs.
GET search Volltextsuche über agentView-Resources (Docs, Account, Displays, APIs).
GET fetch Vollständigen Resource-Inhalt per agentview://… URI laden.
GET search_public_apis 600+ kuratierte freie APIs (Wetter, News, Sport, Bilder, …) inkl. fetch()-Hint.
GET list_public_api_categories Alle Public-API-Kategorien mit API-Anzahl pro Kategorie. Vor search_public_apis für eine geführte Auswahl.

Account, Orgs & Lizenzen

GET get_account Profil, Plan, Display-Quota, Org-Mitgliedschaften.
GET list_organizations Alle Orgs des Nutzers mit Stats.
GET get_organization Mitglieder, Slots, Displays einer Org.
GET list_org_displays Org-Displays mit Online-Status.
GET get_license_info Premium-Lizenz-Pool und Allokationen.
WRITE create_organization Neue Org anlegen, Nutzer wird Owner. Admin-Scope.
EDIT rename_organization Org umbenennen.
DEL delete_organization Org löschen, Displays freigeben.
WRITE invite_member Invite-Link mit Rolle, optional E-Mail-gebunden, 7 Tage gültig.
EDIT update_member_role Rolle eines Mitglieds ändern.
DEL remove_member Mitglied entfernen, Displays an Nachfolger transferieren.
EDIT allocate_licenses Premium-Lizenzen an Org allokieren (0 deallokiert).
EDIT set_display_grant Nutzer Zugriff (view/control) auf Org-Display geben.
DEL remove_display_grant Zugriff eines Nutzers auf ein Display widerrufen.
DEL remove_display_from_org Display aus Org herausnehmen.
EDIT set_org_connectivity Default-Connectivity-Mode und globale Whitelist der Org setzen.

Display-Verwaltung

GET list_displays Alle erreichbaren Displays inkl. Runtime-Hints.
GET get_display Volldetails eines Displays.
GET get_display_capabilities Browser, Engine, Features, Limitations, recommendedDeliveryMode. Vor dem Senden lesen.
WRITE pair_by_code Bevorzugt für physische Geräte: 6-Code vom TV abfragen, in einem Schritt anlegen+pairen.
WRITE create_display Persönliches Display ohne Hardware (Vor-Provisionierung).
WRITE create_org_display Org-Display ohne Hardware. Lizenz benötigt.
WRITE claim_display Guest-/Demo-Display in eigenes Konto übernehmen.
EDIT rename_display Anzeigenamen ändern.
EDIT configure_display Kamera, Mikro, Geo, Sprache, Cursor, Badge, Watermark.
EDIT lock_display / unlock_display Schreibsperre setzen/lösen.
EDIT assign_license / unassign_license Premium-Lizenz zuweisen (entfernt Watermark, +30 MB Storage).
DEL delete_display Display und Inhalte unwiderruflich löschen.

Display-Kategorien (hierarchische Gruppen, Owner- oder Org-scoped, addressierbar via Slug oder Pfad)

GET list_display_categories Alle Kategorien des Nutzers mit Hierarchie und Mitgliedschaftsanzahl.
WRITE create_display_category Neue Kategorie anlegen, optional parent_category_id für Sub-Kategorie.
EDIT set_display_categories_for_display Vollständige Kategorie-Mitgliedschaft eines Displays setzen.
EDIT bulk_assign_display_category Eine Kategorie auf viele Displays in einem Aufruf zuweisen oder entfernen (mode: add|remove).
EDIT rename_display_category Kategorie umbenennen. Mitgliedschaften folgen automatisch.
WRITE broadcast_to_categories HTML an alle Displays mit den angegebenen Kategorien senden. include_descendants für Unter-Kategorien, dry_run für Vorschau ohne Versand.

Content-Delivery

WRITE send_html HTML/Base64 an ein Display senden. description (1-1000) ist Pflicht.
DRY test_display_content Dry-Run-Validator für HTML (Größe, Description). Kein echtes Display, kein Side-Effect. Vor send_html aufrufen, wenn LLM-generiertes HTML zuerst geprüft werden soll. 1 MB Limit.
WRITE send_url Externe URL im Vollbild-Iframe laden.
WRITE broadcast_content An mehrere Displays gleichzeitig (Liste oder all: true). Locked-Displays werden geskippt.
DEL clear_display Live-Inhalt entfernen, zurück auf Idle.
EDIT set_idle_content Idle-/Default-HTML setzen oder löschen.
GET get_display_content Aktueller Content-State + currentContentDescription.
GET read_display_html Roh-HTML lesen (live oder idle) für Inspect/Edit/Re-Upload.
EDIT set_display_privacy_mode Privacy-Modus eines Displays auf Private oder Public setzen. Bestimmt Max-TTL für Share-Links (1h vs 24h). Audit-geloggt.

Assets (Bilder, Fonts, Video, …)

WRITE upload_asset Base64-Files hochladen, stabile URLs zurück. 1-20 Files, max 10 MB pro File.
GET list_assets / get_asset Asset-Inventar durchsuchen, Metadaten holen (URL ändert sich nie).
EDIT update_asset Name/Beschreibung ändern.
DEL delete_asset Ein oder mehrere Assets löschen (max 100).

Data Slots (mutierbarer JSON-Storage)

EDIT set_data_slot Slot anlegen/aktualisieren. type: "aggregate" für JSON-Collections.
GET get_data_slot Inhalt eines Slots inkl. readUrl.
GET list_data_slots Alle Slots (nur Metadaten).
DEL delete_data_slot Slot löschen.
GET get_data_slot_usage Listet Displays, die {{slot:<slug>}} einbetten — vor delete oder strukturellen Änderungen aufrufen.
GET get_storage_quota Speicher-Pool-Snapshot (used/limit/remaining). Vor set_data_slot oder upload_asset mit großen Payloads aufrufen, um 413 zu vermeiden.

Store Templates (fertige Designs)

GET search_store_templates Designs durchsuchen (Bistro, Wartezimmer, Empfang, …).
GET list_store_categories Alle Kategorien mit Counts (de/en).
GET get_store_template_details Volle Markdown-Beschreibung eines Templates, inkl. agentArtifacts[]-Liste der mitgelieferten Bot-Onboarding-Files.
GET get_store_template_content Roher Display-HTML-Body eines Templates plus statische Slot-Definitionen und erlaubte Origins. {{asset:…}} aufgelöst, {{slot:…}} bleibt erhalten, zum Einfügen als bearbeitbare Folie.
GET get_store_template_agent_artifact Rohe Artefakt-Body (Prompt / SKILL.md / .mcp.json) eines Templates per (slug, key). Platzhalter unsubstituiert.
GET get_display_agent_artifact Artefakt-Body fertig für Paste/Save eines installierten Templates, mit {{slot:KEY.prop}}-Platzhaltern gegen die installierten Slot-Slugs des Displays substituiert.
GET get_store_template_install_options Erlaubte Ziel-Displays und benötigte Data Slots.
WRITE send_store_template_to_display Template installieren, optional data_slot_overrides direkt mit-baken.

API Keys

WRITE create_api_key Langlebigen avk_…-Key anlegen. Granular: Slot-/Display-Whitelist, Capabilities, read/write.
GET list_api_keys Eigene Keys auflisten (nur Prefix, nie Klartext).
DEL revoke_api_key Key sofort und unwiderruflich deaktivieren.

Prompts

Vorgefertigte Anweisungs-Templates die ein Client per prompts/get abruft, um den Agenten in eine Aufgabe zu lenken.

PROMPT onboard_first_display Schritt-für-Schritt-Anleitung für das erste Display eines Nutzers.
PROMPT recover_mcp_auth Diagnose-Leitfaden für OAuth-/Discovery-/Session-Fehler.
PROMPT render_premium_display_html Creative-Brief für hochwertige Display-HTML aus einem One-Shot-Wunsch.
PROMPT asset_workflow Anleitung zum Hochladen von Assets (Bilder, Schriften, Medien) und Referenzierung im Display-HTML.

Resources (agentview://…)

Lesbar via fetch-Tool oder direkt über das MCP resources/read-Verb. Public-Resources brauchen kein Login; account/… und display/… erfordern eine authentifizierte Session.

RES agentview://public/status Server-Health, Version, Discovery-URLs.
RES agentview://public/instructions Maschinenlesbare Agent-Anleitung (Markdown).
RES agentview://public/design-system Creative-Brief und Design-Tokens für Display-HTML.
RES agentview://public/developers Diese Entwickler-Doku als Resource.
RES agentview://public/mcp MCP-spezifische Hinweise (Manifest, Prompts, Resources).
RES agentview://public/oauth OAuth-Discovery-URLs und Flow-Hinweise.
RES agentview://public/support Support-Kanäle und Status-Links.
RES agentview://account/me Profil, Plan, Quota des authentifizierten Nutzers.
RES agentview://account/session Aktuelle MCP-Session-Identität (Scope, Auth-Quelle).
RES agentview://account/displays Live-Index der erreichbaren Displays.
RES agentview://display/{displayId} Live-Status, Settings und Management-URLs eines Displays.

Beispiel: Claude Desktop / generischer MCP-Client

In claude_desktop_config.json ergänzen oder mergen (existierende mcpServers beibehalten):

{
  "mcpServers": {
    "agentview": {
      "transport": {
        "type": "streamable-http",
        "url": "https://agentview.de/mcp"
      }
    }
  }
}
macOS ~/Library/Application Support/Claude/claude_desktop_config.json
Windows %APPDATA%\Claude\claude_desktop_config.json
Linux ~/.config/Claude/claude_desktop_config.json

Nach dem Speichern Claude Desktop neu starten — beim ersten Tool-Aufruf öffnet sich der Browser für OAuth-Login. Der Client erkennt OAuth automatisch über die WWW-Authenticate: Bearer resource_metadata=…-Antwort auf einen ersten POST /mcp ohne Token und löst PKCE eigenständig aus. Für Clients ohne OAuth: create_auth_session-Flow nutzen (siehe oben).

Zum Testen: MCP Inspector.

Content-Format

Was auf dem Display ankommt

Es wird eine einzelne HTML-Datei hochgeladen und in einem Fullscreen-Iframe gerendert. Die Datei darf externe Ressourcen laden (CDN-Fonts, Bilder, Scripts, APIs).

Hinweis: Displays können eingeschränkte Konnektivität haben. Prüfe GET /displays/{id}/capabilities vor dem Senden externer URLs oder komplexer HTML-/JS-Inhalte. Siehe Konnektivität.

POST /api/v1/agent/displays/ABCD1234/content
Authorization: Bearer TOKEN
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="/css/inter.css">
</head>
<body style="margin:0;height:100vh;display:grid;place-items:center;
  background:linear-gradient(135deg,#0a0a2e,#1a1a4e);color:#fff;font-family:Inter,sans-serif">
  <div style="text-align:center">
    <h1 style="font-size:4rem;margin:0">Welcome</h1>
    <p id="clock" style="font-size:2rem;opacity:0.7"></p>
  </div>
  <script>setInterval(()=>document.getElementById('clock').textContent=
    new Date().toLocaleTimeString(),1000)</script>
</body>
</html>

Alternativ: POST /displays/{id}/url für eine beliebige URL im iframe, wenn eine externe Webseite unverändert angezeigt werden soll.

Konnektivität

Advisory Connectivity System

Jedes Display hat ein aufgelöstes Konnektivitätsprofil. Admins deklarieren die Netzwerk-Topologie, und der Agent nutzt diese Info, um Inhalte passend auszuliefern. Zusätzlich meldet das Display konkrete Browser- und Runtime-Fakten wie Fetch, WebSockets, srcdoc, CSS-Fähigkeiten und bekannte Einschränkungen. Das System ist rein informativ. Der Server blockt keine Requests.

Drei Modi

  • full-access: Display kann jede URL laden (Standard für private Displays).
  • whitelist-only: Nur URLs die der Whitelist entsprechen. Alles andere als eingebetteten HTML-Payload senden.
  • isolated: Keine externen URLs. Alle Inhalte müssen als Base64/HTML-Payload eingebettet werden.

Vererbung

Einstellungen werden hierarchisch aufgelöst: System-Default → Organisation → Display-Override. Whitelists werden per Union zusammengeführt, es sei denn Strict-Mode ist aktiv.

API-Beispiel

GET /api/v1/agent/displays/ABCD1234/capabilities
Authorization: Bearer TOKEN
{
  "displayId": "ABCD1234",
  "connectivity": {
    "resolvedMode": "whitelist-only",
    "effectiveWhitelist": [
      "*.mycompany.corp",
      "weather-service.io",
      "local-intranet.local"
    ],
    "supportsEmbeddedContent": true
  },
  "runtime": {
    "userAgent": "Mozilla/5.0 (SMART-TV; Linux; Tizen 3.0) AppleWebKit/537.36 Chrome/47.0.2526.69 Safari/537.36",
    "browser": {
      "name": "Chrome",
      "version": "47.0.2526.69",
      "major": 47,
      "source": "user_agent"
    },
    "screen": {
      "width": 1920,
      "height": 1080,
      "resolution": "1920x1080",
      "source": "feature_probe"
    },
    "hasTouch": false,
    "featureSource": "feature_probe",
    "features": {
      "canRenderCustomHtml": true,
      "canRunCustomJavaScript": true,
      "supportsFetch": false,
      "supportsWebSockets": true,
      "supportsIframeSrcdoc": false,
      "supportsCssVariables": true,
      "supportsBackdropFilter": false,
      "supportsModernJsSyntax": false
    },
    "knownLimitations": [
      "Fetch API is unavailable. Prefer inline data or simpler HTML payloads.",
      "iframe srcdoc is unavailable. Prefer file-backed content URLs over inline iframe payloads.",
      "Modern JavaScript syntax is unavailable. Transpile ES6+ code before sending it."
    ],
    "recommendedDeliveryMode": "simple_html"
  },
  "metadata": {
    "inheritedFrom": "Organization-Default"
  }
}
Advisory, not Enforcement: Der Server blockt keine Requests, die gegen die Whitelist verstoßen. Der Agent wird erwartet, die Capabilities zu prüfen und Inhalte entsprechend auszuliefern.
Response-Beispiel

GET /api/v1/agent/displays

{
  "displays": [
    {
      "id": "ABCD1234",
      "name": "Lobby",
      "status": "degraded",
      "isOnline": false,
      "isDegraded": true,
      "isReachable": true,
      "statusHint": "Display is reachable without a live connection. Content changes can arrive with delay.",
      "lastSeen": "2026-03-16T14:23:00Z",
      "lastFallbackPollAt": "2026-03-16T14:24:00Z",
      "locked": false,
      "setupUrl": "https://display.agentview.de/p/AB3K7F",
      "pairingUrl": "https://display.agentview.de/p/AB3K7F",
      "managedUrl": null,
      "approvalUrl": null,
      "settings": {
        "allowCamera": false,
        "allowMicrophone": false,
        "allowGeolocation": false,
        "showMouseCursor": false,
        "showBadgeOverlay": true,
        "watermarkPosition": "BottomLeft"
      }
    }
  ],
  "total": 1,
  "remainingSlots": 2
}

Statuswerte: online = aktive Echtzeitverbindung, degraded = erreichbar ohne aktive Echtzeitverbindung, offline = aktuell nicht erreichbar.

Sharing: Es gibt keine permanent öffentliche Display-URL mehr. Um anderen zu zeigen, was gerade auf dem Bildschirm läuft, generiere einen kurzlebigen signierten Link über POST /api/v1/agent/displays/{id}/preview-link bzw. das MCP-Tool get_display_preview_url. Der Link verfällt automatisch nach Ablauf der TTL oder sobald der Owner neuen Inhalt pusht.

Website-Embedding: Displays mit privacyMode: "Public" können direkt in externe Websites oder CMS eingebettet werden. Nutze GET /display-embed/{profileId} als iframe-src. Standardmaessig (leere Allowlist) ist das Display von jeder Origin einbettbar (frame-ancestors *). Optional kannst du die Einbettung auf bestimmte Origins einschraenken: PATCH /api/v1/agent/displays/{id}/embeddable-origins bzw. MCP-Tool set_display_embeddable_origins mit einem Array wie ["https://deine-website.de"]. Private Displays geben immer 404 zurueck.

Rate Limiting

Response-Headers

Rate-limitierte Endpoints liefern diese Headers in jeder Response:

X-RateLimit-Limit: 100
X-RateLimit-Window: 60
X-RateLimit-Policy: StoragePolicy

Bei Überschreitung: 429 Too Many Requests. Betrifft Login-Endpoints (10/min), Content-Uploads (100/min) und Cancellations (5/10min).