Utviklere / Python

Den Python-SDK

Les og svar på chatter, send bilder og video, bygg boter og la KI-agenten slå opp informasjon i deres egne systemer med Python. Alt REST API-et gjør, med de vanskelige delene ferdig håndtert: kontroll av at en forespørsel virkelig kom fra oss, unngåelse av doble svar på samme hendelse, trygge nye forsøk og oppdagelse av ugyldige meldinger før de forlater prosessen.

Ny på Conecto? Start her , fem minutter med begreper, så blir resten av siden selvforklarende.

pip install conectov0.1.0Python 3.9+MIT

One dependency (requests) · Type hints throughout · På PyPI

Start her

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

Hvis dere aldri har brukt Conecto før, inneholder denne delen hele tankemodellen. Fem minutter her gjør resten av siden selvforklarende.

Hva Conecto er

En chatwidget på nettstedet deres. Besøkende skriver i den; teamet deres, en KI-agent eller deres egen kode svarer. Alt nedenfor er en måte å føre koden deres inn i den løkken.

De fem substantivene

Nesten alle metodene i denne SDK-en tar imot eller returnerer ett av disse objektene.

Arbeidsområde

Kontoen deres. Autentiseringsopplysningen tilhører nøyaktig én konto og kan aldri se en annen.

Widget

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

Besøkende

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

Samtale

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

Melding

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

De tre tingene dere kan bygge

1. Et skript

Kjøres etter deres tidsplan. Synkroniserer kontakter, åpner saker og sender kampanjemeldinger. Ingenting må driftes; skriptet kaller bare API-et.

2. En bot

En av nettserverne deres som vi varsler når en besøkende skriver. Dere bestemmer svaret. Dere styre samtalen.

3. En plugin

En av nettserverne deres som vi kaller når AI trenger en opplysning bare dere har. KI-en styrer samtalen; dere leverer oppslagene.

Bot eller plugin? Hvis dere vil skrive ordene den besøkende leser, bygger dere en bot. Hvis dere heller vil la KI-en snakke og bare gi den tilgang til dataene deres, for eksempel bestillinger, abonnementer og lagernivåer, bygger dere en plugin. De fleste team ender opp med å velge en plugin.

Ord som brukes på denne siden

Sjargong, definert én gang. Ingenting her er spesifikt for Conecto bortsett fra de to siste begrepene.

WebhookNår noe skjer, sender vi en HTTP-forespørsel til en av URL-ene deres i stedet for at dere stadig skal spørre oss. Dere trenger en server som er tilgjengelig fra Internett.
SignaturEn hash som vi legger ved hver forespørsel, beregnet med en hemmelighet bare dere og vi kjenner. Kontrollen beviser at forespørselen faktisk kom fra oss og ikke ble endret. SDK-en gjør dette for dere.
Minst én gangHvis vi ikke kan avgjøre om dere mottok noe, sender vi det på nytt. Dere kan derfor av og til få samme hendelse to ganger, og koden skal oppdage det i stedet for å handle to ganger.
IdempotentÅ gjøre det to ganger gir samme resultat som å gjøre det én gang. Hvis dere sender en melding to ganger med samme nøkkel, returnerer det andre kallet den første meldingen i stedet for å publisere et duplikat.
Markørbasert pagineringLange lister kommer i sider. I stedet for «side 3» sender dere ID-en dere fikk sist. SDK-en skjuler dette; dere trenger bare å iterere.
BlokkEt rich content-element i en melding, for eksempel et bilde, en video, et kort eller en knapperad. Se Rikt innhold.
Verifisert besøkendeNoen vi har e-postadressen til verifisert , ved å sende en kode på e-post eller fordi nettstedet deres fortalte oss at personen er logget inn. Alt som berører personens private data, venter på denne bekreftelsen.

Hvilken seksjon trenger jeg?

Jeg vil…Gå til
Les eller svar på chatter fra et skriptSamtaler
Send et bilde, en GIF, en video eller et kortRikt innhold
Svare besøkende automatisk med min egen logikkBygg en bot
La KI-en slå opp informasjon i systemene mineBygg en plugin
Koble til en butikk KI-en kan søke iBygg en plugin
Hopp over e-postkoden for brukere som allerede er logget innIdentitet
Hold CRM-systemet mitt synkronisertKontakter og besøkende
Vite hva jeg skal fange opp når noe går galtFeil og nye forsøk

Installer og autentiser

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok
pip install conecto

Opprett en autentiseringsopplysning i Innstillinger → Utviklere. Dere får en klient-ID (ck_…) and a hemmelighet (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.

Hurtigstart

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

Tre ting dere kan gjøre i løpet av de første fem minuttene.

Les hva som skjer

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

Svare noen

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

Send noe annet enn tekst

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"),
    ]),
])

Slik er hele biblioteket bygget opp: en klient med ressurser, objekter som kan handle på seg selv, og builders for alt som er strukturert.

Klienten

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

Lag én og behold den. Den gjenbruker tilkoblinger og kan trygt deles mellom tråder.

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
)

Miljø

VariabelAngir
CONECTO_CLIENT_IDKlient-ID-en.
CONECTO_SECRETHemmeligheten.
CONECTO_BASE_URLBasis-URL for API-et. Trengs sjelden.

Ressurser

AttributtDekker
client.conversationsList opp, les, svar, vis skriving, overfør, tildel og lukk.
client.contactsCRM-systemet: opprett eller oppdater etter e-postadresse, søk, oppdater og slett.
client.visitorsØkter, identitetsbekreftelse og proaktive meldinger.
client.ticketsOpprett, oppdater, svar og administrer kategorier.
client.articlesSøk etter og publiser innhold i hjelpesenteret.
client.integrationsRegistrer, installer, kjør og deploy plugins.
client.widgetsLes og oppdater widgetkonfigurasjonen.
client.mediaLast opp filer som skal brukes i blokker.
client.webhooksAbonner endepunkter på hendelser.
client.membersTeammedlemmer, for tildeling.
client.metame(), schema(), stats().

Versjonsavvik

Hvert svar inneholder serverens API-versjon. Loggfør den ved oppstart. Hvis versjonen endres og et felt dere bruker forsvinner, er dette det første dere vil vite.

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() returnerer hele API-et som JSON, inkludert endepunkter, hendelser, blokktyper, reserverte handlinger, feilkoder og alle grenser. Det leveres av den samme koden som håndhever reglene, og kan derfor ikke avvike fra virkeligheten.

Samtaler

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

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

Liste og lesing

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)

Svare

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

Et trykk på en hurtigsvarsknapp kommer tilbake som en vanlig melding fra den besøkende med den teksten, så handleren trenger ikke noe spesialtilfelle.

ArgumentGjør
bodyTeksten, opptil 4000 tegn.
blocksRich content, se Rikt innhold.
buttonsOpptil 6 hurtigsvar.
productsOpptil 4 produktkort, samme antall som den innebygde KI-en legger ved.
ask_emailViser det innebygde skjemaet for innsamling av e-postadresser.
ticket_formLegger ved det innebygde skjemaet for å åpne en sak.
internalEt notat bare for agenter. Den besøkende ser det aldri.
idempotency_keySend med en leverings-ID for webhooken, slik at et nytt forsøk ikke kan publisere dobbelt.

Styring av tråden

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 Feil.

Rikt innhold

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

Hver melding kan inneholde opptil ti blokker: bilder og GIF-er, video, lyd, filer, innebygd innhold fra tredjeparter, kortkaruseller, etikett/verdi-lister, knapper og skillelinjer. De vises i widgeten og i innboksen til agenten.

Blokker er vanlige dictionaries under overføringen, så dere kunne skrevet dem for hånd. Builders finnes fordi en feilstavet nøkkel kan føre til at en kunde sier at bildet aldri dukket opp. De bruker de samme reglene som serveren mens traceback fortsatt peker på koden deres.

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")]),
    ]),
])

Alle blokktyper

BuilderNotater
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 og Spotify. Send den vanlige delingslenken.
blocks.audio(url, title).mp3, .wav, .m4a
blocks.file(url, filename, size)En nedlastingsrad.
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 og link_button.
blocks.text(body)Et ekstra avsnitt under andre blokker.
blocks.divider()En vannrett linje.

To typer knapper

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

Gi en svarknapp en uttrykkelig verdi når etiketten skal være lett å lese, men teksten boten matcher, ikke skal endres hver gang noen skriver om teksten.

Send en GIF

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)

Regler som builders håndhever

  • Every URL must be https. The widget runs on an https page, so anything else is blocked by the browser anyway.
  • Ti blokker per melding, ti kort, tolv listerader og seks knapper.
  • Long strings are truncated, not rejected. Structural mistakes raise BlockError.
  • A YouTube link in a video block raises, and tells you to use embed.

Valideringen skjer lokalt. 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.

Last opp filer

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

Ingen offentlig URL for GIF-en, kvitteringen eller databladet? Last opp filen, opptil 10 MB, og bruk resultatet.

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 eller blocks.file.

Use media.url, not media.absolute_url. url er en bane som widgeten løser relativt til sitt eget opphav. Dermed fungerer samme melding i utvikling, i en forhåndsvisning og i produksjon, også den dagen domenet deres endres.

Kontakter og besøkende

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

Kontakter

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 er slått sammen, ikke erstattet. To jobber kan forvalte hver sine nøkler uten å overskrive hverandre. Opptil 30 nøkler per kall.

Besøkende

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 meldinger

Send til en økt uten å vente på et spørsmål, for eksempel fraktoppdateringer, påminnelser om prøveperioder eller gjenoppretting av handlekurver.

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"),
    ])],
)

Den gjenbruker den aktive samtalen i økten eller åpner en ny. En åpen widget viser den ved neste polling; en lukket widget viser den neste gang den åpnes, merket som ulest.

Saker og artikler

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

Saker

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

Et offentlig svar åpner en løst sak på nytt, fordi et svar på noe som er merket som ferdig, nesten alltid betyr en ny runde.

Hjelpesenterartikler

Nyttig i begge retninger: søk derfra med en bot før dere åpner en sak, og send inn dokumentasjon fra verktøyet der dere skriver den.

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 renses på serversiden mot en tillatelsesliste med sikre tagger, gjennom samme prosess som redigeringsverktøyet i dashboardet bruker. Skript og stiler fjernes før noe lagres.

Bygg en bot

Står dere fast? Spør en KI om denne delen: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()

Tilgjengeliggjøring

# 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()

Abonner deretter endepunktet:

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

Handlers

DekoratørUtløses av
@bot.on_messageEn melding fra en besøkende. Meldinger fra boter og agenter når aldri denne, noe som hindrer en bot i å svare seg selv.
@bot.on_conversation_startedconversation.created , hilse eller angi tilstand.
@bot.on_ratingconversation.rated. ctx.event.rating has score og comment.
@bot.on_ticket · @bot.on_contactticket.created · contact.created.
@bot.on("event.name")Any event by name. @bot.on("*") catches everything.
@bot.on_errorEn handler utløste et unntak. Uten en egen handler blir feil loggført og ignorert.

Hva en handler får

På ctxEr
ctx.textTeksten i meldingen fra den besøkende.
ctx.verified · ctx.verified_emailOm identiteten er verifisert, og hvilken adresse som er verifisert.
ctx.conversation_id · ctx.widget_id · ctx.sessionID-er dere kan handle på.
ctx.reply(...)Answer. Takes everything conversations.reply does.
ctx.note(...)Et notat bare for agenter.
ctx.typing() · ctx.handoff() · ctx.assign() · ctx.close()Styr tråden.
ctx.history()Hent hele meldingshistorikken. Én forespørsel.
ctx.client · ctx.eventLow-level-klienten og den tolkede hendelsen, for alt annet.

Kjører dere mer enn én worker? 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())

Bygg en plugin

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

En bot besvarer webhooks. En plugin går i motsatt retning: dere angir hva systemene kan gjøre, og KI-agenten bestemmer når funksjonene skal brukes mens den snakker med noen. Dere skriver aldri samtalelogikken, bare oppslaget.

Slik blir en butikk, et faktureringssystem eller et CRM-system vi aldri har hørt om, en fullverdig integrasjon.

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()

Tilgjengeliggjør og registrer

# 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() oppretter eller oppdaterer integrasjonen slik at den samsvarer med koden, sletter handlingene dere har fjernet, og installerer den i widgetene. Ingenting eksponeres for KI-en før integrasjonen er installert.

De fem svarene

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.

Risikonivåer

RisikoBetyr
public_readKatalog, tilgjengelighet og dokumentasjon. Ingen identitet kreves.
verified_readDataene til én person. Avvises til e-postadressen er verifisert.
public_writeEn endring som ikke krever identitet, for eksempel påmelding til nyhetsbrev eller registrering av leads.
writeEn endring i kontoen til én person. Verifisert, revidert og aldri forsøkt på nytt i det skjulte.

req.verified_email er den eneste identiteten dere kan stole på. An email in req.arguments is whatever the model parsed out of a chat. For verified_read og 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.

Reserverte handlinger

Noen navn har en egen betydning på plattformen. Deklarer ett av dem, så kobles tjenesten deres til funksjonen som allerede finnes for navnet.

NavnHva dere får
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_statusBestillingsstatus, alltid etter verifisering.
orders.get_trackingSporing av forsendelser, alltid verifisert.
orders.list_recentDe siste bestillingene til den besøkende, alltid verifisert.
account.lookupEn vilkårlig enkel post om den verifiserte besøkende, for eksempel abonnement, kreditter eller fornyelsesdato.

Test det før en besøkende gjør det

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

Dette kjører den faktiske 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.

Skriv en god beskrivelse

Den 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.

Svak: «Slå opp bestilling.»
Sterk: «Slå opp status og leveringsdato for en av den besøkendes egne bestillinger ved hjelp av ordrenummeret. Bruk dette når personen spør hvor noe er, eller når det kommer.»

Hva plattformen garanterer

  • 8 sekunder timeout, 128 kB svargrense.
  • Calls are signed with your signing_secret — same scheme as webhooks.
  • URL-en deres løses én gang og bindes til en offentlig IP-adresse; omdirigeringer avvises.
  • Alt dere returnerer, når modellen som data, aldri som instruksjoner, og nøkler som ser ut som hemmeligheter, blir fjernet.
  • Egendefinerte integrasjoner krever et betalt abonnement med KI-agent. Opptil 20 per arbeidsområde og 40 handlinger i hver integrasjon.

Identitet

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

Alt som berører dataene til én person, venter til vi vet hvem personen er. Det skjer på én av to måter: med en kode på e-post eller gjennom nettstedets bekreftelse for en bruker som allerede er logget inn.

# 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)

Så lenge bekreftelsen gjelder, behandler Conecto e-postadressen som verifisert: widgeten kjenner navnet, chatter knyttes til riktig kontakt, og flyter som ellers ville krevd en kode på e-post, for eksempel oppslag av bestillinger, refusjoner, kontodata eller verifiserte pluginhandlinger, fortsetter uten kode.

Gå bare god for økter som backend faktisk har autentisert. Bekreftelsen deres behandles som like sterk som vår egen kode på e-post, fordi dere faktisk har autentisert personen. Den er tidsbegrenset og knyttet til økten, så en ny nettleser trenger en ny bekreftelse. Nettleseren kan aldri merke seg selv som verifisert; kallet er autentisert og hører hjemme på serveren deres.

Webhooks og hendelser

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

Bot gjør alt dette for dere. Bruk low-level-hjelperne når dere vil verifisere i ytterkanten og behandle via en kø.

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

Verifiser mot det ubehandlede innholdet. 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.

Hendelser

conversation.createdEn besøkende startet en samtale.
message.createdEn besøkende sendte en melding. Utløseren for boten.
conversation.closedEn samtale ble lukket.
conversation.assignedTildelt til eller fjernet fra et teammedlem.
conversation.handoffMerket som at hjelp fra et menneske er nødvendig.
conversation.ratedEn CSAT-vurdering kom inn (1 til 5 samt en kommentar).
ticket.createdEn sak ble åpnet, uansett kilde.
ticket.updatedStatus, prioritet eller tildelt person for en sak ble endret.
contact.createdEn ny person ble lagt til i CRM-systemet.
visitor.identifiedEn økt ble identifisert via API-et.

Leveringssemantikk

Leveringer skjer minst én gang og uten garantert rekkefølge: 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 lar dere avvise gamle leveringer; SDK-en kontrollerer dem som standard med fem minutters toleranse.
signature = webhooks.sign(SECRET, raw_body)      # what we send
webhooks.verify(raw_body, header_value, SECRET)  # True / False, never raises

Integrasjonskall vi utfører til your plugin use the identical scheme with the integration's signing_secret — one verification routine covers both directions.

Feil og nye forsøk

Står dere fast? Spør en KI om denne delen: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")
UnntakStatusBetyr
AuthenticationError401Manglende, ukjent eller tilbakekalt autentiseringsopplysning.
InvalidRequestError400Innholdet besto ikke valideringen; meldingen forklarer hvorfor.
NotFoundError404Objektet finnes ikke i dette arbeidsområdet.
ConflictError409Den identifikatoren er allerede i bruk.
ConversationClosed409Reopen the thread first. A subclass of ConflictError.
LimitReached403En grense for abonnementet eller arbeidsområdet.
PlanRequired403Abonnementet omfatter ikke denne funksjonen.
RateLimited429Over 300 requests/minute. Carries retry_after.
ServiceUnavailable503En avhengighet er ikke konfigurert.
ServerError5xxVår feil. Trygt å prøve på nytt.
BlockErrorUtløst lokalt av en blokk-builder, før noen forespørsel.
SignatureError401Verifiseringen av et webhook- eller integrasjonskall mislyktes.
APIConnectionError · APITimeoutErrorForespørselen fikk aldri noe svar.

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

Nye forsøk

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.

Skriveoperasjoner forsøkes også på nytt, noe som vanligvis er farlig. Her er det trygt fordi hver skriveoperasjon har en idempotensnøkkel, og et nytt forsøk gjenbruker den samme nøkkelen. Serveren kjenner igjen det andre forsøket som det samme kallet og gjentar det ikke.

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

Grenser det er verdt å kjenne til

GrenseVerdi
Forespørsler per minutt og autentiseringsopplysning300
Meldingsinnhold4000 tegn
Blokker per melding10
Hurtigsvarsknapper6
Last opp media10 MB
Integrasjoner per arbeidsområde20
Handlinger per integrasjon40
Webhooks per arbeidsområde10

client.meta.schema()["limits"] inneholder alltid de gjeldende tallene.

Paginering

Står dere fast? Spør en KI om denne delen: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.

Tester

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

The client takes a transport, which is the seam for tests. Anything with get, post, patch, delete og 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/"

For boter og plugins bør dere hoppe over signaturverifisering i tester i stedet for å signere fixtures:

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

Metodereferanse

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

Samtaler

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

Samtaler, nyeste aktivitet først. Returnerer en Page.

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

En samtale med meldingene sine. since_id returnerer bare det som er nyere.

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

Bare meldingene.

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

Send en botmelding. Finnes også som send().

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

Vis eller fjern skriveindikatoren.

client.conversations.handoff(id)

Merk som at hjelp fra et menneske er nødvendig.

client.conversations.assign(id, user_id)

None fjerner tildelingen.

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

Endre status.

Kontakter

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

find() returnerer én kontakt eller None.

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

Opprett eller oppdater etter e-postadresse. Finnes også som create().

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

Besøkende

client.visitors.get(widget_id, session)

Identitet og verifiseringsstatus.

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

Koble til brukeren deres; verified=True går god for identiteten.

client.visitors.unverify(widget_id, session)

Trekk tilbake bekreftelsen. Kall ved utlogging.

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

En proaktiv melding.

Integrasjoner

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

actions() returnerer også katalogen over reserverte handlinger.

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

Registrer én. Svaret inneholder signing_secret.

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

replace_actions gjør listen deres autoritativ.

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

Opprett eller oppdater, og installer deretter. Idempotent, så legg det i deployskriptet.

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

actions er en tillatelsesliste; [] eksponerer ingenting.

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

Kall gjennom den faktiske kallbanen.

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

Saker, artikler, widgeter, medier, webhooks og metadata

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

Den fullstendige konfigurasjonen ligger i widget.raw.

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

Returnerer et Media-objekt som kan sendes til en blokk-builder.

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

Ukjente hendelsesnavn avvises lokalt.

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

Modeller

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

Oppskrifter

Står dere fast? Spør en KI om denne delen:ClaudeChatGPTGrok

Unngå en sak ved hjelp av hjelpesenteret

@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)

Kvalifiser et lead og rut det

@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()

Redd en dårlig vurdering

@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",
        )

Nattlig kontaktsynkronisering

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)},
    )

En butikk vi aldri har hørt om

@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()

Dette er hele integrasjonen. Produktkort, Home-visningen og regelen som hindrer modellen i å lime inn rå URL-er, følger med det reserverte navnet.

Bygger dere noe? Snakk med oss , vi vil at folk skal bygge med Conecto og prioriterer det utviklere ber om. SDK-en har MIT-lisens og er open source; issues og pull requests er velkomne på GitHub.