Metadata-Version: 2.4
Name: doculink-studio
Version: 0.2.0
Summary: Official Python SDK for the DocuLink Studio Customer API (OCR / LLM document processing + AI chat).
Project-URL: Homepage, https://doculink.studio
Project-URL: Documentation, https://doculink.studio/docs
Project-URL: Repository, https://gitlab.com/ecs7702831/doculink-studio/sdk/python
Project-URL: Bug Tracker, https://gitlab.com/ecs7702831/doculink-studio/sdk/python/-/issues
Author-email: DocuLink Studio <support@doculink.studio>
License: MIT
License-File: LICENSE
Keywords: api,doculink,document-processing,llm,ocr,sdk
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.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
Requires-Dist: pydantic>=2
Requires-Dist: python-socketio[client]>=5
Requires-Dist: requests>=2.28
Requires-Dist: websocket-client
Provides-Extra: dev
Requires-Dist: pytest-cov>=5; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: responses>=0.24; extra == 'dev'
Description-Content-Type: text/markdown

# DocuLink Studio — Python SDK

Official Python SDK for the **DocuLink Studio Customer API** — OCR / LLM document
processing and AI chat. It implements the full 32-endpoint contract with
automatic authentication, token auto-refresh, typed models, multipart uploads,
SSE chat streaming and Socket.IO real-time subscriptions.

- Distribution name (PyPI): `doculink-studio`
- Import package: `doculink_studio`

## Install

```bash
pip install doculink-studio
```

Requires Python 3.9+. Runtime dependencies: `requests`, `pydantic>=2`,
`python-socketio[client]`, `websocket-client`.

## Quickstart

```python
from doculink_studio import DoculinkClient

client = DoculinkClient(
    provider_api_key="<35-char provider key>",
    customer_api_key="<35-char customer key>",
    email="you@example.com",          # optional
    # base_url / document_ws_url / chat_ws_url default to the test environment
)

# Authentication is automatic on the first call, but you can force it:
client.authenticate()

usage = client.get_usage()
print(usage.planCode, usage.quotaRemaining)
```

### Configuration

| Kwarg | Default |
|-------|---------|
| `base_url` | `https://test-api-provider.doculink.studio/api/v1` |
| `document_ws_url` | `https://test-ws.doculink.studio` |
| `chat_ws_url` | `https://test-ws.doculink.studio:8000` |
| `timeout` | `30` (SSE / sync chat use 300s automatically) |
| `access_token` / `refresh_token` | optional — pre-set to skip initial auth |
| `session` | optional `requests.Session` |

## Authentication & auto-refresh

The client stores the access + refresh tokens after `authenticate()`. Every
authenticated request sets `Authorization: Bearer <AccessToken>`. On a `401` the
client transparently refreshes the access token and retries once; if refresh
fails it re-authenticates with the API keys and retries once.

## Upload & process a document

```python
result = client.upload_file(
    task_id="my-task",
    file=b"...pdf bytes...",         # bytes, a file path (str), or a stream
    schema_uuid="<schema uuid>",
    return_format_uuid="<return format uuid>",
    return_format_type="JSON",       # JSON | XML | CSV
    client_uuid=None,                # optional
    filename="invoice.pdf",
)
doc_id = result.DocumentScanUUID

# Drive the pipeline (each step returns a status string):
client.ocr_process("my-task", doc_id)
client.schema_process("my-task", doc_id)
client.mapping_process("my-task", doc_id)

output = client.get_json_output("my-task", doc_id)
print(output.JsonOutput)
```

Document status constants: `DocumentStatus.OCR` (`"1"`), `SCHEMA` (`"2"`),
`MAPPING` (`"3"`), `COMPLETED` (`"4"`), `ERROR` (`"9"`).

## Real-time — document processing (Socket.IO)

```python
from doculink_studio import DocScanUpdate

def on_update(u: DocScanUpdate):
    print(u.Status, u.CurrentLog)

sub = client.subscribe_document_scan(doc_id, on_update=on_update)
# ... later ...
sub.close()
```

On connect the SDK emits `join` with the **plain string** `"doc-scan-<uuid>"`
and dispatches `doc-scan` events parsed into `DocScanUpdate`.

## Chat

```python
session_id = client.create_chat_session(model="gpt-4o-mini")

# Synchronous (blocks until the full reply is ready):
msg = client.send_chat_message_sync(session_id, "Summarise the invoice")
print(msg.Content)

# Streaming over SSE:
def on_event(evt):
    if "chunk" in evt:
        print(evt["chunk"], end="")

final = client.send_chat_message_stream(session_id, "Explain more", on_event)
print("\nFinal:", final.Content)

# History / listing
sessions = client.list_chat_sessions(status="active", page=1, limit=20)
history = client.get_chat_history(session_id, page=1, limit=50)
```

### Chat over Socket.IO

```python
sub = client.subscribe_chat_session(
    session_id,
    on_start=lambda e: print("start", e),
    on_chunk=lambda chunk, e: print(chunk, end=""),
    on_end=lambda msg, e: print("\ndone:", msg.Content),
    on_error=lambda e: print("error", e),
)
client.send_chat_message(session_id, "hello")   # async (202) — watch the socket
# ...
sub.close()
```

The chat `join` emits an **object** `{"room": "chat-<sessionID>"}` and passes
`auth={"token": <access token>}` on the handshake when a token is available.

## Error handling

Every method raises `ApiError` on HTTP `>= 400` or an envelope `status: false`
(including billing rejections that come back as HTTP 200 with `status: false`).

```python
from doculink_studio import ApiError

try:
    client.get_usage()
except ApiError as e:
    print(e.status_code, e.message, e.status)
```

## Development

```bash
python -m venv .venv
source .venv/Scripts/activate     # Git Bash on Windows
pip install -e ".[dev]"
pytest -q
```

## More docs

See the bundled API reference at [`../docs/index.html`](../docs/index.html) and
the canonical contract in [`../CONTRACT.md`](../CONTRACT.md).
