Skip to content

Python SDK

The rymi Python package provides a server-side wrapper around the Rymi REST API. Use it from backend services, workers, and data pipelines that need to 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_role="operator"
)

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"],
    agent_role="operator",
)
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 — 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(tier="operator", 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