Metadata-Version: 2.5
Name: didit-sdk
Version: 0.1.1
Summary: Unofficial Python SDK for Didit (didit.me) Identity Verification & KYC
Project-URL: Homepage, https://github.com/agustin18/didit-sdk
Project-URL: Documentation, https://github.com/agustin18/didit-sdk#readme
Project-URL: Repository, https://github.com/agustin18/didit-sdk
Project-URL: Issues, https://github.com/agustin18/didit-sdk/issues
Author-email: Agustin Saiz <agustinsaiz02@gmail.com>
License-Expression: MIT
License-File: LICENSE
License-File: NOTICE
Keywords: biometrics,didit,fastapi,identity-verification,kyc,sdk,webhooks
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.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.25.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: all
Requires-Dist: fastapi>=0.100.0; extra == 'all'
Provides-Extra: dev
Requires-Dist: fastapi>=0.100.0; extra == 'dev'
Requires-Dist: httpx>=0.25.0; extra == 'dev'
Requires-Dist: mypy>=1.10.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: respx>=0.21.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
Description-Content-Type: text/markdown

# didit-sdk

[![CI](https://github.com/agustin18/didit-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/agustin18/didit-sdk/actions/workflows/ci.yml)
[![PyPI version](https://img.shields.io/pypi/v/didit-sdk.svg)](https://pypi.org/project/didit-sdk/)
[![Python versions](https://img.shields.io/pypi/pyversions/didit-sdk.svg)](https://pypi.org/project/didit-sdk/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Checked with mypy](https://img.shields.io/badge/mypy-strict-blue)](https://mypy-lang.org/)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

Unofficial, community-maintained Python client for the [Didit](https://didit.me) Identity Verification API.

> [!IMPORTANT]
> **Legal Notice**: This is an independent, community-driven open-source project and is **not** officially affiliated with, endorsed by, or sponsored by Didit Protocol Inc. All trademarks, service marks, and company names are the property of their respective owners. See [NOTICE](NOTICE) for details.

---

## Highlights

- **Ergonomic Sync & Async**: Dual-client architecture built on top of high-performance `httpx`.
- **Strictly Typed & Validated**: 100% type annotations (PEP 561 compliant with `py.typed`) and robust Pydantic v2 domain models.
- **Didit V3 API Alignment**: Full fidelity to upstream V3 schemas (`id_verifications[]`, `liveness_checks[]`, `face_matches[]`, `aml_screenings[]`, `reviews[]`) with forward compatibility (`extra="allow"`) and backward-compatible property accessors.
- **Complete Session Lifecycle**: Covers all 10 documented Didit session statuses (`Not Started`, `In Progress`, `In Review`, `Approved`, `Declined`, `Expired`, `Abandoned`, `Kyc Expired`, `Resubmitted`, `Awaiting User`) with granular state inspection (`is_decided`, `is_closed`, `requires_review`, `requires_user_action`).
- **Cryptographic Security**: Constant-time HMAC-SHA256 signature verification (`X-Signature-V2` & `X-Signature`), full UTF-8 Unicode canonical JSON support (`ensure_ascii=False`, `allow_nan=False`), and authoritative signed body anti-replay verification with strict header matching.
- **FastAPI Integration**: Plug-and-play `DiditWebhookGuard` dependency for securing webhook endpoints with zero boilerplate.
- **Zero-Network Simulation Mode**: Built-in `SimulatedDidit` and `SimulatedAsyncDidit` to run unit tests and local end-to-end user flows completely offline without live credentials.
- **Resilient Error Hierarchy**: Typed exceptions (`DiditAuthenticationError`, `DiditRateLimitError`, `DiditNotFoundError`, `DiditServerError`) with automated rate-limit retry duration parsing.

---

## Installation

```bash
# Core SDK (httpx + pydantic)
pip install didit-sdk

# With FastAPI integration
pip install "didit-sdk[fastapi]"
```

---

## Quickstart

### 1. Synchronous Client

```python
from didit import Didit, SessionStatus

# Initialize client (falls back to DIDIT_API_KEY environment variable if omitted)
client = Didit(api_key="your_api_key", webhook_secret="your_webhook_secret")

# Create a verification session
session = client.sessions.create(
    vendor_data="user_12345",
    workflow_id="wf_kyc_standard",
    callback="https://yourapp.com/kyc/complete",
    language="es",
)

print(f"Verification URL: {session.url}")
print(f"Session ID: {session.session_id}")

# Fetch verification decision immediately:
decision = client.sessions.get_decision(session.session_id)

# Or poll until a terminal outcome (Approved or Declined) is reached:
decision = client.sessions.poll_decision(session.session_id, timeout=60.0, interval=2.0)
if decision.status == SessionStatus.APPROVED:
    print(f"User approved! Document: {decision.document.document_number}")
```

### 2. Asynchronous Client (`asyncio`)

```python
import asyncio
from didit import AsyncDidit, SessionStatus


async def main():
    async with AsyncDidit(api_key="your_api_key") as client:
        session = await client.sessions.create(
            vendor_data="user_987",
            workflow_id="wf_kyc_standard",
        )
        print(f"Session URL: {session.url}")

        decision = await client.sessions.get_decision(session.session_id)
        if decision.status.is_terminal:
            print(f"Final outcome: {decision.status}")


asyncio.run(main())
```

---

## Securing Webhooks with FastAPI

Didit dispatches signed HTTP POST events upon verification completion. `didit-sdk` provides a dedicated FastAPI dependency to verify signatures in constant time and prevent replay attacks:

```python
from fastapi import FastAPI, Depends
from didit import WebhookPayload, SessionStatus
from didit.integrations.fastapi import DiditWebhookGuard

app = FastAPI()

# Guard reads secret from argument or DIDIT_WEBHOOK_SECRET env var
webhook_guard = DiditWebhookGuard(secret="whsec_...")


@app.post("/api/v1/webhooks/didit")
async def handle_didit_event(
    payload: WebhookPayload = Depends(webhook_guard),
):
    print(f"Received event for session: {payload.session_id}")

    if payload.status == SessionStatus.APPROVED:
        # Mark user verified in your database
        ...
    elif payload.status == SessionStatus.DECLINED:
        # Handle rejection
        ...

    return {"received": True}
```

---

## Testing & Offline Simulation

Developing without live credentials or mock servers? Use the built-in simulator:

```python
from didit import SimulatedDidit, SessionStatus

# Create in-memory client
sim = SimulatedDidit(webhook_secret="test_secret")

# 1. Create a simulated session
session = sim.sessions.create(vendor_data="dev_user", workflow_id="wf_dev")
assert session.session_id.startswith("sim_")

# 2. Simulate user passing identity check
sim.approve_session(session.session_id)

decision = sim.sessions.get_decision(session.session_id)
assert decision.status == SessionStatus.APPROVED

# 3. Generate signed webhook payloads to test your local receiver
raw_body, headers = sim.generate_webhook_event(session.session_id)
# headers contain valid X-Signature-V2 and fresh X-Timestamp!
```

---

## Exception Handling

All client errors inherit from `DiditError`:

```python
from didit import Didit
from didit.errors import (
    DiditAuthenticationError,
    DiditNotFoundError,
    DiditRateLimitError,
    DiditServerError,
)

client = Didit()

try:
    decision = client.sessions.get_decision("non_existent_session")
except DiditNotFoundError:
    print("Session not found")
except DiditRateLimitError as exc:
    print(f"Rate limited. Retry after {exc.retry_after} seconds.")
except DiditAuthenticationError:
    print("Invalid API key")
except DiditServerError as exc:
    print(f"Didit server error ({exc.status_code}): {exc.response_body}")
```

---

## Environment Variables

| Variable | Default | Description |
|---|---|---|
| `DIDIT_API_KEY` | *None* (Required) | Your Didit secret API key |
| `DIDIT_BASE_URL` | `https://verification.didit.me/v3` | Didit API base URL |
| `DIDIT_WEBHOOK_SECRET` | *None* | Shared webhook HMAC secret |
| `DIDIT_TIMEOUT` | `30.0` | HTTP request timeout in seconds |
| `DIDIT_MAX_RETRIES` | `2` | Maximum retry attempts for transient errors |

---

## Development & Testing

```bash
# Clone the repository
git clone https://github.com/agustin18/didit-sdk.git
cd didit-sdk

# Install dependencies with uv
uv sync --extra dev

# Run unit tests with 100% coverage gate
uv run pytest --cov=didit --cov-report=term-missing

# Lint & Format check
uv run ruff check .
uv run ruff format --check .

# Strict type check
uv run mypy src/didit
```

---

## License

This project is licensed under the terms of the [MIT License](LICENSE).
