Metadata-Version: 2.4
Name: cheqpoint
Version: 0.1.1
Summary: Human-in-the-loop approval SDK for AI agents
Home-page: https://cheqpoint.io
Author: Cheqpoint
Keywords: cheqpoint,human-in-the-loop,ai,agents,approval
Classifier: Development Status :: 3 - Alpha
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: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.28.0
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# cheqpoint

Official Python SDK for [Cheqpoint](https://app.cheqpoint.co) — human-in-the-loop approval queues for AI agents.

```bash
python3 -m pip install cheqpoint
```

### Verify installation

```bash
python3 -c "import cheqpoint; print(cheqpoint.__version__)"
```

> [!TIP]
> **macOS / Linux Troubleshooting**: If you see `ModuleNotFoundError` after installing, ensure you are using `python3 -m pip install` to target the correct Python environment.

## Quick start

```python
import os
from cheqpoint import CheqpointClient

client = CheqpointClient(api_key=os.environ["CHEQPOINT_API_KEY"])

# Your agent calls this instead of executing directly.
# checkpoint() submits the request and waits for a human decision.
result = client.checkpoint(
    type="refund",
    risk_level="high",
    summary="Refund $149 to sarah@example.com",
    details={"userId": "usr_123", "amount": 149, "currency": "USD"},
    justification="Double-charged on invoice #1821",
)

# effective_details returns modifiedDetails if set, else original details
stripe.refunds.create(**result.effective_details)
```

## Constructor

```python
CheqpointClient(
    api_key: str,                          # Required. Your workspace API key.
    base_url: str = "https://app.cheqpoint.co",
    timeout: float = 300,                  # Default poll timeout in seconds
)
```

## Methods

### `checkpoint(...)` → `CheckpointResult`

Submit an action for review and **block** until a human decides.

| Parameter | Type | Default | Description |
|---|---|---|---|
| `type` | `str` | required | Short label for the action (e.g. `"refund"`, `"email"`) |
| `risk_level` | `"low" \| "medium" \| "high"` | required | Risk level of the action |
| `summary` | `str` | required | One-sentence description shown to reviewers |
| `details` | `dict` | required | Structured payload. Returned as-is (or modified) on approval |
| `justification` | `str \| None` | `None` | Agent's reasoning shown to reviewers |
| `webhook_url` | `str \| None` | `None` | URL for Cheqpoint to POST the decision to |
| `poll_interval` | `float` | `3` | Seconds between status polls |
| `timeout` | `float \| None` | client default | Max seconds to wait |

**Returns** `CheckpointResult` when approved.
**Raises** `RejectedError` if rejected.
**Raises** `TimeoutError` if no decision within timeout.

```python
@dataclass
class CheckpointResult:
    id: str
    status: str                        # "APPROVED"
    details: dict                      # original payload
    modified_details: dict | None      # reviewer edits, if any
    response_notes: str | None         # reviewer's note

    def effective_details(self) -> dict:
        """Returns modified_details if set, else original details."""
```

### `create_request(...)` → `dict`

Fire-and-forget. Submits the request and returns immediately with `{"id": "..."}`. Pair with a `webhook_url` or poll manually with `get_request`.

### `get_request(request_id)` → `RequestStatus`

Fetch the current status of a request.

```python
@dataclass
class RequestStatus:
    id: str
    status: str             # "PENDING" | "APPROVED" | "REJECTED"
    type: str
    risk_level: str
    summary: str
    details: dict
    modified_details: dict | None
    response_notes: str | None
    webhook_delivered: bool
    created_at: str
    decided_at: str | None
```

## Error handling

```python
from cheqpoint import CheqpointClient, CheqpointError, RejectedError, TimeoutError

try:
    result = client.checkpoint(...)
except RejectedError as e:
    print(f"Rejected: {e.response_notes}")
except TimeoutError as e:
    print(f"Timed out for request {e.request_id}")
except CheqpointError as e:
    print(f"API error {e.status_code}: {e}")
```

## Examples

### With webhook (recommended for production)

```python
result = client.create_request(
    type="db-write",
    risk_level="medium",
    summary="Delete user account usr_456",
    details={"userId": "usr_456", "reason": "GDPR deletion request"},
    webhook_url="https://yourapp.com/webhook/cheqpoint",
)
request_id = result["id"]
# Store request_id — your Flask/Django webhook handler receives the decision
```

### Manual polling

```python
result = client.create_request(type="email", risk_level="low", ...)
request_id = result["id"]

# Check later
status = client.get_request(request_id)
if status.status == "APPROVED":
    payload = status.modified_details or status.details
    send_email(**payload)
```

### LangChain agent tool

```python
from langchain_core.tools import tool

@tool
def issue_refund(user_id: str, amount: float, reason: str) -> str:
    """Issue a refund to a customer. Requires human approval."""
    result = client.checkpoint(
        type="refund",
        risk_level="high",
        summary=f"Refund ${amount} to user {user_id}",
        details={"userId": user_id, "amount": amount, "reason": reason},
    )
    payload = result.effective_details
    return f"Refund approved for ${payload['amount']}"
```

### CrewAI tool

```python
from crewai_tools import BaseTool

class CheqpointApprovalTool(BaseTool):
    name: str = "Request Human Approval"
    description: str = "Submit a risky action for human approval before executing."

    def _run(self, action_type: str, summary: str, details: dict) -> str:
        result = client.checkpoint(
            type=action_type,
            risk_level="high",
            summary=summary,
            details=details,
        )
        return f"Approved. Effective details: {result.effective_details}"
```

### Flask webhook handler

```python
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/webhook/cheqpoint", methods=["POST"])
def cheqpoint_webhook():
    data = request.get_json()
    status = data["status"]
    payload = data.get("modifiedDetails") or data["details"]

    if status == "APPROVED":
        process_action(payload)

    return jsonify({"ok": True}), 200
```

## Requirements

- Python 3.9+
- `requests` library

## License

MIT
