Metadata-Version: 2.4
Name: sixtydb
Version: 1.1.1
Summary: Official 60db SDK for Python
Author: 60db
License: MIT
Keywords: 60db,tts,stt,voice,ai
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.28.0
Requires-Dist: typing-extensions>=4.0.0

# 60db

Official 60db SDK for Python

## Installation

```bash
pip install sixtydb
```

## Quick Start

```python
from sixtydb import SixtyDBClient


def fetch_and_display_voices(client: SixtyDBClient) -> str:
    """
    Fetch all available voices and print their details.

    Returns:
        str: Selected voice_id
    """
    try:
        response = client.get_voices()
    except Exception as exc:
        print("Error fetching voices:", exc)
        raise

    if not response.get("success"):
        raise Exception(response.get("message"))

    voices = response["data"]["built_in_voices"]

    if not voices:
        raise Exception("No voices available.")

    print("\nAvailable Voices:\n" + "-" * 40)

    for voice in voices:
        print(
            f"Name: {voice.get('name')} | "
            f"Voice ID: {voice.get('voice_id')} | "
            f"Language: {voice.get('language_name')} | "
            f"Gender: {voice.get('gender')}"
        )

    return voices[0]["voice_id"]


def generate_speech(client: SixtyDBClient, voice_id: str) -> None:
    """
    Generate speech and save to file.
    """
    try:
        audio = client.text_to_speech(
            text="Hello, world!",
            voice_id=voice_id,
            speed=1.0
        )

        with open("output.mp3", "wb") as file:
            file.write(audio)

        print("\nAudio saved to output.mp3")

    except Exception as exc:
        print("Error generating speech:", exc)
        raise


def main() -> None:
    try:
        client = SixtyDBClient("your-api-key")
    except Exception as exc:
        print("Error creating client:", exc)
        return

    try:
        voice_id = fetch_and_display_voices(client)
        generate_speech(client, voice_id)
    except Exception as exc:
        print("Operation failed:", exc)


if __name__ == "__main__":
    main()
```

---

## API Methods

### Text-to-Speech
- `text_to_speech(text, voice_id=None, **params)` – Convert text to speech

### Speech-to-Text
- `speech_to_text(audio_file, language=None)` – Transcribe audio
- `get_languages()` – Get supported languages

### Voices
- `get_voices()` – List all voices

### Music
Free during beta (no credit check, nothing charged). Each `create()` call
makes **1 song**, and only **one generation runs at a time per workspace**
(a second `create()` while one is running returns 429). A song takes about
1.5-2.5 minutes.
- `music.create(prompt, lyrics_file=None, **params)` – Create a song (async)
- `music.list(**params)` – List songs (limit, offset, q, status, type, liked, sort)
- `music.get(song_id)` – Get a song (includes lyrics)
- `music.update(song_id, liked=None, played=None)` – Update flags
- `music.delete(song_id)` – Move a song to trash
- `music.download(song_id)` – Download the MP3 (bytes)
- `music.wait_for_completion(song_id, interval=4, timeout=600)` – Poll until succeeded/failed
- `music.list_voices()` – List catalog + saved voices
- `music.create_voice(name, file)` – Save a voice from an audio clip
- `music.health()` – Song service status

### Judge

Scores content against a rubric you define, and parses conversational turns.
A classifier, not a generator — answers are probability distributions over
your own options.

```python
run = client.judge.evaluate(
    state=call_transcript,
    questions={
        "tone": {"type": "choice",
                 "criteria": {"professional": "Calm", "dismissive": "Brushes off",
                              "unknown": "Not clear"}},        # always add an escape hatch
        "satisfaction": {"type": "score", "criteria": ["Angry", "Neutral", "Happy"]},
        "resolved": {"type": "noul"},
    },
)
run["answers"]["tone"]["choice"]           # 'dismissive'
run["answers"]["satisfaction"]["score"]    # 1.87 — weighted, not an index
run["answers"]["resolved"]["noul"]         # 0.28 — probability of true, not a confidence
run["min_confidence"]                      # under 0.85 → send to a human

rubric = client.judge.rubrics.create(name="Call QA", questions=questions)
client.judge.evaluate(state=transcript, rubric_id=rubric["id"])

turn = client.judge.extract(
    text="move my 10am appointment to friday",
    schema={"intents": {"booking": "Wants to change a booking"},
            "operations": {"reschedule": "Move it"},
            "entities": {"date": "A calendar date"}},
)
turn["intent"]["label"]   # 'booking'
turn["entities"]          # [{'label': 'date', 'text': 'friday', 'start': 28, 'end': 34, ...}]

client.judge.runs.list(needs_review=True)   # the low-confidence review queue
client.judge.models()                       # authoritative model list
client.judge.usage("current_month")         # spend and run counts
```

**Entity offsets are Unicode code points** — slice `list(text)[start:end]`,
not `text[start:end]`, or an emoji earlier in the turn shifts every span.

**Pricing:** per input token; the shared state is re-encoded once per
question. Context is 8K tokens **per question**. Failed runs are refunded.

### Dialer
Not free: a number costs **$6.00/month**, calls cost **$0.003/min**. Most
calls need a finished trunk setup (dashboard) and approved dialer KYC
(also done in the dashboard, not exposed by this SDK). `calls.reserve()`
does **not** dial — it only pre-authorizes a call id for a SIP client
(softphone/PBX) to use.
- `dialer.status()` – Provisioning status
- `dialer.numbers.search()` / `.buy(number=None, **params)` / `.list()` / `.release(e164)` / `.set_caller_id(e164)` – `buy()` requires approved KYC done in the 60db app, otherwise 403 `DIALER_KYC_REQUIRED`/`DIALER_KYC_PENDING`/`DIALER_KYC_REJECTED`; charges $6.00 now, then $6.00/month
- `dialer.calls.list(limit=None, offset=None)` / `.get(call_id)` / `.live()` / `.line_status()` / `.reserve(to, from_number, sip_call_id, direction="outbound")` – `reserve()` does not dial
- `dialer.recordings.list(**params)` / `.playback_url(id)` / `.transcript(id)` / `.delete(id)` / `.set_enabled(enabled)`
- `dialer.billing.subscriptions()` / `.usage(limit=None)`
- `dialer.webhooks.list()` / `.create(**params)` / `.update(id, **params)` / `.delete(id)` / `.rotate_secret(id)` / `.test(id)` / `.deliveries(id)`

---

## Example: Speech to Text

```python
from sixtydb import SixtyDBClient

client = SixtyDBClient("your-api-key")

try:
    with open("output.mp3", "rb") as file:
        result = client.speech_to_text(file, language="en")

    if result.get("success"):
        print("Transcription:", result["data"]["text"])
    else:
        print("STT Error:", result.get("message"))

except Exception as exc:
    print("Error during transcription:", exc)
```

---

## License

MIT
