> ## Documentation Index
> Fetch the complete documentation index at: https://docs.voicemark.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Full API reference for the VoiceMark Python SDK.

## Installation

```bash theme={null}
pip install voicemark
```

Requires Python 3.9+ and [`httpx`](https://www.python-httpx.org/). No audio processing dependencies — the SDK is a pure API client.

## Initialisation

```python theme={null}
from voicemark import VoiceMark

vm = VoiceMark(
    api_key="vmk_live_...",       # required
    base_url="https://api.voicemark.dev/v1",  # optional override
)
```

For async contexts (FastAPI, async Django, etc.):

```python theme={null}
from voicemark import AsyncVoiceMark

async with AsyncVoiceMark(api_key="vmk_live_...") as vm:
    enrollment = await vm.enroll(audio_bytes, user_id="user_123")
```

## Audio input

All methods that accept audio accept any of:

| Type | Example |
| - | - |
| `bytes` | `audio_bytes` from a file read or in-memory buffer |
| `pathlib.Path` | `Path("recording.webm")` |
| file-like | `open("recording.wav", "rb")` or `io.BytesIO(...)` |

***

## Methods

### `enroll(audio, user_id)`

```python theme={null}
from voicemark import VoiceMark, EnrollResponse

enrollment: EnrollResponse = vm.enroll(audio_bytes, user_id="user_123")
print(enrollment.voiceprint_id)   # store this — pass to verify()
print(enrollment.quality_score)   # 0–1 enrollment quality
```

Returns `EnrollResponse`:

```python theme={null}
@dataclass
class EnrollResponse:
    voiceprint_id: str
    enrolled_at: str           # ISO 8601
    quality_score: float       # 0–1
    duration_s: float
    glyph_params: dict[str, float]
```

***

### `challenge(exclude_phrase?)`

```python theme={null}
challenge = vm.challenge()
print(challenge.phrase)        # show to user before recording
print(challenge.challenge_id)  # pass to verify()
```

Returns `ChallengeResponse`:

```python theme={null}
@dataclass
class ChallengeResponse:
    phrase: str
    challenge_id: str
    expires_in: int            # seconds (default 300)
    source: ChallengeSource | None
```

***

### `verify(audio, *, voiceprint_id?, challenge_id?)`

```python theme={null}
from voicemark import VerifyResponse

result: VerifyResponse = vm.verify(
    audio_bytes,
    voiceprint_id=enrollment.voiceprint_id,  # identity check
    challenge_id=challenge.challenge_id,      # anti-replay
)

if result.authorized:
    # use result.token for downstream authorisation
    pass
```

Returns `VerifyResponse`:

```python theme={null}
@dataclass
class VerifyResponse:
    token: str                  # ephemeral JWT, 60 s TTL
    token_id: str
    authorized: bool
    pii: None
    expires_in: int             # seconds
    liveness_score: float | None
    transcription: str | None
    cloud_data: VerifyCloudData | None
```

***

### `token(token_id, token_jwt?)`

```python theme={null}
check = vm.token(result.token_id, token_jwt=result.token)
print(check.valid)       # True / False
print(check.expires_in)  # seconds remaining
```

Pass `token_jwt` for authoritative JWT signature + expiry validation. Without it the endpoint returns a lightweight status check.

***

### `policy(token, action)`

```python theme={null}
perm = vm.policy(result.token, "payment.authorise")
if perm.permitted:
    process_payment()
```

***

### `receipt(token, action, result, metadata?)`

```python theme={null}
vm.receipt(
    result.token,
    "payment.authorise",
    "success",
    {"amount": 250, "currency": "GBP"},
)
```

***

## Error handling

All methods raise `VoiceMarkError` on failure:

```python theme={null}
from voicemark import VoiceMarkError

try:
    result = vm.verify(audio_bytes, voiceprint_id=stored_id)
except VoiceMarkError as e:
    print(e.code)     # "VOICE_MISMATCH" | "CHALLENGE_EXPIRED" | ...
    print(e.status)   # HTTP status code
    print(str(e))     # human-readable message
```

### Error codes

| Code | Description |
| - | - |
| `UNAUTHORIZED` | Missing or invalid API key |
| `VOICE_MISMATCH` | Speaker below match threshold |
| `TOKEN_EXPIRED` | Token TTL elapsed |
| `INVALID_AUDIO` | Unrecognised audio format |
| `AUDIO_TOO_SHORT` | Clip under 2 seconds |
| `AUDIO_TOO_QUIET` | RMS below threshold |
| `LOW_VOICE_SIGNAL` | No voice identity in audio |
| `CHALLENGE_EXPIRED` | Challenge TTL elapsed |
| `NETWORK_ERROR` | Network-level error |
| `PIPELINE_UNAVAILABLE` | Transient model unavailability (503) |

***

## Context managers

Both clients implement context managers for connection pooling:

```python theme={null}
# sync
with VoiceMark(api_key="vmk_live_...") as vm:
    enrollment = vm.enroll(audio, user_id="user_1")

# async
async with AsyncVoiceMark(api_key="vmk_live_...") as vm:
    enrollment = await vm.enroll(audio, user_id="user_1")
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.