SDK reference

Python

Package caspian. Python 3.10+. Same program surface as TypeScriptCaspian, rules, Thread — not a second API.

Install

pip install caspian-sdk
# optional sockets:
pip install "caspian-sdk[discord]"
pip install "caspian-sdk[slack-socket]"

Public imports: Caspian, Thread, Message, Action, Button, Attachment, HandlerContext, ProvisionError, Result, ToolSet.

Caspian

from caspian import Caspian

cx = Caspian()                       # self-host; no Caspian key
cx = Caspian(api_key=KEY)            # hosted: via="hosted" and run()
# api_key="", base_url="", webhook_secret="", dispatch=True
cx.appInspectable App of rules. Test without a network.
cx.channels.add(channel, *, via, …)Always pass via="hosted" or via="self-host". Self-host needs no Caspian key.
cx.on_message(options)Decorator or on_message(opts, fn).
cx.on_action(options)Button / callback handler.
cx.use(rule)Raw core Rule.
cx.tools(thread=None, *, preset=…)"messenger" or "outbound".
cx.interpret()MemoryInterpreter, no HTTP.
cx.handle(channel, body, headers=None)Self-host webhook, or handle("gateway", …) hosted push.
cx.poll(channel, *, max_iterations=None, interval=1.0)Telegram getUpdates. Same pipeline as handle.
cx.listen(channel="discord", *, max_events=None)Discord socket or Slack Socket Mode. Blocks.
cx.run(*, max_iterations=None, interval=1.0)Hosted: GET /v1/events then handle("gateway", …). Needs api_key.

channels.add kwargs that matter: bot_token, webhook_url, inbound=True, webhook_secret, signing_secret, app_secret, api_key (Linear / iMessage — that platform’s token, not Caspian’s), display_name, plus channel extras (app_token for Slack Socket Mode).

Handlers

Message handler: (thread, message, ctx) — sync or async def. Action handler: (thread, action, ctx). ctx.skipped is the burst overlap collapsed before this turn. See Overlap.

Options: channel (name or list), kind (dm / group / channel), command, overlap (queue / debounce / drop / parallel / stream), bound (default 16), ack (user-visible reply before the handler). on_action adds data.

Default overlap for both on_message and on_action is queue. Pass overlap: "drop" on buttons and voice.

Thread

Methods enqueue commands. They are not an HTTP client.

thread.post(text, *, actions=())
thread.send(text, *, actions=())          # not a reply to this turn
thread.reply(reply_to, text, *, actions=())
thread.send_blocks(blocks, *, text="", actions=())
thread.send_media(attachment, *, caption="")
thread.typing()
thread.edit(message_id, text, *, actions=())
thread.delete(message_id)
thread.react(message_id, emoji)
thread.pin / unpin(message_id)
thread.forward(to_thread_id, message_id)
thread.mark_read(message_id="")
thread.initiate(text, *, actions=())
thread.schedule(text, send_at, *, actions=())
thread.history(*, limit=20, before="")
thread.subscribe()
thread.set_state(key, value)
with thread.stream(*, min_chars=24, throttle=0.5) as out:
    out.append(chunk)

thread.post answers the inbound turn (email In-Reply-To). thread.send starts a standalone message. thread.reply quotes a specific id.

Message, Action, Button

Message: kind, thread_id, text, chat_kind, sender, message_id,
         attachments, blocks, reply_to, topic_id, metadata, raw
Action:  kind, thread_id, data, sender, message_id, interaction_id, metadata, raw
Button:  label, data="", url="", style="default"|"primary"|"danger"
Attachment: type, url, file_id, filename, mime_type, size_bytes, caption

Errors

ProvisionError on paperwork. handle / listen / poll / run return a list of Result (per-event failure). Hosted HTTP classifies 401/403 AuthRequired, 402 InsufficientCredit, 429 RateLimited.