Metadata-Version: 2.4
Name: ravi-sdk
Version: 0.2.0
Summary: Ravi SDK — Python client for the Ravi identity API
Project-URL: Homepage, https://github.com/ravi-hq/ravi-python
Project-URL: Documentation, https://github.com/ravi-hq/ravi-python#readme
Project-URL: Issues, https://github.com/ravi-hq/ravi-python/issues
Project-URL: Repository, https://github.com/ravi-hq/ravi-python
Author: Ravi Technologies, Inc.
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: ai-agents,email,identity,ravi,sms
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
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: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.9.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
Requires-Dist: pytest>=8.3.0; extra == 'dev'
Requires-Dist: respx>=0.21.1; extra == 'dev'
Requires-Dist: ruff>=0.8.0; extra == 'dev'
Description-Content-Type: text/markdown

# ravi

The Python SDK for Ravi — the identity provider for AI agents.

```bash
pip install ravi
```

```python
import os
from ravi import Ravi

# Keys are ravi_id_... (scoped to one identity) or ravi_mgmt_... (account-wide).
ravi = Ravi(api_key=os.environ["RAVI_API_KEY"])

# Create an identity. The email address is an identifier (local part) plus an
# optional domain; omit both to auto-generate. provision_phone adds a number.
identity = ravi.identities.create(email_identifier="support-agent", provision_phone=True)
print(identity.inbox, identity.phone)   # support-agent@... , +1...

# Place an outbound voice call from the identity's number.
call = ravi.calls.start(to_number="+15551234567")
print(call.uuid, call.status)
transcript = ravi.calls.transcript(call.id)

# Replay events — the deploy-safe companion to the wss://<host>/ws/events/ stream.
for event in ravi.events.list(since=0, event_types=["call.ended", "email.message.received"]):
    print(event.seq, event.type, event.data)
```

By default the SDK talks to the Ravi API at `https://api.ravi.id` (override with
`RAVI_BASE_URL` or `base_url=`).

## Status

## Getting an API key

Mint a key one of three ways, then pass it as `Ravi(api_key=...)` (or set
`RAVI_API_KEY`):

- `ravi auth login` with the [Ravi CLI](https://github.com/ravi-hq/cli) (one-time
  human sign-in, then agents self-serve),
- `POST /api/auth/keys/management/` from an authenticated session, or
- the dashboard at `/dashboard/api-keys/`.

Keys are `ravi_mgmt_...` (account-wide) or `ravi_id_...` (scoped to one identity).

## Resources

| Accessor | Endpoints |
|----------|-----------|
| `ravi.identities` | identity bundles + `provision_phone` + `voice_agent` |
| `ravi.email` | inboxes, messages (`compose`/`reply`/`reply_all`/`forward`), `threads` |
| `ravi.phones` | provisioned numbers (read-only) |
| `ravi.sms` | SMS `list`/`get`/`send` + `conversations` |
| `ravi.calls` | `start`/`list`/`get`/`hangup`/`transcript` |
| `ravi.events` | durable event replay (`list(since=...)`) |
| `ravi.contacts` | directory + `find` + `search` |
| `ravi.passwords` | website credentials + `generate` |
| `ravi.secrets` | key/value secrets |
| `ravi.mfa` | TOTP secrets |
| `ravi.domains` | verified domains (read-only) |
| `ravi.webhooks` | subscriptions + `deliveries` log |
| `ravi.api_keys` | `management` + `identity` keys |

## Verifying inbound webhooks

Ravi signs each webhook with `X-Ravi-Timestamp` + `X-Ravi-Signature: sha256=<hex>`
over `"{timestamp}.{raw_body}"`. Verify against the **raw** body:

```python
from ravi import verify_webhook_signature, WebhookSignatureError

try:
    verify_webhook_signature(
        raw_body,                       # bytes/str, exactly as received
        request.headers["X-Ravi-Timestamp"],
        request.headers["X-Ravi-Signature"],
        signing_secret,                 # from the webhook subscription
    )
except WebhookSignatureError as exc:
    ...  # reject: exc.code is "invalid_header" | "expired" | "signature_mismatch"
```

## License

[Apache 2.0](https://github.com/ravi-hq/ravi/blob/main/LICENSE).
