Metadata-Version: 2.5
Name: kasookoo-server-sdk
Version: 0.1.0b0
Summary: Official Python server-side SDK for Kasookoo — create call intents and other backend operations.
Author: Kasookoo
License-Expression: MIT
License-File: LICENSE
Keywords: backend,click-to-call,kasookoo,sdk,server-sdk,voip
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Description-Content-Type: text/markdown

# kasookoo-server-sdk (Python)

Python SDK for Kasookoo's server-side API. **Server-only** — it holds your
organization's secret key, which must never reach a browser.

[PyPI package](https://pypi.org/project/kasookoo-server-sdk/)

This SDK only creates **call intents** — it doesn't place calls itself. That
half happens on the frontend, in either of two places:

- [`kasookoo-sdk`](https://www.npmjs.com/package/kasookoo-sdk#placing-a-call-to-a-phone-number-sip)'s
  `initSipCall({ phoneNumber, intentId, clientSecret })` ([readme.io: Calls](https://kasookoo-dev.readme.io/docs/calls)) — for a custom call UI you build yourself.
- [`kasookoo-click-to-call-widget`](https://www.npmjs.com/package/kasookoo-click-to-call-widget)
  ([readme.io docs](https://kasookoo-dev.readme.io/docs/kasookoo-click-to-call-widget))'s
  `<kasookoo-dialer>` — a drop-in call button/dial pad. It doesn't call `initSipCall()`
  directly itself; instead, it fetches an intent on its own from a route you
  give it (`intentEndpoint`, passed to its `init()`), then dials with what
  that route returns.

Either way, the shape is the same: your frontend asks your backend for a call
intent, your backend creates it with this SDK, and **your route must respond
with the two fields the frontend needs to actually place the call:**
`intent_id` and `client_secret`. Nothing else you put in that response
matters to either integration.

## Install

```bash
pip install kasookoo-server-sdk
```

## Usage

```python
import os
from kasookoo_server_sdk import Kasookoo

kasookoo = Kasookoo(secret_key=os.environ["KASOOKOO_SECRET_KEY"])

intent = kasookoo.call_intents.create(
    subject="Website visitor",  # must match the `subject` your frontend passes to init() — see below
    phone_number="+18005551234",
    max_duration_seconds=300,  # optional
)
```

`call_intents.create()` sends your secret key to Kasookoo and returns:

```python
@dataclass
class CallIntent:
    intent_id: str        # unique id for this call intent
    client_secret: str    # short-lived secret for the frontend — hand this off, never the org secret key
    subject: str
    phone_number: str
    expires_in: int       # seconds until client_secret expires if unused
    max_duration_seconds: int
    status: str            # "requires_dial", or any other status string the API returns
```

Respond from your route with at least `intent_id` and `client_secret` —
that's the whole contract, whichever frontend integration is consuming it
(see above): `kasookoo-sdk`'s `initSipCall()`, or
`kasookoo-click-to-call-widget`'s `intentEndpoint`. Your org's secret key
never needs to leave the server.

### Keep `subject` in sync with the frontend's `init()`

The `subject` you pass here must be the **exact same value** the frontend
passed to `init()` — `kasookoo-sdk`'s `KasookooClient.init({ subject })` or
`kasookoo-click-to-call-widget`'s `KasookooClickToCall.init({ subject })`.
Kasookoo checks the two against each other and rejects the call intent if
they don't match (surfaced as a `KasookooApiError` — see
[Error handling](#error-handling) below), so always pass `subject` here
explicitly, kept identical to whatever your frontend's `init()` call used —
don't assume it's implied or optional just because the frontend already sent
one.

## Configuration

| Option | Type | Default | Description |
|---|---|---|---|
| `secret_key` | `str` | *required* | Your organization's secret key. |
| `base_url` | `str` | `https://sdk-test.kasookoo.ai` | API base URL. |
| `timeout_seconds` | `float` | `10.0` | Request timeout in seconds. |

All three are keyword-only arguments to `Kasookoo(...)`.

## Error handling

```python
from kasookoo_server_sdk import Kasookoo, KasookooApiError, KasookooConfigError

try:
    kasookoo.call_intents.create(subject="Visitor", phone_number="+18005551234")
except KasookooApiError as err:
    # err.status  — HTTP status code (0 for network errors/timeouts)
    # err.code    — API-provided error code, if any
    # err.details — full parsed error response, if any
    ...
except KasookooConfigError:
    # invalid SDK usage — missing/empty field, bad config — raised before any network call
    ...
```

## Example

```python
import os

from kasookoo_server_sdk import Kasookoo, KasookooApiError

kasookoo = Kasookoo(
    secret_key=os.environ["KASOOKOO_SECRET_KEY"],
    base_url=os.environ.get("KASOOKOO_BASE_URL", "https://sdk-test.kasookoo.ai"),
)


def main() -> None:
    try:
        intent = kasookoo.call_intents.create(
            subject="Website visitor",
            phone_number="+18005551234",
            max_duration_seconds=300,
        )
        print("Call intent created:", intent)
        # Hand `intent.client_secret` (and `intent.intent_id`) to the frontend —
        # never the org's secret key itself.
    except KasookooApiError as err:
        print(f"Kasookoo API error [{err.status}]{f' {err.code}' if err.code else ''}: {err}")


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

## Requirements

- Python 3.9+. No runtime dependencies.
