Entwickler / Python

Die Python-SDK

Chats lesen und beantworten, Bilder und Videos senden, Bots erstellen und den KI-Agenten aus den eigenen Systemen abfragen lassen, mit Python. Alles, was die REST API kann, wobei die heiklen Teile bereits erledigt sind: prüfen, ob eine Anfrage wirklich von uns stammt, nicht zweimal auf dasselbe Ereignis antworten, sicher wiederholen und fehlerhafte Nachrichten abfangen, bevor sie Ihren Prozess verlassen.

Neu bei Conecto? Hier beginnen , fünf Minuten Begriffe, danach erklärt sich der Rest der Seite von selbst.

pip install conectov0.1.0Python 3.9+MIT

One dependency (requests) · Type hints throughout · Auf PyPI

Hier beginnen

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Wenn Sie Conecto noch nie verwendet haben, vermittelt dieser Abschnitt das gesamte mentale Modell. Fünf Minuten hier machen den Rest der Seite verständlich.

Was Conecto ist

Ein Chat-Widget auf Ihrer Website. Besucher schreiben hinein; Ihr Team, ein KI-Agent oder Ihr eigener Code antwortet. Alles Folgende dient dazu, Ihren Code Ihren Code in diesen Ablauf einzubinden.

Die fünf Substantive

Fast jede Methode dieses SDK akzeptiert oder gibt eines dieser Objekte zurück.

Workspace

Ihr Konto. Ihre Zugangsdaten gehören genau zu einem Konto und können niemals ein anderes sehen.

Widget

One installed chat box. You might have one per website or brand. Has an id.

Besucher

A browser on your site, identified by a session string stored in that browser.

Gespräch

One thread between a visitor and you. Has an id, and a status of open or closed.

Nachricht

One bubble in a thread. Sent by a visitor, an agent (human), a bot, or the system.

Die drei Dinge, die Sie erstellen können

1. Ein Skript

Wird nach Ihrem Zeitplan ausgeführt. Synchronisiert Kontakte, öffnet Tickets und sendet Kampagnennachrichten. Kein Hosting erforderlich, das Skript ruft nur die API auf.

2. Ein Bot

Ein Webserver von Ihnen, den wir benachrichtigen, wenn ein Besucher schreibt. Sie bestimmen die Antwort. Sie die Unterhaltung zu steuern.

3. Ein Plugin

Ein Webserver von Ihnen, den wir aufrufen, wenn der KI eine Information benötigt, die nur Sie besitzen. Die KI steuert die Unterhaltung; Sie liefern die Abfragen.

Bot oder Plugin? Wenn Sie die Texte für Besucher selbst schreiben möchten, erstellen Sie einen Bot. Wenn Sie lieber die KI sprechen lassen und ihr nur Zugriff auf Ihre Daten wie Bestellungen, Abonnements oder Lagerbestände geben, erstellen Sie ein Plugin. Die meisten Teams entscheiden sich letztlich für ein Plugin.

Auf dieser Seite verwendete Begriffe

Fachbegriffe, einmal erklärt. Nur die letzten beiden sind Conecto-spezifisch.

WebhookWir senden bei einem Ereignis eine HTTP-Anfrage an Ihre URL, statt Sie ständig abfragen zu lassen. Dafür benötigen Sie einen über das Internet erreichbaren Server.
SignaturEin Hash, den wir jeder Anfrage hinzufügen und mit einem Secret berechnen, das nur Sie und wir kennen. Die Prüfung beweist, dass die Anfrage wirklich von uns stammt und nicht verändert wurde. Das SDK übernimmt dies für Sie.
Mindestens einmalWenn wir nicht feststellen können, ob Sie etwas erhalten haben, senden wir es erneut. Gelegentlich kann dasselbe Ereignis zweimal eintreffen; Ihr Code sollte das erkennen, statt zweimal zu handeln.
IdempotentEine zweimalige Ausführung hat dasselbe Ergebnis wie eine einmalige. Senden Sie zweimal eine Nachricht mit demselben Schlüssel, gibt der zweite Aufruf die erste Nachricht zurück, statt ein Duplikat zu veröffentlichen.
Cursor-PaginierungLange Listen werden seitenweise geliefert. Statt „Seite 3“ übergeben Sie die zuletzt erhaltene ID. Das SDK verbirgt dies, Sie iterieren einfach.
BlockEin Rich-Content-Element in einer Nachricht, etwa Bild, Video, Karte oder Schaltflächenzeile. Siehe Rich Content.
Verifizierter BesucherEine Person, deren E-Mail-Adresse wir bestätigt , entweder durch einen Code per E-Mail oder weil Ihre Website uns mitgeteilt hat, dass die Person angemeldet ist. Alles, was private Daten einer Person betrifft, wartet auf diese Bestätigung.

Welchen Abschnitt benötige ich?

Ich möchte…Gehen Sie zu
Chats aus einem Skript lesen oder beantwortenGespräche
Ein Bild, GIF, Video oder eine Karte sendenRich Content
Besuchern automatisch mit meiner eigenen Logik antwortenEinen Bot erstellen
Die KI in meinen Systemen nachschlagen lassenEin Plugin erstellen
Einen Shop anbinden, den die KI durchsuchen kannEin Plugin erstellen
E-Mail-Code für bereits angemeldete Benutzer überspringenIdentität
Mein CRM synchron haltenKontakte und Besucher
Wissen, welche Fehler abgefangen werden müssenFehler und Wiederholungsversuche

Installieren und authentifizieren

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok
pip install conecto

Zugangsdaten erstellen unter Einstellungen → Entwickler. Sie erhalten eine Client-ID (ck_…) and a Secret (cs_…). The secret is shown once and stored hashed, so put it somewhere your code can read and your repository cannot.

export CONECTO_CLIENT_ID=ck_your_client_id
export CONECTO_SECRET=cs_your_secret
from conecto import Conecto

client = Conecto()                    # reads the environment
client = Conecto("ck_...", "cs_...")  # or pass them directly

print(client.ping()["workspace"]["name"])

Call ping() once at startup. It raises AuthenticationError immediately if the credential is wrong or revoked — much better than finding out mid-conversation. A credential can cover the workspace or be scoped to one widget; a scoped one simply cannot see other widgets' conversations.

Schnellstart

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Drei Dinge, die Sie in den ersten fünf Minuten tun können.

Aktuelle Vorgänge lesen

for convo in client.conversations.list(status="open"):
    who = convo.visitor.email if convo.visitor else "anonymous"
    print(f"#{convo.id}  {who}  {convo.preview}")

Jemandem antworten

convo = client.conversations.get(4821)
print(convo.messages[-1].body)          # what they said last
convo.reply("On it — give me one moment.")

Etwas senden, das kein Text ist

from conecto import blocks

convo.reply("Here's how to reset it:", blocks=[
    blocks.image("https://cdn.you.com/reset.gif", alt="Reset flow"),
    blocks.buttons([
        blocks.reply_button("That worked"),
        blocks.link_button("Full guide", "https://docs.you.com/reset"),
    ]),
])

So ist die gesamte Bibliothek aufgebaut: ein Client mit Ressourcen, Objekte, die auf sich selbst einwirken können, und Builder für alle strukturierten Inhalte.

Der Client

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Erstellen Sie einen Client und behalten Sie ihn. Er bündelt Verbindungen und kann sicher von mehreren Threads verwendet werden.

client = Conecto(
    client_id="ck_...",
    secret="cs_...",
    base_url="https://conecto.chat/api/v1",  # override for staging
    timeout=30.0,        # seconds per request
    max_retries=2,       # timeouts, 429s and 5xx
    app="acme-billing",  # added to the User-Agent; shows up in our logs
)

Umgebung

VariableSetzt
CONECTO_CLIENT_IDDie Client-ID.
CONECTO_SECRETDas Secret.
CONECTO_BASE_URLBasis-URL der API. Selten erforderlich.

Ressourcen

AttributUmfasst
client.conversationsAuflisten, lesen, antworten, Tippanzeige, Übergabe, zuweisen, schließen.
client.contactsDas CRM: Upsert per E-Mail, suchen, aktualisieren, löschen.
client.visitorsSitzungen, Identitätsbestätigung und proaktive Nachrichten.
client.ticketsErstellen, aktualisieren, antworten, Kategorien.
client.articlesHilfe-Center-Inhalte suchen und veröffentlichen.
client.integrationsPlugins registrieren, installieren, ausführen und bereitstellen.
client.widgetsWidget-Konfiguration lesen und aktualisieren.
client.mediaDateien zur Verwendung in Blöcken hochladen.
client.webhooksEndpunkte für Ereignisse abonnieren.
client.membersTeammitglieder für Zuweisungen.
client.metame(), schema(), stats().

Versionsabweichung

Jede Antwort enthält die API-Version des Servers. Protokollieren Sie sie beim Start. Wenn sie sich ändert und ein verwendetes Feld verschwindet, ist dies die erste Information, die Sie benötigen.

print(client.api_version)   # what the server last reported
print(client.sdk_version)   # what this SDK was built against

limits = client.meta.schema()["limits"]
assert limits["blocks_per_message"] >= 4

client.meta.schema() gibt die gesamte API als JSON zurück, einschließlich Endpunkten, Ereignissen, Blocktypen, reservierten Aktionen, Fehlercodes und allen Limits. Dieselbe Codebasis setzt die Grenzen durch und liefert die Antwort, daher kann sie nicht von der Realität abweichen.

Gespräche

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

A thread between a visitor and your workspace. Messages have a sender von visitor, agent, bot oder system.

Auflisten und lesen

page = client.conversations.list(status="open", limit=25)

page.items                     # this page only
for convo in page:             # every page, fetched lazily
    ...

convo = client.conversations.get(4821)
convo.status, convo.visitor.email, convo.messages[-1].body

# Poll cheaply: only what is newer than what you already have.
fresh = client.conversations.messages(4821, since_id=last_seen_id)

Antworten

convo.reply("I can refund order #1284 — confirm?",
            buttons=["Yes, refund it", "Talk to a human"])

Ein Klick auf eine Schnellantwort kommt als normale Besuchernachricht mit diesem Text zurück. Ihr Handler benötigt daher keinen Sonderfall.

ArgumentFunktion
bodyDer Text, bis zu 4000 Zeichen.
blocksRich Content, siehe Rich Content.
buttonsBis zu 6 Schnellantworten.
productsBis zu 4 Produktkarten, entsprechend den von der integrierten KI angefügten Karten.
ask_emailZeigt das native Formular zur E-Mail-Erfassung.
ticket_formFügt das native Formular zum Öffnen eines Tickets an.
internalEine nur für Mitarbeiter sichtbare Notiz. Der Besucher sieht sie nie.
idempotency_keyÜbergeben Sie die ID einer Webhook-Zustellung, damit ein erneuter Versuch nicht doppelt veröffentlicht.

Thread steuern

convo.typing()                  # "…is typing", expires after ~8s
convo.reply("Working on it…")

convo.reply("Escalating: refund over policy limit.", internal=True)
convo.handoff()                 # flag for a human; routing rules apply
convo.assign(user_id=12)        # ids come from client.members.list()

convo.close()
convo.reopen()

Writing to a closed thread raises ConversationClosed. Reopen it first, or catch it — see Fehler.

Rich Content

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Jede Nachricht kann bis zu zehn enthalten Blöcke: Bilder und GIFs, Video, Audio, Dateien, Einbettungen von Drittanbietern, Kartenkarussells, Label/Wert-Listen, Schaltflächen und Trennlinien. Sie werden im Widget und im Posteingang des Mitarbeiters angezeigt.

Blöcke sind bei der Übertragung einfache Dicts und könnten manuell geschrieben werden. Die Builder existieren, weil ein falsch geschriebener Schlüssel dazu führt, dass ein Kunde meldet, ein Bild sei nie erschienen. Sie wenden dieselben Regeln wie der Server an, solange der Traceback noch auf Ihren Code zeigt.

from conecto import blocks

convo.reply("Here's your order:", blocks=[
    blocks.list_block([
        blocks.row("Placed", "12 July 2026"),
        blocks.row("Status", "Shipped"),
        blocks.row("Carrier", "DHL", subtitle="Tracking 4Z8871",
                   url="https://track.dhl.com/4Z8871"),
    ], title="Order #1042"),
    blocks.divider(),
    blocks.cards([
        blocks.card("Trail Runner 2",
                    subtitle="Road · Neutral",
                    price="89.00 USD",
                    badge="Back in stock",
                    image="https://cdn.you.com/tr2.jpg",
                    url="https://shop.you.com/p/tr2",
                    buttons=[blocks.link_button("Buy", "https://shop.you.com/cart")]),
    ]),
])

Jeder Blocktyp

BuilderNotizen
blocks.image(url, alt, caption, link)Animated GIFs are images — point at the .gif. blocks.gif is an alias.
blocks.video(url, poster, autoplay, loop, muted)Direct files only (.mp4, .webm, .mov…).
blocks.embed(url)YouTube, Vimeo, Loom, Wistia, Spotify. Übergeben Sie den normalen Teilen-Link.
blocks.audio(url, title).mp3, .wav, .m4a
blocks.file(url, filename, size)Eine Downloadzeile.
blocks.cards([...], layout)carousel scrolls sideways, list stacks full width.
blocks.list_block([...], title)Label/value rows. blocks.rows is an alias.
blocks.buttons([...])Mix reply_button und link_button.
blocks.text(body)Ein zusätzlicher Absatz unter anderen Blöcken.
blocks.divider()Eine horizontale Trennlinie.

Zwei Arten von Schaltflächen

blocks.reply_button("Yes, refund it")                    # sends that text back
blocks.reply_button("Yes, refund it", "confirm_refund")  # nice label, stable value
blocks.link_button("Read the policy", "https://you.com/refunds")  # opens a tab

Geben Sie einer Antwortschaltfläche einen expliziten Wert, wenn das Label gut lesbar sein soll, der vom Bot ausgewertete Text sich aber nicht bei jeder Textänderung ändern darf.

Ein GIF ausliefern

A .gif works. A muted, looping, autoplaying video is usually the better trade — a fraction of the bytes, and nobody can tell.

blocks.video("https://cdn.you.com/reset.mp4",
             autoplay=True, loop=True, muted=True)

Von den Buildern durchgesetzte Regeln

  • Every URL must be https. The widget runs on an https page, so anything else is blocked by the browser anyway.
  • Zehn Blöcke pro Nachricht, zehn Karten, zwölf Listenzeilen, sechs Schaltflächen.
  • Long strings are truncated, not rejected. Structural mistakes raise BlockError.
  • A YouTube link in a video block raises, and tells you to use embed.

Die Validierung erfolgt lokal. reply(blocks=…) checks the list before sending, so a typo raises BlockError from your line rather than coming back as an HTTP 400 from inside the client.

Dateien hochladen

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Keine öffentliche URL für dieses GIF, den Beleg oder das Datenblatt? Laden Sie die Datei mit bis zu 10 MB hoch und verwenden Sie das Ergebnis.

media = client.media.upload("celebrate.gif")     # a path
media = client.media.upload(open("a.pdf", "rb")) # a file object
media = client.media.upload(raw_bytes, filename="receipt.png")

convo.reply("All set!", blocks=[blocks.image(media)])

A Media can be passed straight to blocks.image, blocks.video oder blocks.file.

Use media.url, not media.absolute_url. url ist ein Pfad, den das Widget relativ zu seinem eigenen Ursprung auflöst. Dieselbe Nachricht funktioniert dadurch in Entwicklung, Vorschau und Produktion und auch nach einem Domainwechsel.

Kontakte und Besucher

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Kontakte

The CRM. upsert is keyed on email, so it is safe to point a nightly sync at.

client.contacts.upsert(
    "maya@acme.io",
    name="Maya Silva",
    company="Acme",
    custom_fields={"plan": "scale", "mrr": "480"},
)

contact = client.contacts.find("maya@acme.io")     # or None
for c in client.contacts.list(query="acme"):
    print(c.email, c.custom_fields.get("plan"))

custom_fields ist zusammengeführt, nicht ersetzt. Zwei Aufgaben können jeweils eigene Schlüssel verwalten, ohne einander zu überschreiben. Bis zu 30 Schlüssel pro Aufruf.

Besucher

A browser session on a widget, keyed by (widget_id, session). The session id lives in the visitor's browser; read it client-side and send it to your backend.

visitor = client.visitors.get(widget_id=7, session=session_id)
visitor.email, visitor.verified

Proaktive Nachrichten

An eine Sitzung senden, ohne auf eine Anfrage zu warten, etwa Versandupdates, Testhinweise oder Warenkorbwiederherstellung.

client.visitors.message(
    widget_id=7, session=session_id,
    body="Still thinking it over? Your cart is saved:",
    blocks=[blocks.cards([
        blocks.card("Trail Runner 2", price="89.00 USD",
                    image="https://cdn.you.com/tr2.jpg",
                    url="https://shop.you.com/cart"),
    ])],
)

Verwendet die aktive Unterhaltung der Sitzung erneut oder öffnet eine. Ein geöffnetes Widget zeigt sie bei der nächsten Abfrage; ein geschlossenes Widget beim nächsten Öffnen, als ungelesen markiert.

Tickets und Artikel

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Tickets

ticket = client.tickets.create(
    email="maya@acme.io",
    message="Card was declined on renewal.",
    priority="high",          # low · normal · high · urgent
)

client.tickets.update(ticket.id, status="pending", assignee_user_id=12)
ticket.reply("We've fixed the card on file — try again?")   # emails the requester
ticket.reply("Billing confirmed the retry.", internal=True)  # private note

Eine öffentliche Antwort öffnet ein gelöstes Ticket erneut, weil eine Antwort auf etwas als erledigt Markiertes fast immer eine neue Runde bedeutet.

Hilfe-Center-Artikel

In beide Richtungen nützlich: Ein Bot kann vor dem Öffnen eines Tickets darin suchen, und Sie können Dokumentation aus jedem Autorensystem hineinschieben.

hits = client.articles.list(query="refund", published=True)
if hits:
    convo.reply(f"This might help: “{hits[0].title}”")

client.articles.create("Shipping times", "<p>2-4 business days.</p>",
                       published=True)

HTML wird serverseitig anhand einer Positivliste sicherer Tags bereinigt, über dieselbe Pipeline wie im Dashboard-Editor. Skripte und Styles werden entfernt, bevor etwas gespeichert wird.

Einen Bot erstellen

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

A bot is a webhook and a reply. Bot handles the five things that are easy to get subtly wrong — signature verification, delivery deduplication, event routing, filtering out your own messages, and answering fast — so your code is the part that is yours.

from conecto import Conecto, Bot, blocks

client = Conecto()
bot = Bot(client, secret=WEBHOOK_SECRET)

@bot.on_message
def handle(ctx):
    text = ctx.text.lower()

    if "human" in text:
        ctx.reply("Of course — connecting you now.")
        ctx.handoff()
        return

    if "order" in text:
        if not ctx.verified:
            ctx.reply("What's the email on the order?", ask_email=True)
            return
        order = lookup_order(ctx.verified_email)     # your system
        ctx.reply("Here it is:", blocks=[
            blocks.list_block([
                blocks.row("Status", order["status"]),
                blocks.row("Carrier", order["carrier"]),
            ], title=order["number"]),
        ])
        return

    ctx.reply("I'm not sure about that one — let me get someone who is.")
    ctx.note(f"Bot could not classify: {ctx.text!r}")
    ctx.handoff()

Bereitstellung

# Flask
app.add_url_rule("/conecto", view_func=bot.flask_view(), methods=["POST"])

# FastAPI (handlers run in a worker thread, so they never stall the loop)
app.post("/conecto")(bot.fastapi_route())

# Django (urls.py)
path("conecto/", csrf_exempt(bot.django_view()))

# No framework at all
from wsgiref.simple_server import make_server
make_server("", 8000, bot.wsgi_app()).serve_forever()

Abonnieren Sie anschließend den Endpunkt:

hook = client.webhooks.create(
    "https://bots.you.com/conecto",
    ["message.created", "conversation.created", "conversation.rated"],
)
print(hook.secret)   # store this — it is how you verify deliveries

Handler

DecoratorWird ausgelöst bei
@bot.on_messageEine Nachricht von einem Besucher. Nachrichten von Bots und Mitarbeitern erreichen ihn nie. Dadurch antwortet ein Bot nicht auf sich selbst.
@bot.on_conversation_startedconversation.created , begrüßen oder einen Zustand einrichten.
@bot.on_ratingconversation.rated. ctx.event.rating has score und comment.
@bot.on_ticket · @bot.on_contactticket.created · contact.created.
@bot.on("event.name")Any event by name. @bot.on("*") catches everything.
@bot.on_errorEin Handler hat einen Fehler ausgelöst. Ohne einen solchen Handler werden Fehler protokolliert und verworfen.

Was ein Handler erhält

Im KontextobjektIst
ctx.textDer Nachrichtentext des Besuchers.
ctx.verified · ctx.verified_emailOb die Identität bestätigt ist und welche Adresse bestätigt wurde.
ctx.conversation_id · ctx.widget_id · ctx.sessionIDs, auf die Sie einwirken können.
ctx.reply(...)Answer. Takes everything conversations.reply does.
ctx.note(...)Eine nur für Mitarbeiter sichtbare Notiz.
ctx.typing() · ctx.handoff() · ctx.assign() · ctx.close()Den Thread steuern.
ctx.history()Den vollständigen Nachrichtenverlauf abrufen. Eine Anfrage.
ctx.client · ctx.eventDer Low-Level-Client und das geparste Ereignis für alles Weitere.

Mehr als einen Worker ausführen? Deduplication is in-process by default, so two workers can each handle the same delivery once. Pass your own store — anything with add(id) -> bool, backed by Redis or your database.

class RedisSeen:
    def add(self, delivery_id):
        # True if new, False if we have handled it before
        return bool(redis.set(f"conecto:{delivery_id}", 1, nx=True, ex=86400))

bot = Bot(client, secret=WEBHOOK_SECRET, seen=RedisSeen())

Ein Plugin erstellen

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Ein Bot beantwortet Webhooks. Ein Plugin ist die Gegenrichtung: Sie deklarieren, was Ihre Systeme können, und der KI-Agent entscheidet während des Gesprächs, wann er es verwendet. Sie schreiben keine Konversationslogik, nur die Abfrage.

So wird ein uns unbekannter Shop, ein Abrechnungssystem oder CRM zu einer vollwertigen Integration.

from conecto import Conecto, Plugin, parameter

plugin = Plugin("acme-store", base_url="https://api.acme.com/conecto")

@plugin.action(
    "catalog.search_products",
    description="Search the Acme catalog by keyword.",
    parameters=[parameter("query", required=True,
                          description="Search words from the visitor.")],
)
def search(req):
    hits = catalog.search(req.get("query"), limit=req.get("limit", 4))
    if not hits:
        return req.not_found()
    return req.ok(products=[
        {"title": p.name, "url": p.url, "image": p.image,
         "price_from": f"{p.price:.2f}", "currency": "USD",
         "available": p.stock > 0}
        for p in hits
    ])

@plugin.action(
    "orders.get_status",
    risk="verified_read",
    description="Status of one of the visitor's own orders.",
    parameters=[parameter("order_number", required=True)],
)
def order_status(req):
    order = orders.find(req.get("order_number"), email=req.verified_email)
    return req.ok(**order) if order else req.not_found()

Bereitstellen und registrieren

# One route serves every action.
app.add_url_rule("/conecto/<path:action>", view_func=plugin.flask_view(),
                 methods=["POST"])

# Tell Conecto it exists. Idempotent — run it on every release.
integration = plugin.deploy(Conecto())
print(integration.signing_secret)   # set this in your environment

deploy() erstellt oder aktualisiert die Integration passend zu Ihrem Code, löscht entfernte Aktionen und installiert sie auf Ihren Widgets. Vor der Installation wird der KI nichts offengelegt.

Die fünf Antwortarten

return req.ok(status="Shipped", carrier="DHL")   # success
return req.ok(result, blocks=[...])              # …plus rich content in the reply
return req.not_found()                           # nothing matched — a normal outcome
return req.verify_required()                     # "I need a proven identity"
return req.error("That subscription was already cancelled.")

Write error messages for the visitor, not for your logs: the AI may relay them. Returning a plain dict is also fine — it is treated as the result.

Risikostufen

RisikoBedeutung
public_readKatalog, Verfügbarkeit, Dokumentation. Keine Identität erforderlich.
verified_readDaten einer Person. Abgelehnt, bis ihre E-Mail-Adresse bestätigt ist.
public_writeEine Änderung ohne Identitätsprüfung, etwa Newsletter-Anmeldung oder Lead-Erfassung.
writeEine Änderung am Konto einer Person. Verifiziert, protokolliert und niemals stillschweigend wiederholt.

req.verified_email ist die einzige vertrauenswürdige Identität. An email in req.arguments is whatever the model parsed out of a chat. For verified_read und write the platform will not call you at all until we have proven who the visitor is, and the proven address is the one we hand you.

Reservierte Aktionen

Einige Namen haben für die Plattform eine besondere Bedeutung. Deklarieren Sie einen, und Ihr Dienst wird mit der bereits vorhandenen Funktion verbunden.

NameWas Sie erhalten
catalog.search_productsResults become product cards under the AI's replies, and can fill the widget's Home showcase. Return {"products": [{title, url, image, price_from, currency, available}]}.
orders.get_statusBestellstatus, immer hinter einer Verifizierung.
orders.get_trackingSendungsverfolgung, immer verifiziert.
orders.list_recentDie letzten Bestellungen des Besuchers, immer verifiziert.
account.lookupBeliebiger flacher Datensatz über den verifizierten Besucher, etwa Plan, Guthaben oder Verlängerungsdatum.

Vor dem Einsatz bei Besuchern testen

client.integrations.run("acme-store", "catalog.search_products",
                        {"query": "trail shoes"})
# {'status': 'ok', 'result': {...}, 'blocks': []}

Dies führt den echt call path — the signature, your credential, the checks that stop your URL pointing anywhere private, the timeout, the parsing — so what you see is exactly what the AI will get. Pass conversation_id= to run in the context of a live chat, which is the only way a verified action can succeed.

Eine gute Beschreibung schreiben

Die description is the highest-leverage string in your integration: it is what the AI reads to decide whether this action answers the question in front of it. Write it as an instruction to a capable colleague who cannot see your code.

Schwach: „Bestellung suchen.“
Stark: „Status und Lieferdatum einer eigenen Bestellung des Besuchers anhand der Bestellnummer abrufen. Immer verwenden, wenn gefragt wird, wo sich etwas befindet oder wann es eintrifft.“

Was die Plattform garantiert

  • 8 Sekunden Timeout, 128 KB Antwortlimit.
  • Calls are signed with your signing_secret — same scheme as webhooks.
  • Ihre URL wird einmal aufgelöst und an eine öffentliche IP gebunden; Weiterleitungen werden abgelehnt.
  • Alles, was Sie zurückgeben, erreicht das Modell als Daten, niemals als Anweisungen. Schlüssel, die wie Secrets aussehen, werden entfernt.
  • Eigene Integrationen erfordern einen kostenpflichtigen KI-Agent-Plan. 20 pro Workspace, jeweils 40 Aktionen.

Identität

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Alles, was Daten einer einzelnen Person betrifft, wartet, bis ihre Identität feststeht. Das geschieht auf zwei Arten: mit einem Code per E-Mail oder durch Bestätigung durch Ihre Website für einen bereits angemeldeten Benutzer.

# your backend, right after your own auth check
client.visitors.identify(
    widget_id=7,
    session=conecto_session,          # read from the visitor's browser
    email=user.email,
    name=user.name,
    verified=True,                    # the vouch
    verify_hours=24,                  # max 72, default 12
    data={"plan": user.plan, "customer_since": "2024"},
)

# when they log out
client.visitors.unverify(widget_id=7, session=conecto_session)

Solange die Bestätigung gilt, behandelt Conecto die E-Mail-Adresse als nachgewiesen: Das Widget kennt den Namen, Chats werden dem richtigen Kontakt zugeordnet, und Abläufe, die sonst einen E-Mail-Code verlangen würden, etwa Bestellabfragen, Erstattungen, Kontodaten oder verifizierte Aktionen Ihres Plugins, funktionieren ohne diesen.

Bestätigen Sie nur Sitzungen, die Ihr Backend tatsächlich authentifiziert hat. Ihre Bestätigung wird als ebenso stark wie unser eigener E-Mail-Code behandelt, weil Sie die Person tatsächlich authentifiziert haben. Sie ist zeitlich begrenzt und sitzungsgebunden, daher braucht ein neuer Browser eine neue Bestätigung. Der Browser kann sich niemals selbst als verifiziert markieren, denn dieser Aufruf ist authentifiziert und gehört auf Ihren Server.

Webhooks und Ereignisse

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Bot übernimmt all das. Verwenden Sie die Low-Level-Helfer, wenn Sie am Rand verifizieren und über eine Queue verarbeiten möchten.

from conecto import webhooks

event = webhooks.parse(request.get_data(), request.headers, secret=SECRET)

event.id                  # the delivery id — dedupe on this
event.event               # "message.created"
event.conversation_id
event.text
event.is_visitor_message  # excludes your own bot's messages
event.visitor.verified

Anhand des unverarbeiteten Bodys verifizieren. Re-serializing JSON changes the bytes and the signature stops matching — this is the most common first-integration bug there is. Use request.get_data() in Flask, request.body in Django, await request.body() in FastAPI. Passing a parsed dict raises with this exact advice.

Ereignisse

conversation.createdEin Besucher hat eine Unterhaltung begonnen.
message.createdEin Besucher hat eine Nachricht gesendet. Der Bot-Auslöser.
conversation.closedEine Unterhaltung wurde geschlossen.
conversation.assignedEinem Teammitglied zugewiesen oder die Zuweisung aufgehoben.
conversation.handoffAls „Mensch erforderlich“ markiert.
conversation.ratedEine CSAT-Bewertung ist eingegangen (1–5 plus Kommentar).
ticket.createdEin Ticket wurde aus einer beliebigen Quelle geöffnet.
ticket.updatedStatus, Priorität oder zugewiesenes Teammitglied eines Tickets wurden geändert.
contact.createdEine neue Person wurde im CRM angelegt.
visitor.identifiedEine Sitzung wurde über die API identifiziert.

Zustellungssemantik

Zustellungen erfolgen mindestens einmal und ungeordnet: if we cannot confirm you got one we send it again, and they will not always arrive in the order things happened. They also time out after 2.5 seconds — so answer 200 immediately and do the work afterwards.

  • Every payload carries an id (also in X-Conecto-Delivery). Store it and skip ids you have already handled.
  • Pass that same id as idempotency_key when you reply, and a redelivery can never make your bot speak twice.
  • X-Conecto-Timestamp ermöglicht das Ablehnen veralteter Zustellungen; das SDK prüft standardmäßig mit einer Toleranz von fünf Minuten.
signature = webhooks.sign(SECRET, raw_body)      # what we send
webhooks.verify(raw_body, header_value, SECRET)  # True / False, never raises

Integrationsaufrufe von uns bis your plugin use the identical scheme with the integration's signing_secret — one verification routine covers both directions.

Fehler und Wiederholungsversuche

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Every failure raises a subclass of ConectoError, so you can catch exactly what you expect.

from conecto import ConversationClosed, RateLimited, NotFoundError

try:
    convo.reply("Refunded — you'll see it in 3-5 days.")
except ConversationClosed:
    convo.reopen()
    convo.reply("Refunded — you'll see it in 3-5 days.")
except NotFoundError:
    log.warning("conversation vanished")
AusnahmeStatusBedeutung
AuthenticationError401Fehlende, unbekannte oder widerrufene Zugangsdaten.
InvalidRequestError400Der Nachrichtentext hat die Validierung nicht bestanden; die Meldung nennt den Grund.
NotFoundError404Objekt nicht gefunden in diesem Workspace.
ConflictError409Diese Kennung ist bereits vergeben.
ConversationClosed409Reopen the thread first. A subclass of ConflictError.
LimitReached403Ein Plan- oder Workspace-Limit.
PlanRequired403Der Plan umfasst diese Funktion nicht.
RateLimited429Over 300 requests/minute. Carries retry_after.
ServiceUnavailable503Eine Abhängigkeit ist nicht konfiguriert.
ServerError5xxUnser Fehler. Ein erneuter Versuch ist sicher.
BlockErrorWird lokal von einem Block-Builder ausgelöst, bevor eine Anfrage gesendet wird.
SignatureError401Die Verifizierung eines Webhook- oder Integrationsaufrufs ist fehlgeschlagen.
APIConnectionError · APITimeoutErrorDie Anfrage hat nie eine Antwort erhalten.

Every exception carries code, message, status and the raw response.

Wiederholungsversuche

Timeouts, rate limits (429) and server errors (5xx) are retried for you. Each retry waits a little longer than the last, with a random amount added so that a thousand clients recovering at once do not all come back in the same instant — and if we send a Retry-After, that wins.

Auch Schreibvorgänge werden wiederholt, was normalerweise gefährlich ist. Hier ist es sicher, weil jeder Schreibvorgang einen Idempotenzschlüssel enthält und ein neuer Versuch denselben verwendet. Der Server erkennt den zweiten Versuch als denselben Aufruf und wiederholt ihn nicht.

client = Conecto(max_retries=0)   # opt out entirely

Wichtige Limits

LimitWert
Anfragen pro Minute und Zugangsdaten300
Nachrichtentext4000 Zeichen
Blöcke pro Nachricht10
Schnellantwort-Schaltflächen6
Medien-Upload10 MB
Integrationen pro Workspace20
Aktionen pro Integration40
Webhooks pro Workspace10

client.meta.schema()["limits"] enthält immer die aktuellen Zahlen.

Paginierung

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

List endpoints return a Page you can use three ways.

page = client.contacts.list(query="acme")

page.items        # just this page — a list, so len() and [0] work
page.has_more     # is there another?

for contact in page:   # every page, fetched lazily as you consume
    ...

page.all()        # everything, collected into one list

Iterating is lazy: next(iter(page)) costs exactly one request no matter how many contacts exist. all() is fine for contacts and tickets; think twice on a busy workspace's conversations, where iterating and stopping early is usually what you meant.

Wird getestet

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

The client takes a transport, which is the seam for tests. Anything with get, post, patch, delete und request works — no network, no credentials, no mocking library.

class FakeTransport:
    base_url = "https://conecto.test/api/v1"

    def __init__(self):
        self.calls = []
        self.responses = {}

    def request(self, method, path, **kw):
        self.calls.append((method, path, kw.get("json_body")))
        return self.responses.get(f"{method} {path}", {})

    def get(self, p, **kw): return self.request("GET", p, **kw)
    def post(self, p, json_body=None, **kw): return self.request("POST", p, json_body=json_body, **kw)
    def patch(self, p, json_body=None, **kw): return self.request("PATCH", p, json_body=json_body, **kw)
    def delete(self, p, **kw): return self.request("DELETE", p, **kw)


def test_bot_answers_refunds():
    transport = FakeTransport()
    client = Conecto(transport=transport)
    client.conversations.reply(1, "Refunded.")
    assert transport.calls[0][1] == "/conversations/1/messages/"

Überspringen Sie bei Bots und Plugins die Signaturprüfung in Tests, statt Fixtures zu signieren:

bot = Bot(client, verify=False)
plugin = Plugin("acme", base_url="https://x.test", verify_signatures=False)

Methodenreferenz

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Gespräche

client.conversations.list(status, widget_id, session, limit, before_id)

Unterhaltungen, neueste Aktivität zuerst. Gibt eine Page zurück.

client.conversations.get(id, since_id=None)

Eine Unterhaltung mit ihren Nachrichten. since_id gibt nur neuere Nachrichten zurück.

client.conversations.messages(id, since_id=None)

Nur die Nachrichten.

client.conversations.reply(id, body, blocks, buttons, products, ask_email, ticket_form, internal, idempotency_key)

Eine Bot-Nachricht senden. Alias: send().

client.conversations.typing(id, on=True, name="Bot")

Tippanzeige ein- oder ausblenden.

client.conversations.handoff(id)

Als „Mensch erforderlich“ markieren.

client.conversations.assign(id, user_id)

None hebt die Zuweisung auf.

client.conversations.close(id) · reopen(id) · set_status(id, status)

Status ändern.

Kontakte

client.contacts.list(email, query, limit, before_id) · get(id) · find(email)

find() gibt einen Kontakt oder None zurück.

client.contacts.upsert(email, name, title, company, location, phone, notes, custom_fields)

Per E-Mail erstellen oder aktualisieren. Alias: create().

client.contacts.update(id, **fields) · delete(id)

Besucher

client.visitors.get(widget_id, session)

Identität und Verifizierungsstatus.

client.visitors.identify(widget_id, session, email, name, data, verified, verify_hours)

Ihren Benutzer anhängen; verified=True bestätigt seine Identität.

client.visitors.unverify(widget_id, session)

Bestätigung widerrufen. Beim Abmelden aufrufen.

client.visitors.message(widget_id, session, body, blocks, buttons, products)

Eine proaktive Nachricht.

Integrationen

client.integrations.list() · get(slug) · actions(slug)

actions() gibt auch den Katalog reservierter Aktionen zurück.

client.integrations.create(slug, base_url, name, description, auth_type, credential, actions)

Einen registrieren. Die Antwort enthält signing_secret.

client.integrations.update(slug, actions, replace_actions, rotate_signing_secret, **fields)

replace_actions macht Ihre Liste maßgeblich.

client.integrations.deploy(slug, base_url, actions, widget_ids, install=True)

Erstellen oder aktualisieren und anschließend installieren. Idempotent, daher geeignet für Ihr Deployment-Skript.

client.integrations.install(slug, widget_ids, actions, enabled) · uninstall(slug, widget_ids)

actions ist eine Positivliste; [] legt nichts offen.

client.integrations.run(slug, action, arguments, conversation_id)

Über den echten Aufrufpfad ausführen.

client.integrations.delete(slug) · remove_action(slug, name)

Tickets, Artikel, Widgets, Medien, Webhooks, Metadaten

client.tickets.list · get · create · update · reply · categories
client.articles.list · get · create · update · delete
client.widgets.get(id) · update(id, **fields)

Die vollständige Konfiguration befindet sich in widget.raw.

client.media.upload(file, filename, content_type)

Gibt ein Media-Objekt zurück, das Sie an einen Block-Builder übergeben können.

client.webhooks.list() · create(url, events, widget_id) · delete(id)

Unbekannte Ereignisnamen werden lokal abgelehnt.

client.members.list() · client.meta.me() · schema() · stats()

Modelle

Every returned object keeps the payload it was built from. Attribute access is the nice path; obj.raw is always there for a field this SDK version does not know about yet, so a release is never what stands between you and a new API field.

convo.status          # typed
convo.raw["status"]   # the same thing
convo["status"]       # shorthand for the raw payload
convo.to_dict()       # exactly what the API sent

Rezepte

Sie kommen nicht weiter? Fragen Sie eine KI zu diesem Abschnitt:ClaudeChatGPTGrok

Ein Ticket mithilfe Ihres Hilfe-Centers vermeiden

@bot.on_message
def deflect(ctx):
    hits = ctx.client.articles.list(query=ctx.text, published=True)
    if hits:
        ctx.reply(f"This might help: “{hits[0].title}”. Did that solve it?",
                  buttons=["Solved it", "Open a ticket"])
    else:
        ctx.reply("Let's get this to the team.", ticket_form=True)

Einen Lead qualifizieren und weiterleiten

@bot.on_message
def qualify(ctx):
    if not ctx.event.visitor or not ctx.event.visitor.email:
        ctx.reply("Happy to help — what's your work email?", ask_email=True)
        return

    ctx.client.contacts.upsert(
        ctx.event.visitor.email,
        custom_fields={"lead_source": "chat", "intent": classify(ctx.text)},
    )
    ctx.note("Qualified lead — routing to sales.")
    ctx.assign(SALES_USER_ID)
    ctx.handoff()

Eine schlechte Bewertung auffangen

@bot.on_rating
def follow_up(ctx):
    rating = ctx.event.rating or {}
    if rating.get("score", 5) <= 2:
        ctx.client.tickets.create(
            email=ctx.event.visitor.email,
            message=f"Low CSAT ({rating['score']}/5): {rating.get('comment', '')}",
            priority="high",
        )

Nächtliche Kontaktsynchronisierung

for user in your_database.active_users():
    client.contacts.upsert(
        user.email, name=user.name, company=user.company,
        custom_fields={"plan": user.plan, "mrr": str(user.mrr)},
    )

Ein uns unbekannter Shop

@plugin.action("catalog.search_products",
               description="Search the catalog by keyword.",
               parameters=[parameter("query", required=True)])
def search(req):
    hits = your_catalog.search(req.get("query"))
    return req.ok(products=[to_conecto(p) for p in hits]) if hits else req.not_found()

Das ist die vollständige Integration. Produktkarten, Home-Auswahl und die Regel, die das Modell am Einfügen roher URLs hindert, gehören automatisch zum reservierten Namen.

Sie entwickeln etwas? Sprechen Sie mit uns , wir möchten, dass Entwickler auf Conecto aufbauen, und priorisieren ihre Wünsche. Das SDK ist MIT-lizenziert und Open Source; Issues und Pull Requests sind willkommen auf GitHub.