bulletins
Python SDK for the Bulletins API. Async client, SSE streaming, and a declarative bot framework.
Install
uv add bulletins-sdk
Quick start
Client
import asyncio
from bulletins import BulletinsClient
async def main():
client = BulletinsClient("bk_...", base_url="https://bulletins.no")
async with client:
await client.resolve() # auto-resolves party + integration from key
threads = await client.list_threads()
for t in threads:
print(f"{t.name} ({t.status})")
await client.send_message(threads[0].id, "Hello from Python")
asyncio.run(main())
SSE streaming
async with client.stream_party() as stream:
async for event in stream:
print(f"[{event['type']}] {event.get('senderName')}: {event['content']}")
Bot
Declarative bot with auto-registration of event types and actions:
import asyncio
from bulletins import Bot, EventTypeSpec, ActionSpec
bot = Bot(
"Ping Bot",
event_types=[
EventTypeSpec("bot:pong",
content_schema={"message": {"type": "string"}},
display_hint={"label": "Pong"}),
],
actions=[
ActionSpec("Approve", color="success"),
ActionSpec("Reject", color="destructive"),
],
)
@bot.on("message")
async def handle(event, client):
body = (event.content or {}).get("body", "")
if body.startswith("!ping"):
await client.send_message(event.thread_id, "pong")
@bot.on("action_invoked")
async def on_action(event, client):
key = (event.content or {}).get("key", "")
match key:
case "Approve": ...
case "Reject": ...
asyncio.run(bot.start(api_key="bk_...", base_url="https://bulletins.no"))
Create the integration and API key in the Bulletins settings UI. The bot auto-resolves its party and integration from the API key, registers event types and actions on startup, and dispatches incoming events to handlers.
Action IDs are available via bot.action_ids after start — pass them to create_event to attach inline controls:
await client.create_event(thread_id, "bot:pong", {"message": "pong"},
actions=[bot.action_ids["Approve"], bot.action_ids["Reject"]])
API
BulletinsClient
| Method | Description |
|---|---|
resolve() |
Auto-resolve party + integration from API key |
list_threads() |
List threads for the active party |
get_thread(id) |
Thread detail with events and participants |
create_thread(name, to_party_id, body) |
Create a thread with initial message |
list_events(thread_id, before, limit) |
Paginated event history |
create_event(thread_id, type, content, actions) |
Create any event type, optionally with inline action IDs |
send_message(thread_id, body) |
Send a text message |
stream_party() |
SSE stream for all party events |
stream_thread(thread_id) |
SSE stream for a single thread |
list_integrations() |
List integrations |
get_integration_detail(id) |
Integration with all child resources |
create_event_type(integration_id, ...) |
Register a custom event type |
create_action(integration_id, ...) |
Create an action |
create_webhook(integration_id, ...) |
Create a webhook |
create_api_key(integration_id, ...) |
Create an API key |
get_agent_token(thread_id) |
LiveKit token for joining calls |
Bot
| Method | Description |
|---|---|
on(event_type) |
Decorator to register an event handler |
start(api_key, base_url) |
Resolve identity, register event types + actions, dispatch loop |
stop() |
Stop the dispatch loop |
action_ids |
dict[str, str] — action name to UUID, populated after start |
ActionSpec
| Field | Type | Description |
|---|---|---|
name |
str |
Action button label |
method |
str |
"event" (default) or "POST" |
color |
str | None |
Semantic token: "destructive", "success", or "warning" |
requested_schema |
dict | None |
JSON Schema for form/slider input |
url_template |
str | None |
URL template for POST/link actions |
EventTypeSpec
| Field | Type | Description |
|---|---|---|
name |
str |
Namespaced type, e.g. "bot:now_playing" |
content_schema |
dict |
JSON Schema for event content validation |
display_hint |
dict | None |
{"label": "Human-readable name"} |
Requirements
Python 3.12+, httpx.
Metadata
Release files for bulletins-sdk 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bulletins_sdk-0.3.0.tar.gz | 7.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bulletins_sdk-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 18.5 kB
Release files / bulletins_sdk-0.3.0.tar.gz
| Download URL | bulletins_sdk-0.3.0.tar.gz |
|---|---|
| Size | 7.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f46fb25c11f585d11e9423a298d857e73f83b0f66a7ec6076d1c407d7621b1b8
|
|
BLAKE2b-256 checksum How to use checksums |
58f7f52419fd552ac21f8954cd2025ee96a30ca26e9f9c959e2ae7b47cf19959
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / bulletins_sdk-0.3.0-py3-none-any.whl
| Download URL | bulletins_sdk-0.3.0-py3-none-any.whl |
|---|---|
| Size | 10.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f1f8257d59ab697caccde12f4c0f316d42164a2eb9e92509724df1c9a7b3bf5f
|
|
BLAKE2b-256 checksum How to use checksums |
d2254584137081e175b5b8dd3d88ae30eaeeabb1c0ff5ae8eace4ed72d25a383
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|