Integrationsunterlagen · v0.1.0 Integration material · v0.1.0
Eine Checkliste für den Tag, an dem Sie anfangen. Sie ist bewusst zweigeteilt: oben steht, was Sie zum Starten brauchen, unten, was Sie zum Livegehen brauchen — und dazwischen liegt eine Menge Arbeit, die Sie ohne einen einzigen Zugangsschlüssel erledigen können. A checklist for the day you begin. It is deliberately split in two: what you need to start comes first, what you need to go live comes second — and a great deal of work sits in between that you can do without a single credential.
Der wichtigste Satz auf dieser SeiteThe most important sentence on this page
Die Plattform läuft vollständig auf Mocks — mit null Zugangsdaten. Das ist keine Demo-Einschränkung, sondern die tragende Eigenschaft: Jede kaufbare Fähigkeit hat einen deterministischen Mock, und der Mock ist überall der Standard. Sie können also die Konsolen bedienen, Wissen aufnehmen und freigeben, Gespräche end-to-end durchspielen, gegen /v1 entwickeln und Ihre Webhook-Empfänger bauen, bevor irgendjemand einen Vertrag mit einem Anbieter unterschrieben hat. Eine Änderung, die einen Zugangsschlüssel nötig macht, um die Testsuite grün zu bekommen, wird abgelehnt. The platform runs fully on mocks — with zero credentials. That is not a demo limitation, it is the load-bearing property: every buyable capability has a deterministic mock, and the mock is the default everywhere. So you can drive the consoles, ingest and approve knowledge, play conversations through end to end, develop against /v1 and build your webhook receivers before anyone has signed a contract with a provider. Any change that makes a credential necessary to reach a green test suite is rejected.
LaufzeitRuntime
Erst beim LivegangOnly when going live
Jede Zeile schaltet unabhängig um. Es gibt keinen Stichtag, an dem alles gleichzeitig live gehen muss — Sie liefern einen Schlüssel, setzen eine Variable, und genau dieser eine Pfad verlässt die Mock-Welt. Each row flips independently. There is no cut-over day on which everything must go live at once — you supply a key, set a variable, and exactly that one path leaves the mock world.
| FähigkeitCapability | Was Sie liefernWhat you supply | Was es freischaltetWhat it unlocks |
|---|---|---|
| SprachmodellLanguage model | ANTHROPIC_API_KEY ANTHROPIC_BASE_URL |
Echte Antworten statt Mock-Antworten. ANTHROPIC_BASE_URL zeigt optional auf ein EU-Gateway oder einen Bedrock-Proxy — das ist der Weg für „Frankfurt statt weltweit“. Alternativ stehen OpenAI, Gemini, Grok und ein selbstgehostetes offenes Modell hinter demselben Router.Real answers instead of mock answers. ANTHROPIC_BASE_URL optionally points at an EU gateway or a Bedrock proxy — that is the route to "Frankfurt, not worldwide". OpenAI, Gemini, Grok and a self-hosted open model sit behind the same router as alternatives. |
| SpracherkennungSpeech to text | SPEECHMATICS_API_KEY PARAKEET_URL |
Gesprochenes wird zu Text. Speechmatics ist der gemanagte Weg; Parakeet ist selbstgehostet und braucht keinen Schlüssel, nur eine erreichbare Adresse — es ist der Pfad für die Bahn RESTRICTED. Hinweis: DEEPGRAM_API_KEY steht in der Beispielumgebung, aber ein Deepgram-Adapter ist in diesem Repository noch nicht enthalten.Speech becomes text. Speechmatics is the managed route; Parakeet is self-hosted and needs no key, only a reachable address — it is the path for the RESTRICTED lane. Note: DEEPGRAM_API_KEY appears in the example environment, but no Deepgram adapter ships in this repository yet. |
| SprachsyntheseText to speech | FISH_API_KEY AZURE_SPEECH_KEY · AZURE_SPEECH_REGION ELEVENLABS_API_KEY · ELEVENLABS_VOICE_ID CARTESIA_API_KEY |
Der Agent bekommt eine Stimme. Vier gemanagte Anbieter stehen bereit, dazu Piper als selbstgehostete Stimme ohne Cloud-Zugangsdaten. Fehlt der Schlüssel zum gewählten Anbieter, fällt die Fabrik mit einer Warnung auf den Mock zurück, statt den Dienst zu stoppen.The agent gets a voice. Four managed providers are available, plus Piper as a self-hosted voice with no cloud credential. If the selected provider's key is missing, the factory falls back to the mock with a warning rather than taking the service down. |
| Sprache-zu-SpracheSpeech to speech | OPENAI_REALTIME_URL GEMINI_LIVE_URL |
Nur nötig, wenn Sie den Realtime-Modus statt der Kaskade fahren wollen. Beide Adressen sollten auf eine EU-Ressource zeigen (Azure OpenAI beziehungsweise Vertex EU).Only needed if you want realtime mode instead of the cascade. Both URLs should point at an EU resource (Azure OpenAI and Vertex EU respectively). |
| MedientransportMedia transport | LIVEKIT_URL LIVEKIT_API_KEY LIVEKIT_API_SECRET |
Echte Audioräume: WebRTC für den Browser, die Brücke für das Telefon. Ohne das läuft Sprache nur gegen den Mock-Transport.Real audio rooms: WebRTC for the browser, the bridge for the phone. Without it, voice only runs against the mock transport. |
| TelefonieTelephony | SIP-Anschluss und mindestens eine Rufnummera SIP trunk and at least one phone number | Erst damit erreicht ein echter Anrufer den Agenten. Das ist keine Umgebungsvariable dieser Plattform, sondern eine Konfiguration auf der LiveKit-Seite plus ein Vertrag mit einem Anbieter. Beachten Sie den offenen Befund H2: die Token-Ausgabe für LiveKit muss authentifiziert und ratenbegrenzt sein, bevor eine Nummer scharf geschaltet wird.Only this lets a real caller reach the agent. It is not an environment variable of this platform but a configuration on the LiveKit side plus a contract with a carrier. Mind open finding H2: LiveKit token minting must be authenticated and rate-limited before a number is switched on. |
| ERPERP | ODOO_URL · ODOO_DB ODOO_USER · ODOO_API_KEY |
Der Agent sieht echte Kunden, Aufträge und Tickets und kann Vorschläge einstellen. Vergeben Sie einen eigenen technischen Benutzer mit den kleinstmöglichen Rechten — der Agent schreibt ohnehin nur Vorschläge, und die Trennung macht das Protokoll lesbar.The agent sees real customers, orders and tickets, and can file proposals. Issue a dedicated technical user with the smallest workable rights — the agent only writes proposals anyway, and the separation is what makes the audit trail readable. |
Umgebungsvariablen · PlattformEnvironment variables · platform
Diese Variablen gehören dem Deployment, nicht einem Mandanten. Sie entscheiden, ob überhaupt echte Engines gebaut werden dürfen und wer die Konsole betreten darf. These belong to the deployment, not to a tenant. They decide whether real engines may be constructed at all, and who may enter the console.
| NameName | GültigkeitScope | BedeutungMeaning | StandardDefault |
|---|---|---|---|
| ACP_ENV | Plattformplatform | Die Haltung des Deployments. Nur mock, test und ci gelten als offline; alles andere — auch production, prod oder ein Tippfehler — gilt als LIVE.The deployment's posture. Only mock, test and ci count as offline; anything else — including production, prod or a typo — counts as LIVE. | mock |
| ACP_LOG_LEVEL | Plattformplatform | Schwelle der Protokollierung.Logging threshold. | INFO |
| ACP_LOG_FORMAT | Plattformplatform | console für die Entwicklung, json für die Produktion — eine Zeile, ein Objekt.console for development, json for production — one object per line. | console |
| ACP_CONSOLE_SECRET | Plattformplatform | HMAC-Schlüssel, mit dem Konsolen-Sitzungen signiert werden. In live Pflicht und mindestens 32 Byte lang, sonst startet der Prozess nicht. Auf Mocks wird ein flüchtiger Schlüssel pro Prozess erzeugt — es gibt keinen fest eingebauten Standardwert mehr.The HMAC key that signs console sessions. Mandatory in live and at least 32 bytes long, otherwise the process refuses to start. On mocks an ephemeral per-process key is minted — there is no hardcoded default any more. | nicht gesetztunset |
| ACP_CONSOLE_USERS | Plattformplatform | Pfad zu console_users.json mit argon2id-Hashes. Fehlt die Datei, kann sich niemand anmelden; ist sie unlesbar oder kaputt, ist das ein Konfigurationsfehler und kein „keine Benutzer“.Path to console_users.json holding argon2id hashes. A missing file means nobody can log in; an unreadable or malformed one is a configuration error, not "no users". | nicht gesetztunset |
| ACP_ALLOW_ANON | Plattformplatform | Öffnet die Konsole ohne Anmeldung — ausschließlich für lokale Entwicklung. In einem live gewerteten Deployment wird die Variable abgelehnt, nicht ignoriert.Opens the console with no login — local development only. In a deployment that counts as live the variable is refused, not ignored. | nicht gesetzt (falsch)unset (false) |
| ACP_INGEST_TOKEN | Plattformplatform | Bearer-Token der Ingest-Brücke, über die Voice-Worker Ereignisse einliefern. In live Pflicht. Ist er nicht gesetzt, ist die Brücke geschlossen (503) — früher hieß „kein Token“ einmal „keine Prüfung nötig“.Bearer token for the ingest bridge through which voice workers deliver events. Mandatory in live. If it is unset the bridge is closed (503) — "no token" once meant "no check required". | nicht gesetztunset |
| ACP_METRICS_TOKEN | Plattformplatform | Bearer-Token für den Prometheus-Abgriff an /metrics. Nicht gesetzt heißt auch hier: Endpunkt geschlossen (503), nicht offen.Bearer token for the Prometheus scrape at /metrics. Unset means the same thing here: endpoint closed (503), not open. | nicht gesetztunset |
| ACP_DATA_ROOT | Plattformplatform | Wurzel, unter der die Daten jedes Mandanten liegen: <root>/<mandant>/transcripts und so fort.The root under which every tenant's data lives: <root>/<tenant>/transcripts and so on. | var/tenants |
| ACP_WEBHOOK_ALLOW_HOSTS | Plattformplatform | Kommagetrennte Ausnahmeliste: genau diese Hostnamen dürfen Webhook-Ziel sein, obwohl sie privat auflösen. Bewusst opt-in und niemals Standard.A comma-separated exception list: exactly these hostnames may be webhook destinations even though they resolve privately. Deliberately opt-in, never the default. | leerempty |
| ACP_TRANSCRIPT_DIR | Plattformplatform | Altlast. Wird heute nur noch benutzt, um console_users.json zu finden, wenn ACP_CONSOLE_USERS fehlt. Für neue Deployments: ACP_DATA_ROOT setzen und diese Variable weglassen.Legacy. Today it is used only to locate console_users.json when ACP_CONSOLE_USERS is absent. For new deployments: set ACP_DATA_ROOT and leave this one out. | leerempty |
| ACP_SMOKE_OUT | Werkzeugtooling | Nur für das Sprach-Smoke-Skript: Dateipfad, in den die erzeugte Audioausgabe geschrieben wird. Für den Betrieb ohne Bedeutung.Used only by the voice smoke script: the file path the generated audio is written to. Of no operational significance. | nicht gesetztunset |
Umgebungsvariablen · MandantEnvironment variables · tenant
Im Einzelmandanten-Betrieb kommen diese Werte aus der .env und beschreiben den Standardmandanten. Sobald mehrere Mandanten laufen, werden sie je Mandant aus einem expliziten Mapping aufgelöst — dieselben Namen, andere Quelle. In single-tenant operation these come from .env and describe the default tenant. Once several tenants run, they are resolved per tenant from an explicit mapping — same names, different source.
| NameName | GültigkeitScope | BedeutungMeaning | StandardDefault |
|---|---|---|---|
| ACP_TENANT_ID | Mandanttenant | Kennung des Standardmandanten. Ein geprüfter DNS-tauglicher Slug: Kleinbuchstaben, Ziffern, einzelne Bindestriche, höchstens 63 Zeichen, kein reserviertes Wort. Groß- und Kleinschreibung wird normalisiert.The default tenant's id. A validated DNS-safe slug: lowercase, digits, single hyphens, at most 63 characters, no reserved word. Case is normalised. | default |
| ACP_TENANT_NAME | Mandanttenant | Anzeigename des Mandanten für die Konsolen.The tenant's display name for the consoles. | der Slugthe slug |
| ACP_REGION | Mandanttenant | Region der Datenhaltung. Voreingestellt ist Frankfurt.The data-residency region. Frankfurt by default. | eu-central-1 |
| ACP_APPROVAL_EUR | Mandanttenant | Betrag in Euro, oberhalb dessen eine Aufsicht freigeben muss, bevor der Agent handelt.The amount in euros above which a supervisor must approve before the agent acts. | 100 |
| ACP_CONFIDENTIAL_FRONTIER | Mandanttenant | Dürfen Frontier-APIs vertraulichen Verkehr bedienen? Nur auf true setzen, wenn eine EU-Region und eine Zero-Retention-Vereinbarung tatsächlich vorliegen — die Variable behauptet diese Haltung, sie prüft sie nicht.May frontier APIs serve confidential traffic? Set it to true only when an EU region and a zero-retention agreement genuinely exist — the variable asserts that posture, it does not verify it. | false |
| ACP_STT | Mandanttenant | Spracherkennung: mock, speechmatics oder parakeet. Ein unbekannter Name wird abgelehnt.Speech recognition: mock, speechmatics or parakeet. An unknown name is rejected. | mock |
| ACP_TTS | Mandanttenant | Sprachsynthese: mock, azure, elevenlabs, cartesia, fish oder piper.Speech synthesis: mock, azure, elevenlabs, cartesia, fish or piper. | mock |
| ACP_TRANSPORT | Mandanttenant | Medientransport: mock oder livekit.Media transport: mock or livekit. | mock |
| ACP_VOICE_MODE | Mandanttenant | cascaded (STT → LLM → TTS) oder realtime (Sprache zu Sprache). Eine als RESTRICTED eingestufte Bahn wird unabhängig davon in die Kaskade gezwungen.cascaded (STT → LLM → TTS) or realtime (speech to speech). A lane classified RESTRICTED is forced into the cascade regardless. | cascaded |
| ACP_REALTIME | Mandanttenant | Anbieter für den Realtime-Modus: mock, openai oder gemini.Provider for realtime mode: mock, openai or gemini. | mock |
| ACP_TURN_BUDGET_MS | Mandanttenant | Zeitbudget für einen Gesprächszug in Millisekunden. Überschreitungen werden gezählt und können als Ereignis herausgehen.The latency budget for one conversation turn, in milliseconds. Breaches are counted and can leave as an event. | 800 |
| ODOO_URL · ODOO_DB ODOO_USER · ODOO_API_KEY |
Mandanttenant | Kein ACP_-Präfix, aber Mandantensache: die ERP-Zugangsdaten gehören zu den Settings eines Mandanten, damit zwei Mandanten getrennte Odoo-Instanzen bedienen können.No ACP_ prefix, but tenant-scoped all the same: the ERP credentials live in a tenant's settings, so two tenants can serve separate Odoo instances. | leerempty |
GeheimnisseSecrets
Jedes Geheimnis darf als Datei kommen. Statt FOO=… setzen Sie FOO_FILE=/run/secrets/foo; die Datei gewinnt gegen den Inline-Wert. Das ist die Form, die Docker- und Kubernetes-Secrets ohnehin haben — und im Gegensatz zu einer Umgebungsvariablen landet ein Dateipfad weder in docker inspect noch in einem Absturzabbild. Every secret may arrive as a file. Instead of FOO=… set FOO_FILE=/run/secrets/foo; the file wins over the inline value. That is the shape Docker and Kubernetes secrets already have — and unlike an environment variable, a file path lands neither in docker inspect nor in a crash dump.
Ein leerer oder nur aus Leerzeichen bestehender Wert gilt als nicht gesetzt — bei beiden Formen. Ein versehentliches FOO= in einer .env darf nicht als „konfiguriert“ durchgehen, und eine leere Secret-Datei ebenso wenig. Aufgelöste Geheimnisse werden in einen Wrapper gelegt, dessen Textdarstellung redigiert ist, damit eine Protokollzeile oder ein Traceback Secret(ANTHROPIC_API_KEY=***) zeigt und nicht den Schlüssel. An empty or whitespace-only value counts as unset — in both forms. An accidental FOO= in a .env must not read as "configured", and neither must an empty secret file. Resolved secrets are wrapped in a type whose text representation is redacted, so a log line or a traceback shows Secret(ANTHROPIC_API_KEY=***) rather than the key.
NetzwerkNetwork
AusgehendOutbound
Nur zu den Engines, die Sie tatsächlich gewählt haben — solange alles auf Mocks läuft, braucht die Plattform überhaupt keinen Ausgang. Live sind das: HTTPS zum LLM-Anbieter oder Ihrem EU-Gateway, HTTPS beziehungsweise WebSocket zu STT und TTS, WebSocket zu LiveKit, JSON-RPC zu Odoo und, falls selbstgehostet, HTTP zu Parakeet, Piper und Ihrem eigenen Modellserver. Only to the engines you actually selected — while everything runs on mocks the platform needs no egress at all. Live, that means: HTTPS to the LLM provider or your EU gateway, HTTPS or WebSocket to STT and TTS, WebSocket to LiveKit, JSON-RPC to Odoo and, if self-hosted, HTTP to Parakeet, Piper and your own model server.
Dazu kommt die Webhook-Zustellung an Ihre Empfänger — und die ist der Grund, warum es überhaupt eine Egress-Richtlinie gibt. Add webhook delivery to your receivers — which is the reason an egress policy exists at all.
EingehendInbound
HTTPS auf die Konsolen, auf /v1 für Ihre Server, auf die Widget-Endpunkte für die Browser Ihrer Besucher und, bei Telefonie, den Medienpfad zu LiveKit. HTTPS to the consoles, to /v1 for your servers, to the widget endpoints for your visitors' browsers and, with telephony, the media path to LiveKit.
Für Webhooks brauchen Sie einen eingehenden Endpunkt: öffentlich erreichbar, per HTTPS, der die Signatur prüft. Der signierte Text ist "{zeitstempel}.{rumpf}" — der Zeitstempel steckt also innerhalb der Signatur, weshalb eine mitgeschnittene Zustellung später nicht mehr erneut eingespielt werden kann, wenn Ihr Empfänger die Frische prüft. Weisen Sie alles zurück, was älter als 300 Sekunden ist. Ein Mandant darf höchstens 20 Endpunkte registrieren. For webhooks you need an inbound endpoint: publicly reachable, over HTTPS, verifying the signature. The signed material is "{timestamp}.{body}" — the timestamp sits inside the signature, which is why a captured delivery cannot be replayed later against a receiver that checks freshness. Reject anything older than 300 seconds. One tenant may register at most 20 endpoints.
Egress-Richtlinie für Webhook-ZieleEgress policy for webhook destinations
Wenn ein Mandant eine Webhook-URL wählt, wählt er ein Ziel, das die Plattform aus ihrer eigenen Vertrauenszone heraus anspricht. Ohne Richtlinie wäre das serverseitige Anfragefälschung: Cloud-Metadatendienste, Loopback-Adminports und interne Service-Meshes sind von einem Worker aus erreichbar, aus dem Internet aber nicht. Deshalb gilt: When a tenant chooses a webhook URL, it chooses a destination the platform will connect to from inside its own trust boundary. Without a policy that is server-side request forgery: cloud metadata services, loopback admin ports and internal service meshes are reachable from a worker but not from the internet. So:
Was Sie auf keinen Fall tun dürfenWhat you must never do
Ein API-Schlüssel (acp_sk_…) gewährt Lese- und Schreibzugriff auf die gesamte API eines Mandanten. In eine öffentliche Seite kopiert ist er keine Konfiguration mehr, sondern ein veröffentlichtes Geheimnis — jeder Besucher kann ihn lesen. Ein Widget trägt deshalb einen öffentlichen Embed-Schlüssel (acp_pk_…), der absichtlich anders aussieht, damit die Verwechslung auf einen Blick auffällt. Er ist öffentlich by design und beweist für sich genommen nichts: was schützt, ist die Bindung an eine Origin-Liste. Der Browser setzt den Origin-Header, eine Seite kann ihn nicht fälschen — ein von Ihrer Seite abgeschriebener Schlüssel funktioniert auf einer fremden Seite also nicht. Und er kauft ohnehin nur eine kurzlebige Sitzung von 30 Minuten, mit der das Widget dann arbeitet. Der Schadensradius eines kopierten Embed-Schlüssels ist damit „jemand kann von Ihrer eigenen Domain aus Chats starten“, nicht „jemand besitzt Ihre API“. Eine leere Origin-Liste erlaubt übrigens nichts: ein Schlüssel ohne konfigurierte Herkunft ist ein unfertiges Setup, und unfertig darf nicht offen heißen. An API key (acp_sk_…) grants read and write across a tenant's entire API. Pasted into a public page it stops being configuration and becomes a published secret — every visitor can read it. A widget therefore carries a public embed key (acp_pk_…), deliberately shaped to look different so the mix-up is obvious on sight. It is public by design and proves nothing on its own: what protects you is the binding to an origin allow-list. The browser sets the Origin header and a page cannot forge it — so a key lifted from your site does not work on someone else's. And it only buys a short-lived session of 30 minutes, which is what the widget actually uses. The blast radius of a copied embed key is therefore "someone can start chats from your own domain", not "someone owns your API". An empty origin list, incidentally, permits nothing: a key with no configured origin is an unfinished setup, and unfinished must not mean open.
| acp_sk_… | acp_pk_… | |
|---|---|---|
| Gehört aufBelongs on | einen Server, in einen Geheimnisspeichera server, in a secret store | eine öffentliche HTML-Seitea public HTML page |
| VertraulichkeitSecrecy | geheimsecret | öffentlich, absichtlichpublic, by design |
| UmfangScope | Lesen und Schreiben, der ganze Mandantread and write, the whole tenant | einen Chat beginnen, sonst nichtsstart a chat, nothing else |
| Gebunden anBound to | nichtsnothing | eine ausdrückliche Origin-Listean explicit origin allow-list |
| GewährtGrants | API-Zugriff unmittelbarAPI access directly | nur eine kurzlebige Sitzungonly a short-lived session |
<script src="https://acp.example/static/widget/acp-widget.js"
data-key="acp_pk_..." data-lang="de" async></script>
Zwei weitere Dinge, die die Konsolen absichtlich nicht tun: kein Schreib-Zugangsdatum wird im localStorage abgelegt (dort wäre es unbegrenzt für jedes Skript der Origin lesbar, lange nachdem der Bediener weggegangen ist), und Modellausgabe wird im Widget über textContent gesetzt, nie über innerHTML — was ein Modell sagt, darf auf einer fremden Seite nicht ausführbar werden. Two more things the consoles deliberately do not do: no write credential is kept in localStorage (where it would stay readable by any script on the origin, indefinitely, long after the operator walked away), and model output in the widget is set via textContent, never innerHTML — what a model says must not become executable in someone else's page.
DatenschutzData protection