Skip to main content

woise (Python)

Voice and chat agents for your systems: your CRM, app or phone system places calls, runs campaigns, chats with agents, talks to them live, and gets the results back as webhooks.

pip install woise            # live audio: pip install "woise[live]"

Python 3.9+. One dependency (httpx). Woise is sync, AsyncWoise is the same API with await. The API key picks your region: sk-woise-us-... talks to https://us.woise.ai, sk-woise-in-... to https://in.woise.ai. Older keys without a region need Woise(key, region="us"). Local dev or a partner domain: Woise(key, base_url="http://localhost:8000").

Build an agent and place a call

import os
from woise import Woise, Agent

woise = Woise(os.environ["WOISE_API_KEY"])

agent = Agent("Smile Dental", prompt="You are Priya, the receptionist at Smile Dental.")
agent.start("greet", say="Hi, this is Priya from Smile Dental.", prompt="Confirm tomorrow's visit.",
            next={"done": "they confirmed or cancelled"})
agent.end("done", say="Thanks, bye.")
agent.after_call(collect={"outcome": "confirmed, cancelled or rescheduled"})
saved = woise.agents.save(agent, publish=True)          # save again later = new draft

call = woise.calls.create(agent=saved, to="+14155550123", variables={"first_name": "Ravi"})
print(call.status, call.id)                              # queued, enr_...

next maps the step to go to, to the condition in plain words. agent.anytime("human", when="asks for a person", transfer="+14155550100") adds a step that can fire from anywhere. agent.node(id, type, data) adds any other node.

Run a campaign from a CSV

# leads.csv: phone_number,first_name,plan   (every other column becomes a {{variable}})
campaign = woise.campaigns.create(agent=saved, contacts="leads.csv", number="+14155550100")
campaign.launch()
print(campaign.progress())
campaign.pause(); campaign.resume()
campaign.add_contacts([{"phone_number": "+14155550124", "first_name": "Sita"}])

Get results back: call.ended in Flask, with the signature check

hook = woise.webhooks.create(url="https://crm.example.com/woise", events=["call.ended"])
print(hook.secret)                                       # shown ONCE: store it as WOISE_WEBHOOK_SECRET
from flask import Flask, abort, request
from woise import verify_signature

app = Flask(__name__)

@app.post("/woise")
def woise_event():
    if not verify_signature(request.get_data(), request.headers.get("X-Woise-Signature"),
                            os.environ["WOISE_WEBHOOK_SECRET"]):
        abort(401)
    event = request.get_json()
    print(event["event"], event["call_id"], event["call_disposition"], event["collected"])
    return "", 204

Events: call.started, call.ended, chat.ended, campaign.finished. Failed deliveries are retried with backoff; woise.webhooks.deliveries(hook) shows the log.

Chat

chat = woise.chats.start(saved, variables={"first_name": "Ravi"})
print(chat.greeting)
print(chat.send("Are you open on Sunday?").text)
chat.end()

Runs: transcripts, outcomes, recordings

for run in woise.runs.list(agent=saved, limit=20):
    print(run.id, run.outcome, run.collected)
    print(run.transcript)                                # "agent: ...\ncaller: ..."
print(woise.runs.recording_url(run))                     # short-lived link

Live voice: Twilio Media Streams -> Woise in ~20 lines

Point a Twilio <Connect><Stream url="wss://you.example.com/twilio"/></Connect> at this relay. It forwards Twilio's messages to Woise verbatim and Woise answers in the same format (media, mark, clear), so barge-in works unchanged.

import asyncio, json, os
import websockets
from woise import AsyncWoise

woise = AsyncWoise(os.environ["WOISE_API_KEY"])
AGENT = "wf_..."                                          # your agent id

async def relay(twilio):
    async with woise.live.voice(AGENT, format="twilio") as call:
        async def up():                                   # Twilio -> Woise
            async for message in twilio:
                await call.send_raw(message)
        async def down():                                 # Woise -> Twilio
            async for event in call:
                await twilio.send(json.dumps(event.data))
        await asyncio.gather(up(), down())

async def main():
    async with websockets.serve(relay, "0.0.0.0", 8080):
        await asyncio.Future()

asyncio.run(main())

Raw PCM16 instead (your own media server, 8/16/24 kHz mono):

with woise.live.voice(AGENT, sample_rate=16000) as call:
    call.send_audio(pcm16_bytes)
    for event in call:
        if event.type == "audio": speaker.write(event.audio)
        elif event.type == "interrupt": speaker.flush()     # the caller barged in
        elif event.type == "transcript": print(event.data["role"], event.text)

woise.live.chat(agent) is the same socket for text: send_text, then stream agent_text events.

More

  • Numbers: woise.numbers.connections(), .carrier_numbers(conn), .import_byo(conn, ["+1..."]), .list(connection_id=conn), .attach(number, agent, connection_id=conn), .detach(number, connection_id=conn) (managed numbers: .list(), .attach(number, agent))
  • woise.tools.create_http("lookup_patient", url="https://crm.example.com/patients", parameters={"phone": "the caller's number"})
  • woise.knowledge.add("faq.pdf")
  • Errors are WoiseError with .status, .code, .message, .request_id. GET/PUT/DELETE are retried on 429/5xx; calls.create never is (pass idempotency_key= to retry safely).

MIT. Copyright (c) 2026 Stele Labs Private Limited.

Metadata

Release files for woise 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for woise 1.0.1
File Size Uploaded Provenance Actions
woise-1.0.1.tar.gz 29.1 kB Without provenance Details for woise-1.0.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for woise 1.0.1
File Interpreter ABI Platform Actions
woise-1.0.1-py3-none-any.whl Python 3 none any Details for woise-1.0.1-py3-none-any.whl

Total release size: 51.5 kB

Release files / woise-1.0.1.tar.gz

Download URL woise-1.0.1.tar.gz
Size 29.1 kB
Tags Source
SHA-256 checksum
How to use checksums
5a7a665fc712549c87dcf86ad563d72372d14a222f9501749c75c532622fc2c9
BLAKE2b-256 checksum
How to use checksums
454fe727fb818e361abd118677b7b89abedc7b18d727660071f85e74cd4aba72
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Release files / woise-1.0.1-py3-none-any.whl

Download URL woise-1.0.1-py3-none-any.whl
Size 22.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1c2486d5efc0e6a6caceba66d1e8c6967b5dad24541a52ccd03bc4196e37b249
BLAKE2b-256 checksum
How to use checksums
edbea564fecd061daaf881e117eee905e42e6df8f9a37f67ec72b380ba4e80d5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Security for woise 1.0.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page