Skip to main content

matilda-client

Public Python client SDK for the Matilda API — the Python port of @maincode-ai/matilda-client-sdk. OAuth login (PKCE + device flow) with managed token refresh, streaming chat with typed events, grammar-constrained structured output, robust chunked parallel file uploads, conversations, feedback, and API-key management. Async-first, minimal dependencies: httpx + stdlib only (per ADR-025 SDK-boundary rule).

Install

pip install matilda-client

Authentication

Two managed login flows (core-auth is an OAuth facade over FusionAuth; endpoints are discovered via RFC 8414 metadata). On success the client is auto-wired with a TokenManager — every request carries a managed access token, refreshed automatically (single-flight) before expiry and once on a 401:

async with MatildaClient(base_url="https://matilda.maincode.com/api") as client:
    # Headless (servers, CI, SSH): prints a URL + user code, polls until approved
    tokens = await client.auth.login_with_device_flow(client_id="matilda-code")

    # Desktop: opens the browser, receives the callback on 127.0.0.1 (RFC 8252)
    tokens = await client.auth.login_with_browser(
        client_id="matilda-code", open_browser=lambda url: webbrowser.open(url)
    )

# Persist the session across runs (0600 JSON file + cross-process refresh lock):
from matilda_client import create_file_token_store
store = create_file_token_store("~/.matilda/tokens.json")
await client.auth.login_with_device_flow(
    client_id="matilda-code", token_store=store.store, token_lock=store.lock
)

await client.auth.get_tokens()  # current TokenSet or None
await client.auth.logout()      # clears the session + detaches the provider

Or skip the flows and bring your own token:

MatildaClient(token="eyJ...")                      # static JWT
MatildaClient(get_token=my_async_token_provider)   # provider, 401→refresh→retry

The lower-level protocol is exported too (fetch_auth_server_metadata, create_pkce_pair, begin_login/complete_login for web BFF redirects, request_device_code/poll_device_token, TokenManager, StorageAdapter) — mirror of the TS SDK's auth-core/auth-node split.

matilda-key CLI

matilda-key create-api-key --name "ci-runner" [--scopes api:code,api:chat] [--expires-at 2026-12-31T23:59:59Z]

Runs a device-flow login, mints an API key, and prints the secret to stdout (pipeable) with all metadata on stderr.

Quick start

import asyncio
from matilda_client import MatildaClient

async def main():
    # Token comes from the matilda-code OAuth/PKCE flow (a FusionAuth Bearer JWT).
    async with MatildaClient(token="eyJ...", base_url="https://matilda.maincode.com/api") as client:
        response = await client.chat.create(input="G'day")
        print(response.output_text)

asyncio.run(main())

Chat

stream yields the full typed event stream; create accumulates one turn; stream_text / create_text are the text-only conveniences.

# Full typed events
async for event in client.chat.stream(input="Summarise this", file_ids=["f_..."]):
    if event.type == "response.output_text.delta":
        print(event.delta, end="")

# Just the text, raising on stream errors
text = await client.chat.create_text(input="Hello")

# Multi-turn: thread the conversation id you want to continue. Omitting it
# starts a NEW conversation, and the response does not return the auto-created
# id — so hold the id yourself (from conversations.list or your own store).
await client.chat.create(input="Hi", conversation_id="conv_...")
await client.chat.create(input="And a follow-up", conversation_id="conv_...")

Events mirror the TS SDK union: ResponseCreated, OutputTextDelta, OutputTextReplace, StatusEvent / QueuedEvent, ToolCall*, GenerationStatus, UsageEvent, CursorEvent, Truncated, Completed, ResponseError.

Structured output

Grammar-constrained generation from a JSON-schema dict or a pydantic model class (duck-typed — pydantic is not a dependency; local $ref/$defs are inlined before sending since the server's grammar compiler does not resolve pointers):

schema = {
    "type": "object",
    "properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
    "required": ["name"],
}
result = await client.chat.create_object(schema, input="Extract a person from 'Ada, 36'")
print(result.object)  # {'name': 'Ada', 'age': 36} — MatildaObjectParseError on failure

Resume a detached stream

active = await client.chat.active_stream(conversation_id)
if active.get("streamId"):
    async for event in client.chat.resume(active["streamId"], last_event_id=cursor):
        ...

Files

Chunked parallel uploads (OpenAI-shaped, S3-multipart-backed), auto-selecting single-shot vs chunked per file.

result = await client.files.upload("dataset.jsonl", on_progress=lambda pct: print(f"{pct}%"))
results = await client.files.upload_many(["a.jsonl", "b.jsonl"], file_concurrency=3)
  • Small files (< chunked_threshold, default 16 MiB): single-shot POST /files/upload.
  • Large files: create session → upload parts in parallel (default 4 concurrent) with per-part exponential-backoff retry (default 3) → complete (assembles S3 object + runs scan/extract). A dropped chunk just retries.

Conversations, feedback, API keys

conversations = await client.conversations.list(limit=20)
await client.conversations.update(conv_id, title="Quarterly report")
await client.conversations.set_message_feedback(conv_id, msg_id, "positive")

await client.feedback.report(message_id=msg_id, conversation_id=conv_id, reason="inaccurate")
await client.feedback.report_bug(title="SDK crash on resume", description="...")

key = await client.api_keys.create(name="ci")   # secret returned exactly once
await client.api_keys.revoke(key_id)

api_keys hits core-auth at {origin}/api/auth/api-keys — the origin is derived from base_url, so a base_url of https://host/api works as-is.

Errors

MatildaError is the base class. Non-2xx responses raise MatildaAPIError (QuotaExceededError on 429 with a structured quota body). Stream errors are ResponseError events on stream/create, and raise as MatildaStreamError on the text/object paths; SafetyReplaceError fires there when the server replaces in-flight output. Uploads raise UploadError after retries are exhausted.

If config.get_token (an async token provider) is configured, a 401 triggers one automatic retry with a force-refreshed token.

Configuration

Parameter Default Notes
chunk_size 8 MiB Part size (server is authoritative)
chunked_threshold 16 MiB Files ≥ this use chunked
part_concurrency 4 Parallel parts per file
max_part_retries 3 Per-part retry count
timeout 120 s httpx request timeout
api_version 2026-06-23 Sent as X-Matilda-API-Version

Development

python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/python -m pytest tests   # includes a contract-drift guard against @matilda/contracts
.venv/bin/ruff check src tests

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

matilda_client-0.3.0.tar.gz (41.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

matilda_client-0.3.0-py3-none-any.whl (46.1 kB view details)

Uploaded Python 3

File details

Details for the file matilda_client-0.3.0.tar.gz.

File metadata

  • Download URL: matilda_client-0.3.0.tar.gz
  • Upload date:
  • Size: 41.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for matilda_client-0.3.0.tar.gz
Algorithm Hash digest
SHA256 934f6d6a4190b2395168d3f6ca98043c5df17d78d3b77d2050948cfcff02ac85
MD5 c57e2c06abfda4fb2371361f3816a1af
BLAKE2b-256 7bece3dc2c262b9f31d0c48c4e942c886df582ddd47606ffe3fb81e6aca9c78f

See more details on using hashes here.

Provenance

The following attestation bundles were made for matilda_client-0.3.0.tar.gz:

Publisher: publish-pypi.yml on MaincodeHQ/matilda-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file matilda_client-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: matilda_client-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 46.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for matilda_client-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3eae0f66cd8f9cce1c05cb9d5e030cba482541f8e3f51d028e8ee737c797e170
MD5 5b3e8af03bfb163d8e6b59b11250c5a8
BLAKE2b-256 3df0d6e4294a92d8531bee6ead4b66af4251ef650a77ed340901994086371fba

See more details on using hashes here.

Provenance

The following attestation bundles were made for matilda_client-0.3.0-py3-none-any.whl:

Publisher: publish-pypi.yml on MaincodeHQ/matilda-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page