Metadata-Version: 2.4
Name: raidxai
Version: 0.1.0
Summary: Official Python SDK for the Raid AI detection API — detect AI-generated and manipulated media, and fact-check claims.
Project-URL: Homepage, https://docs.raidxai.com
Project-URL: Documentation, https://docs.raidxai.com
Project-URL: Repository, https://github.com/Raid-AI-Corporation/Raid-AI-SDK
Author-email: Raid AI <info@raidxai.com>
License-Expression: LicenseRef-Raid-AI-Terms
License-File: LICENSE
Keywords: ai-detection,deepfake,fact-checking,forensics,raid,raidxai,sdk
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Provides-Extra: dev
Requires-Dist: datamodel-code-generator>=0.25; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# raidxai

Official Python SDK for the **Raid AI** detection API — detect AI-generated and manipulated media
(images, audio, video) and fact-check media against the public record.

Sync and async clients, Python 3.10+. Full API reference: **https://docs.raidxai.com**.

## Install

```bash
pip install raidxai
```

## Authentication

Every request uses a developer token. Create one in the Raid AI dashboard
(**Settings → API keys**) and keep it server-side — never ship it in client code.

```python
from raidxai import RaidClient, FileInput

raid = RaidClient(
    api_key="<your-api-key>",                 # or os.environ["RAID_API_KEY"]
    base_url="<raid-ai-api-url>",             # required — or os.environ["RAID_API_BASE_URL"]
    # timeout=60.0,
    # max_retries=2,
    # auth_header="bearer",                   # or "x-api-key"
)
```

Each API key carries scopes (`image`, `audio`, `video`, `fact-check`) — a call to a modality
your key isn't scoped for raises `RaidApiError` with a `403` (`api_key.scope_missing`).

## Usage

### Images (synchronous)

```python
res = raid.images.process(FileInput.from_path("suspect.jpg"))
print(res.images[0].verdict, res.images[0].confidence)

# …or from a URL:
raid.images.process_from_url("https://example.com/photo.jpg")
```

### Audio (synchronous)

```python
from raidxai import VoiceWorkflow

res = raid.audio.process(
    FileInput.from_path("clip.mp3"),
    workflow_type=VoiceWorkflow.AI_DETECTION_ONLY,
)
print(res.is_ai_detected, res.detection_confidence)
```

### Video (asynchronous — submit then poll)

```python
# One call: submit and wait for the terminal verdict.
job = raid.video.submit_and_wait(
    FileInput.from_path("clip.mp4"),
    client_duration_seconds=42,
    interval_seconds=3,
    timeout_seconds=300,
)
print(job.status, job.result.verdict if job.result else None)

# …or drive it yourself:
submitted = raid.video.submit(FileInput.from_path("clip.mp4"), client_duration_seconds=42)
state = raid.video.get_job(submitted.job_id)
```

### Fact-checking (asynchronous)

```python
job = raid.fact_checking.submit_and_wait(
    FileInput.from_path("photo.jpg"),
    "image",
    user_context="Claimed to be from the 2024 election.",
)
print(job.result.summary if job.result else None)
```

## Async

Every resource has an async twin on `AsyncRaidClient` with the same method names:

```python
import asyncio
from raidxai import AsyncRaidClient, FileInput

async def main():
    async with AsyncRaidClient(api_key="<your-api-key>") as raid:
        res = await raid.images.process(FileInput.from_path("photo.jpg"))
        print(res.images[0].verdict)

asyncio.run(main())
```

## Errors

Non-2xx responses raise `RaidApiError` (`.status`, `.code`, `.message`, `.body`, plus `.is_auth` /
`.is_payment_required` / `.is_rate_limited`). Transient `429`/`5xx` responses are retried automatically
with exponential backoff (`max_retries`). A `submit_and_wait` that never finishes raises `RaidTimeoutError`.

```python
from raidxai import RaidApiError, RaidTimeoutError

try:
    raid.images.process(file)
except RaidApiError as err:
    if err.is_auth:
        print("bad or unscoped token:", err.code)
    elif err.is_payment_required:
        print("out of credits:", err.code)
    else:
        print(err.status, err.code, err.message)
except RaidTimeoutError as err:
    print("job did not finish in time; last status:", err.last_status)
```

## Types

Response models are Pydantic v2 classes generated from the API's OpenAPI spec (`ImageForensicsResponse`,
`VideoJob`, `Verdict`, `JobStatus`, …), so attributes are snake_case and tracked against the server contract.

## Development

```bash
uv venv --python 3.12 && source .venv/bin/activate
uv pip install -e ".[dev]"
./scripts/generate.sh   # regenerate src/raidxai/_generated/models.py from ../spec/openapi.yaml
ruff check .
pytest
```

Run the live smoke test against a real tier (auto-skips without the key):

```bash
RAID_API_KEY=<your-api-key> RAID_API_BASE_URL=<raid-ai-api-url> pytest tests/test_live_smoke.py
```
