Metadata-Version: 2.4
Name: chronary
Version: 0.4.2
Summary: Python client library for the Chronary calendar-as-a-service API
Project-URL: Homepage, https://chronary.ai
Project-URL: Documentation, https://docs.chronary.ai
Project-URL: Repository, https://github.com/Chronary/chronary-python
Project-URL: Issues, https://github.com/Chronary/chronary-python/issues
Author-email: Chronary <support@chronary.ai>
License-Expression: Apache-2.0
License-File: LICENSE
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
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: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: anyio<5,>=3.5.0
Requires-Dist: httpx<1,>=0.25.0
Requires-Dist: pydantic<3,>=2.0
Requires-Dist: typing-extensions<5,>=4.7
Provides-Extra: dev
Requires-Dist: pyright<2,>=1.1; extra == 'dev'
Requires-Dist: pytest-asyncio<2,>=0.24; extra == 'dev'
Requires-Dist: pytest-cov<8,>=5.0; extra == 'dev'
Requires-Dist: pytest<10,>=8.0; extra == 'dev'
Requires-Dist: respx<1,>=0.21; extra == 'dev'
Requires-Dist: ruff<1,>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# Chronary Python SDK

The official Python client for the [Chronary](https://chronary.ai) calendar-as-a-service API.

## Installation

```bash
pip install chronary
```

Requires Python 3.10+.

## Quickstart

### Synchronous

```python
from chronary import Chronary

client = Chronary(api_key="chr_sk_...")

# Create a calendar
calendar = client.calendars.create(name="Sales Team", timezone="America/New_York")

# Create an event
event = client.events.create(
    calendar.id,
    title="Strategy Sync",
    start_time="2026-03-28T14:00:00Z",
    end_time="2026-03-28T14:30:00Z",
)

# Check availability
free_slots = client.availability.get(
    "agt_abc123",
    start="2026-03-28T09:00:00Z",
    end="2026-03-28T17:00:00Z",
    slot_duration="30m",
)
```

### Asynchronous

```python
from chronary import AsyncChronary

async with AsyncChronary(api_key="chr_sk_...") as client:
    agent = await client.agents.create(name="Support Bot", type="ai")
    calendars = await client.agents.calendars.list(agent.id)
```

## Configuration

### Environment variable

```bash
export CHRONARY_API_KEY="chr_sk_..."
```

```python
# No api_key argument needed when the env var is set
client = Chronary()
```

### Custom options

```python
client = Chronary(
    api_key="chr_sk_...",
    base_url="https://api.chronary.ai",  # default
    timeout=60.0,                         # seconds, default 60
    max_retries=2,                        # default 2
)
```

### Custom httpx client

```python
import httpx

transport = httpx.HTTPTransport(retries=3)
http_client = httpx.Client(transport=transport)

client = Chronary(api_key="chr_sk_...", httpx_client=http_client)
```

## Resources

| Resource | Accessor | Methods |
|---|---|---|
| Agents | `client.agents` | `create`, `list`, `get`, `update`, `delete` |
| Calendars | `client.calendars` | `create`, `list`, `get`, `update`, `delete` |
| Events | `client.events` | `create`, `list`, `get`, `update`, `delete` |
| Webhooks | `client.webhooks` | `create`, `list`, `get`, `update`, `delete` |
| iCal Subscriptions | `client.ical_subscriptions` | `create`, `list`, `get`, `update`, `delete`, `sync` |
| Availability | `client.availability` | `get`, `get_calendar`, `find_meeting_time` |
| Usage | `client.usage` | `get` |

### Agent-scoped resources

```python
# Calendars owned by an agent
client.agents.calendars.create("agt_abc123", name="My Cal", timezone="UTC")
client.agents.calendars.list("agt_abc123")

# Events across all of an agent's calendars
client.agents.events.list("agt_abc123", start_after="2026-03-01T00:00:00Z")

# Agent availability
client.agents.availability.get("agt_abc123", start="...", end="...")
```

## Pagination

List methods return a pager with `auto_paging_iter()` for transparent pagination:

```python
pager = client.agents.list()

# Iterate through all pages automatically
for agent in pager.auto_paging_iter():
    print(agent.name)

# Or access the current page directly
print(pager.data)       # list of items
print(pager.total)      # total count
print(pager.has_more)   # whether more pages exist
```

## Error handling

```python
from chronary import Chronary, NotFoundError, RateLimitError, APIError

client = Chronary()

try:
    agent = client.agents.get("agt_nonexistent")
except NotFoundError:
    print("Agent not found")
except RateLimitError:
    print("Slow down")
except APIError as e:
    print(f"{e.status_code}: {e.message}")
```

All errors carry `.status_code`, `.message`, `.request_id`, `.error_type`, and `.body`.

## Per-request options

Override retries on a per-call basis:

```python
agent = client.agents.get("agt_abc123", max_retries=5)
```

## Webhook verification

Verify incoming webhook signatures using HMAC-SHA256:

```python
from chronary.webhook import verify_signature, SignatureVerificationError

try:
    verify_signature(
        payload=request.body,
        headers=request.headers,
        secret=webhook_secret,
    )
    # signature valid — process the event
except SignatureVerificationError:
    # reject the request (e.g. return 401)
    ...
```

## Request IDs

Every response model carries an `_request_id` attribute for correlating with server logs:

```python
agent = client.agents.get("agt_abc123")
print(agent._request_id)  # "req_..."
```

## Plans & limits

Chronary enforces a per-key rate limit and per-org monthly quotas that vary by plan:

| | Free | Pro |
|---|---|---|
| Rate limit | 10 req/s | 50 req/s |
| Webhook delivery retries | 4 | 8 |
| Agents | 3 | 50 |
| Calendars | 10 | 250 |
| Events / mo | 2,500 | 125,000 |
| API calls / mo | 50,000 | 1,000,000 |
| Pro-only features | — | Scheduling proposals, temporal holds, cross-calendar availability, per-agent API keys (`chr_ak_*`) |

Pro-only feature calls raise `APIError` (status 403) on Free. The SDK automatically retries `429` and `5xx` responses with exponential backoff (`max_retries`). See <https://docs.chronary.ai/resources/rate-limits/> for the full matrix.

## License

Apache-2.0
