Metadata-Version: 2.4
Name: formable-sdk
Version: 0.1.0
Summary: Official Python SDK for the Formable API
Project-URL: Homepage, https://www.formabledocs.com
Project-URL: Repository, https://github.com/FormableDocs/formable-python
Project-URL: Issues, https://github.com/FormableDocs/formable-python/issues
Author: Formable
License-Expression: MIT
License-File: LICENSE
Keywords: contracts,esignature,formable,redlining,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
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: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Requires-Dist: typing-extensions>=4.7
Description-Content-Type: text/markdown

# formable-sdk

Official Python SDK for the [Formable API](https://api.formabledocs.com) (v1). Covers templates, signature requests, redlining, and billing.

- Sync (`Formable`) and async (`AsyncFormable`) clients
- Fully typed requests and responses (`py.typed`)
- Python 3.9+

## Installation

```bash
pip install formable-sdk
```

## Usage

```python
import os
from formable import Formable

formable = Formable(api_key=os.environ["FORMABLE_API_KEY"])
```

### Templates

```python
with open("nda.docx", "rb") as f:
    result = formable.templates.create(
        file=f.read(),
        filename="nda.docx",
        signer_roles=[
            {"name": "Client", "order": 0},
            {"name": "Witness", "order": 1},
        ],
    )

template_id = result["templateId"]

# Mint a fresh edit URL later (expires after 1 day)
edit = formable.templates.create_edit_url(template_id)
print(edit["editUrl"], edit["expiresAt"])
```

### Signature requests

```python
# Formable emails each signer a signing link
request = formable.signature_requests.create(
    template_id=template_id,
    signers=[
        {"email": "jane@example.com", "name": "Jane Doe", "role": "Client"},
        {"email": "bob@example.com", "name": "Bob Smith", "role": "Witness"},
    ],
)

# Embedded flow: mint signing URLs to embed in an iframe yourself
embedded = formable.signature_requests.create_embedded(
    template_id=template_id,
    signers=[{"email": "jane@example.com", "name": "Jane Doe", "role": "Client"}],
    test_mode=True,
)

signer = embedded["signers"][0]
signing = formable.signature_requests.create_signing_url(
    signer["recipientSignatureId"]
)

# Track progress
from datetime import datetime, timezone

current = formable.signature_requests.get(embedded["signatureRequestId"])
all_requests = formable.signature_requests.list(
    updated_since=datetime(2026, 1, 1, tzinfo=timezone.utc)
)
events = formable.signature_requests.get_events(embedded["signatureRequestId"])

# Download the signed document once completed
envelope = formable.signature_requests.get_signed_envelope(
    embedded["signatureRequestId"]
)
print(envelope["signedEnvelopePresignedUrl"])
```

### Redline requests

```python
created = formable.redline_requests.create(
    template_id=template_id,
    members=[
        {"email": "us@example.com", "display_name": "John Doe", "role": "DisclosingParty"},
        {"email": "them@example.com", "display_name": "Jane Smith", "role": "ReceivingParty"},
    ],
    metadata={"subject": "Mutual NDA"},
)

redline_request_id = created["redlineRequestId"]

# Mint a redline URL for a member (embed in an iframe)
url = formable.redline_requests.create_url(redline_request_id, "them@example.com")

# Manage members and track progress
formable.redline_requests.update_members(
    redline_request_id,
    [{"email": "counsel@example.com", "display_name": "Counsel", "role": "ReceivingCounsel"}],
)
redline = formable.redline_requests.get(redline_request_id)
events = formable.redline_requests.get_events(redline_request_id)
```

### Billing and health

```python
billing = formable.billing()
print(billing["numberOfRedliningSessions"])

health = formable.health()
```

### Async client

Every method is also available on `AsyncFormable` with the same signatures.

```python
import asyncio
from formable import AsyncFormable

async def main():
    async with AsyncFormable(api_key=os.environ["FORMABLE_API_KEY"]) as formable:
        health = await formable.health()

asyncio.run(main())
```

## Error handling

All non-2xx responses raise a `FormableError` with the server's error message, HTTP status, and parsed response body.

```python
from formable import FormableError

try:
    formable.signature_requests.get("missing-id")
except FormableError as error:
    print(error.status, error)
```

## Configuration

| Option     | Description                                                      | Default                           |
| ---------- | ---------------------------------------------------------------- | --------------------------------- |
| `api_key`  | Your Formable API key (sent as a bearer token). Required.        | -                                 |
| `base_url` | Override the API base URL.                                       | `https://api.formabledocs.com/v1` |
| `client`   | Custom `httpx.Client` (or `httpx.AsyncClient` for async).        | Built-in client with 60s timeout  |

## Development

```bash
python3 -m venv .venv
.venv/bin/pip install -e . pytest mypy build
.venv/bin/python -m pytest tests
.venv/bin/python -m mypy src/formable
```

## Publishing

```bash
.venv/bin/python -m build
.venv/bin/python -m pip install twine
.venv/bin/python -m twine upload dist/*
```
