Metadata-Version: 2.5
Name: voxcore-sdk
Version: 0.1.1
Summary: The official Python client for VOXCORE — AI agents that answer your phone, text your customers, and talk to your website visitors.
Project-URL: Homepage, https://voxcore.net
Project-URL: Documentation, https://voxcore.net/docs
Project-URL: Source, https://github.com/CodeCraftStudios/voxcore-python
Author-email: CodeCraft Studios <support@voxcore.net>
License-Expression: MIT
Keywords: agents,ai,sms,telephony,voice,voxcore
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.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: Topic :: Communications :: Telephony
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.24
Provides-Extra: dev
Requires-Dist: mypy>=1.5; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: respx>=0.20; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Description-Content-Type: text/markdown

# VOXCORE for Python

The official Python client for [VOXCORE](https://voxcore.net) — AI agents that answer your phone, text your customers, and talk to visitors on your website.

```bash
pip install voxcore-sdk
```

The distribution is `voxcore-sdk`; what you import is `voxcore`.

## Getting started

You need a **secret key** and your **organization id**. Both come from your dashboard: keys under Settings → API keys, and the org id (`org__…`) from the URL.

```python
from voxcore import Voxcore

vox = Voxcore(api_key="vox_sk_live_…", org="org__…")

for contact in vox.contacts.iterate():
    print(contact["display_name"])
```

Or leave them out and set `VOXCORE_API_KEY` and `VOXCORE_ORG` in the environment, which is where a key belongs — not in a file that ends up in a commit:

```python
vox = Voxcore()
```

The client holds a connection pool, so reuse one rather than making a new one per call. It works as a context manager if you want the pool closed:

```python
with Voxcore() as vox:
    vox.agents.list()
```

## Listing things

Every collection has the same five verbs — `list`, `iterate`, `retrieve`, `create`, `update`, `delete` — and two ways to read:

```python
vox.contacts.list(status="customer")      # the first page, as a list
vox.contacts.iterate(status="customer")   # every page, as a lazy iterator
```

`list()` returns **one page**. That is deliberate: a tenant with forty thousand contacts should not discover that `list()` quietly made sixteen hundred requests. Use `iterate()` when you want all of them — it follows the API's cursor, so rows arriving while you read do not shift the pages under you.

```python
consented = [
    c for c in vox.contacts.iterate()
    if c["marketing_consent_message"]
]
```

## Writing things

```python
contact = vox.contacts.create(
    first_name="Dana",
    phone="+18175550147",     # E.164, always
    status="lead",
)

vox.contacts.update(contact["id"], status="customer")
```

### Retries and idempotency

`GET`s are retried automatically on connection failures, 429s and 5xx, with backoff.

**Writes are not**, unless you make them safe. Repeating a `POST` can create a second contact or place a second call, so the client will not do it on its own. Pass an idempotency key and it will:

```python
vox.contacts.create(first_name="Dana", idempotency_key=True)      # generated
vox.contacts.create(first_name="Dana", idempotency_key="order-8127")  # yours
```

Use your own key when you have a natural one — an order id, a row id — because that is what makes the retry safe across process restarts too.

Sending a text is keyed by default. Texting somebody twice is worse than the alternative:

```python
vox.threads.send("thr__…", "Your order is ready.")
```

## Errors

Everything inherits from `VoxcoreError`, so one `except` catches everything this library raises and nothing it does not.

```python
from voxcore import Voxcore, AuthenticationError, PermissionDenied, RateLimitError

try:
    vox.contacts.list()
except AuthenticationError:
    ...   # the key is wrong, expired or revoked — retrying will never help
except PermissionDenied:
    ...   # the key is fine and lacks a scope — fix it in the dashboard
except RateLimitError as e:
    ...   # retries were already exhausted; e.retry_after is seconds
```

Every error carries `.code` (VOXCORE's machine-readable string — branch on this, it is more stable than the HTTP status), `.message`, `.status` and `.request_id`. Quote the request id in a support conversation; it is how a specific request is found in the logs.

## What you can reach

| | |
|---|---|
| `vox.agents` | Agents — what they say, how they sound. Also `.publish()` |
| `vox.calls` | Calls in and out, and `.transcript()` |
| `vox.call_campaigns` | Outbound calling campaigns. `.start()`, `.pause()`, `.attempts()` |
| `vox.campaigns` | 10DLC registrations, which let a number send texts at all |
| `vox.webhooks` | Where we post when something happens. `.test()` |
| `vox.invoices`, `vox.usage` | What you were billed, and what you used |
| `vox.contacts` | The people you talk to. `.find_by_phone()` |
| `vox.contact_batches` | Named audiences. `.members()`, `.add()`, `.exclude()` |
| `vox.threads` | Texting conversations. `.send()` |
| `vox.phone_numbers` | Your lines |
| `vox.widgets`, `vox.forms`, `vox.flows` | Website widgets, forms, messaging sequences |
| `vox.meetings`, `vox.meeting_types` | Booking |
| `vox.keys` | API keys |

Anything not wrapped yet is still reachable, and that is on purpose — an SDK that lags the API by a release is an SDK that blocks you:

```python
vox.request("GET", "/api/v1/orgs/org__…/something-new")
```

## Two things worth knowing

**Consent is read-only.** `marketing_consent_call`, `marketing_consent_message` and `marketing_consent_email` can be read but never written through the API. They record what a customer actually said — with a timestamp, the call it came from, and a verbatim quote — and a field a script can set is not evidence that anybody said anything. The opt-out flags (`do_not_call`, `do_not_message`) *are* writable, because someone ringing to ask to be removed has to be recordable by whoever takes the call.

**Publishing is not saving.** Updating an agent does not change what callers hear. A live conversation pins the agent's published version, so changes reach the phone only when you publish:

```python
vox.agents.update(agent_id, system_prompt="…")
vox.agents.publish(agent_id, notes="new opening line")
```

## Requirements

Python 3.9+. One dependency: `httpx`.

## License

MIT
