Skip to content

Python SDK

The rymi Python package wraps the Rymi REST API for server-side use. Use it from backend services, workers, and data pipelines that manage agents, calls, numbers, and webhook verification.

INFO

rymi is published on PyPI and is the supported server-side SDK for Python teams. For in-browser / WebRTC voice, use the Browser or React SDKs.

Installation

bash
pip install rymi

Latest release: rymi on PyPI.

Initialization

python
from rymi import Rymi

rymi = Rymi(api_key="rymi_your_secret_key")

# Or use the RYMI_API_KEY environment variable:
rymi = Rymi()

Agents

python
agent = rymi.agents.create(
    name="Customer Support",
    system_prompt="You are a helpful customer support assistant for Acme Corp.",
    voice="Aoede",
    language="en-US",
)

agent_id = agent["id"]
agents = rymi.agents.list(limit=20)
options = rymi.agents.llm_options()

create and update accept **kwargs that pass straight through to the REST body, so any documented agent field works, including multi-language and model-stack configuration:

python
bilingual = rymi.agents.create(
    name="Ganga Sahayak",
    language="hi-IN",
    supported_languages=["hi-IN", "en-US"],
    llm_provider="gemini",
    llm_model="gemini-2.5-flash",
    tts_provider="sarvam",
    llm_fallback_provider="openai",
    llm_fallback_model="gpt-4o-mini",
)

Preview the model stack

python
preview = rymi.agents.preview_stack(
    supported_languages=["hi-IN", "en-US"],
)
print(preview["blockers"], preview["warnings"])  # empty == good to save

Knowledge sources, history & evals

python
# Knowledge (RAG) — add from text or a URL
rymi.agents.add_knowledge_source(agent_id, kind="text", title="Refund policy",
                                 text="Refunds are processed within 5 business days...")
sources = rymi.agents.list_knowledge_sources(agent_id)
rymi.agents.delete_knowledge_source(agent_id, sources["sources"][0]["id"])

# Configuration history — auditable, reversible
changes = rymi.agents.list_changes(agent_id)
rymi.agents.undo_change(agent_id, changes["changes"][0]["change_id"])

# Evaluations
run = rymi.agents.run_evals(agent_id, mode="synthetic")
runs = rymi.agents.list_eval_runs(agent_id)
detail = rymi.agents.get_eval_run(agent_id, run["id"])
python
# Clone an agent
clone = rymi.agents.clone(agent_id)

# List calls for an agent
calls = rymi.agents.list_calls(agent_id, limit=20)

# Validate before publishing
report = rymi.agents.validate_publish(agent_id=agent_id)

# Apply a change-set (does not persist — follow up with update())
result = rymi.agents.apply_changes(
    current_config={"name": "Support", "voice": "Aoede"},
    changes=[{"key": "voice", "value": "Charon"}],
    mode="edit"
)
rymi.agents.update(agent_id, **result["config"])

# Enrich company from website
info = rymi.agents.enrich_company("Acme Corp", "https://acme.example.com")
if info["enriched"]:
    print(info["companyDescription"])

Calls

python
call = rymi.calls.create(
    agent_id=agent_id,
    participants=[
        {
            "transport": "pstn",
            "identity": "+15551234567",
            "from_number": "+15559876543"
        }
    ],
    metadata={"customer_name": "Alice"},
    variables={"customer_segment": "renewal"}
)

details = rymi.calls.retrieve(call["id"])
transcript = rymi.calls.transcript(call["id"])

Control a live call

python
# Add a participant to a live call (warm transfer / conference)
rymi.calls.add_participants(call["id"], [
    {"transport": "pstn", "identity": "+15557654321", "from_number": "+15559876543"}
])

# End the call
rymi.calls.end(call["id"])

Batch Calls

python
batch = rymi.calls.batch(
    agent_id=agent_id,
    to=["+15551234567", "+15557654321"],
    from_number="+15559876543",
    variables={"batch_label": "renewal-q2"}
)

print(batch["batch_id"], batch["queued"])

Do-Not-Call (DNC)

Numbers are normalized to E.164 server-side, so any input format is accepted. Outbound calls to blocked numbers are refused.

python
rymi.dnc.add(phone_number="+15551234567", reason="customer opt-out")

# Add up to 1000 at once — invalid numbers are skipped and returned
result = rymi.dnc.add_batch(phone_numbers=["+15551234567", "+15557654321"])
print(result["count"], result["invalid_count"], result["invalid"])

# Check up to 500 numbers without adding them (read-only)
check = rymi.dnc.check(phone_numbers=["+15551234567"])
print(check["blocked_count"])

entries = rymi.dnc.list(limit=50)
rymi.dnc.remove("+15551234567")  # re-enables outbound to it

See the Compliance API for the underlying REST endpoints.

Numbers

python
rymi.numbers.register("+15559876543")

numbers = rymi.numbers.list()
rymi.numbers.attach(numbers["numbers"][0]["number"], agent_id)

# Detach + remove a number from the account
rymi.numbers.remove(numbers["numbers"][0]["number"])

Telephony

python
status = rymi.telephony.status()        # {"connected": ..., "provider": ...}
carrier_numbers = rymi.telephony.numbers()

# Connect / disconnect a carrier (requires carrier credentials)
rymi.telephony.connect(provider="twilio", auth_id="AC...", auth_token="...")
rymi.telephony.disconnect()

Publishable keys

list_publishable returns prefixes only. create_publishable returns the full key once, so store it then. These use your secret key, so call them server-side only.

python
keys = rymi.keys.list_publishable()     # prefixes only, never full secrets

created = rymi.keys.create_publishable(
    agent_id=agent["id"],
    label="Marketing site widget",
    allowed_channels=["web"],
)
print(created["key"])                    # sb_publishable_... — store now

rymi.keys.revoke_publishable(created["id"])

Usage

Voice balance is reported in minutes (the customer-facing unit), not dollars.

python
usage = rymi.billing.usage_summary()
print(usage["voice_runtime"]["remaining_minutes"], usage["voice_runtime"]["status"])

Cost estimate & spend controls

Spend-management settings are denominated in USD.

python
est = rymi.billing.estimate(
    stt_model="nova-2",
    llm_model="gemini-2.5-flash",
    tts_model="gemini-2.5-flash-tts",
    duration_seconds=300,
)

rymi.billing.set_auto_recharge(enabled=True, pack_usd=50, threshold_usd=10)
rymi.billing.set_alerts(thresholds_usd=[25, 50], low_balance_pct=15, email_enabled=True)

Templates

python
templates = rymi.templates.list()["templates"]
starter = next((t for t in templates if t["label"] == "Healthcare Receptionist"), None)
if starter:
    rymi.agents.create(name="Front Desk", **starter["defaults"])

Webhooks

Register and maintain the endpoints Rymi delivers events to. Each endpoint has its own signing secret; keep it to verify deliveries (below).

python
created = rymi.webhooks.create(
    url="https://api.example.com/rymi-webhook",
    events=["call.completed", "call.intelligence.ready"],
    secret="whsec_your_webhook_secret",
    alert_email="oncall@example.com",
)

hooks = rymi.webhooks.list()
rymi.webhooks.update(hooks["webhooks"][0]["id"], events=["call.intelligence.ready"])
rymi.webhooks.delete(hooks["webhooks"][0]["id"])

Verifying deliveries

verify_signature is a stateless helper; it needs no API key.

python
import json
from flask import Flask, request, jsonify
from rymi import Rymi

app = Flask(__name__)
rymi = Rymi()

@app.route("/webhook", methods=["POST"])
def handle_webhook():
    signature = request.headers.get("x-rymi-signature")
    timestamp = request.headers.get("x-rymi-timestamp")
    payload_raw = request.get_data(as_text=True)

    rymi.webhooks.verify_signature(
        payload_raw,
        signature,
        timestamp,
        "whsec_your_webhook_secret"
    )

    event = json.loads(payload_raw)
    if event["type"] == "call.intelligence.ready":
        print("Summary:", event["data"]["summary"])

    return jsonify(status="OK"), 200