clark-platform-client
Typed, standalone Python client (sync + async) for the Clark Platform API — the OpenAI-compatible-ish public HTTP API for creating Clark agent runs, streaming their progress, and reading back chat-completion/response objects, usage, artifacts, and durable memory. It also covers Clark-owned file uploads and repository commit context for agent integrations.
This client implements the wire contract documented in
platform/clients/API_CONTRACT.md in the Clark
monorepo. If something here disagrees with that file, the contract file is
the source of truth.
- Only required dependency:
httpx. - No
pydantic— models are plain stdlibdataclasses. - Both a sync client (
ClarkClient) and an async client (AsyncClarkClient); pick one without pulling inasynciomachinery you don't need. - Python 3.9+.
Install
Published on PyPI:
uv add clark-platform-client
# or: pip install clark-platform-client
Authentication
Platform API keys (ck_live_...) are minted from the signed-in /platform
page in the Clark web app — there is no public self-serve key-creation
endpoint. Every key carries a fixed scope set
(responses:create, responses:read, models:read, artifacts:read,
memories:read, files:read, files:write).
from clark_platform import ClarkClient
client = ClarkClient(api_key="ck_live_xxxxxxxxxxxxxxxxxxxx")
By default the client talks to production (https://www.clarkchat.com).
Quickstart — sync
from clark_platform import ClarkClient
with ClarkClient(api_key="ck_live_...") as client:
models = client.models.list()
print([m.id for m in models.data])
response = client.responses.create(model="clark", input="Summarize this repo's README.")
print(response.status, response.output_text)
chat = client.chat.completions.create(
model="openrouter:qwen37_flash",
messages=[{"role": "user", "content": "hello"}],
)
print(chat.output_text)
Quickstart — async
import asyncio
from clark_platform import AsyncClarkClient
async def main() -> None:
async with AsyncClarkClient(api_key="ck_live_...") as client:
response = await client.responses.create(model="clark", input="hello")
print(response.output_text)
asyncio.run(main())
Two execution modes: agentic vs. provider gateway
The API has two entirely different behaviors depending on model:
- Agentic tiers (
clark,clark_max,openrouter:*) — Clark runs its own internal agent loop and returns only the final projected answer.tools/tool_choiceare not accepted by these methods (the server rejects them with400 unsupported_parameter, so the SDK doesn't even expose those parameters onresponses.create/chat.completions.create). - Provider-qualified
author/modelgateway IDs — yourmessages(including priortool_calls/toolmessages) are forwarded verbatim to the underlying OpenRouter-compatible model, and native OpenAI-formattool_callscome back for you to execute yourself. Clark does not run tools for this tier.clark-coderemains a compatibility alias. This Python client's typed passthrough helper uses/v1/chat/completions; the REST API also exposes provider-qualified passthrough on/v1/responses.
Because these are different response shapes, the passthrough tier is exposed via clearly separate methods that return the raw upstream JSON dict instead of a typed Clark object:
import os
# Agentic — typed ChatCompletionObject, no tools allowed.
chat = client.chat.completions.create(model="clark", messages=[...])
# Passthrough — raw OpenAI-compatible dict, tools/tool_choice forwarded verbatim.
raw = client.chat.completions.create_passthrough(
model=os.environ["CLARK_MODEL"],
messages=[...],
tools=[{"type": "function", "function": {...}}],
)
Streaming
responses.stream(...)
Named SSE events (response.created, response.output_text.delta,
response.artifact.completed, response.usage.updated,
response.completed/response.failed, ...). Per the contract, the full
answer arrives as a single response.output_text.delta event, not
token-by-token — the iterator API is for symmetry with chat-completions
streaming and future finer-grained deltas.
for event in client.responses.stream(model="clark", input="hello"):
if event.type == "response.output_text.delta":
print(event.delta, end="")
elif event.type == "response.completed":
print("\n--- done ---", event.response.status)
Async:
async for event in async_client.responses.stream(model="clark", input="hello"):
...
chat.completions.stream(...)
Plain data:-only SSE (no event: name), chat.completion.chunk objects,
terminated by the server's literal data: [DONE] (already consumed for you
— it never appears as a yielded item):
for chunk in client.chat.completions.stream(
model="clark",
messages=[{"role": "user", "content": "hello"}],
stream_options={"include_usage": True},
):
for choice in chunk.choices:
if choice.delta.content:
print(choice.delta.content, end="")
if chunk.usage:
print("\nusage:", chunk.usage)
chat.completions.stream_passthrough(...)
Same framing, but yields raw upstream JSON dicts (native tool_calls
deltas and all) instead of a typed ChatCompletionChunk, since the
passthrough tier's chunk shape is whatever the upstream OpenAI-compatible
provider sends.
Other endpoints
# Poll a response (e.g. after background=True, or to recover from a chat
# completions timeout by replacing the "chatcmpl_" prefix with "resp_").
response = client.responses.get("resp_01jz4n8h2f7g9k4q2m6s")
# Progress events for a response.
events = client.responses.list_events("resp_01jz4n8h2f7g9k4q2m6s", after_seq=0, limit=200)
# Durable memory.
memories = client.memories.list(q="deploy checklist")
# Upload, list, retrieve, and delete caller-owned files. Use the returned
# clark_file_* id in later gateway requests; upstream ids are never exposed.
uploaded = client.files.create(
b"release notes",
filename="notes.txt",
mime_type="text/plain",
purpose="assistants",
)
files = client.files.list(limit=20)
metadata = client.files.retrieve(uploaded.id)
deleted = client.files.delete(uploaded.id)
# Bounded commit evidence for a repository previously indexed by Clark Desktop.
context = client.code.repositories.context(
"repository-fingerprint",
q="authentication changes",
limit=8,
)
# Membership-checked organizational knowledge available to the caller.
knowledge = client.organization_knowledge.search(
query="authentication changes",
limit=8,
)
# Server-owned image generation accepts only base64 data-image references.
image = client.images.generate(
prompt="Turn this into a warm editorial illustration.",
input_images=["data:image/png;base64,iVBORw0KGgo..."],
idempotency_key="image-retry-1",
)
# Raw HTTP response for range-capable artifact downloads.
artifact = client.artifacts.download(
"conversation-id", "report.pdf", download=True, headers={"Range": "bytes=0-1023"}
)
The async client exposes the same methods under async_client.files and
async_client.code.repositories; await each call.
Errors
All non-2xx responses raise ClarkApiError, parsed from the API's error
envelope:
from clark_platform import ClarkApiError
try:
client.responses.create(model="clark", input="hello")
except ClarkApiError as err:
print(err.status_code, err.type, err.code, err.param, err.message)
Network-level failures (connection errors, timeouts, DNS failures) are
not wrapped — they surface as the underlying httpx exception (e.g.
httpx.ConnectError, httpx.TimeoutException), so you can rely on
httpx's own exception hierarchy for those.
Timeouts
Non-background responses/chat.completions calls can legitimately take up
to ~120s server-side (CLARK_PLATFORM_RESPONSE_WAIT_MS, default 120000ms).
The client defaults its httpx timeout to 150s to comfortably clear that;
pass your own timeout= (or a pre-configured http_client=) to
ClarkClient/AsyncClarkClient to change it.
Development
cd platform/clients/python
uv sync --python 3.12
uv run --python 3.12 pytest -q
examples/live_smoke.py is an opt-in, real-network smoke test — it reads
CLARK_API_BASE_URL, CLARK_API_KEY, and CLARK_TEST_MODEL from the
environment and exits early with a clear message if CLARK_API_KEY is
unset. It is not part of the test suite and is never run automatically.
Metadata
Release files for clark-platform-client 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| clark_platform_client-0.3.0.tar.gz | 37.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| clark_platform_client-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 62.1 kB
Release files / clark_platform_client-0.3.0.tar.gz
| Download URL | clark_platform_client-0.3.0.tar.gz |
|---|---|
| Size | 37.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7b21b1100114a80477898e84c85ac13ec76dee87669d63f2f7465dad5dc9418f
|
|
BLAKE2b-256 checksum How to use checksums |
0426a76214396dce07731018352e2af2823cd6906e0cbdf5884111073fc91d45
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / clark_platform_client-0.3.0-py3-none-any.whl
| Download URL | clark_platform_client-0.3.0-py3-none-any.whl |
|---|---|
| Size | 25.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
546d8aa9c6750c4e338e42fd82d8be9bdc6bc92d5c1d2b4ba92987837386cb84
|
|
BLAKE2b-256 checksum How to use checksums |
1d71514be99156ed0cc82960a630de1ce855f14b544f9e6ffeba6ef1569b91e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|