Client
user_idis mandatory, validated at construction (^user_[A-Za-z0-9]{20,}$). Chat and thread requests send it. Memory requests do not. Resource methods never takeuser_id.- Use it as an async context manager (
async with) or callawait client.close(). - Properties:
client.user_id,client.base_url. - Sub-resources:
client.chat,client.threads,client.memory, and, since 1.2.0,client.mcp,client.agents,client.skills,client.sources,client.documents. The last five do not takeuser_ideither.
Resources
client.chat
startreturns aStartedChat, a run whose.threadis guaranteed present.thread_capabilitiesis optional and only accepted here. Passing it tosendis aTypeError.message_scopeis accepted by both. See Capabilities.max_stepscaps the tool rounds of one message. Seemax_stepsbelow and Limiting agent steps.sendreturns anAssistantRunwith no.thread. You hold thethread_id, andthreads.getfetches the rest. Use it to answer the agent’s questions. Read the previous message to the end, or cancel it, before you callsend: see One message at a time.- Invalid arguments raise
InvalidParamsbefore any request is sent.
Stop a message
cancel stops an assistant message that is still being generated. It returns
the message with status set to cancelled. What the agent produced before the
stop stays in events.
cancel is not idempotent. A “stop” button should treat the 400 as “too late,
nothing to stop”. A cancelled message frees the thread right away: you can send
the next message without reading the cancelled message’s events. See
One message at a time.
▶ examples/12_cancel_message.py
Export a message: export
export(message_id, *, format) renders a message to a document and returns the
raw bytes. format is "pdf" or "docx" (type ExportFormat, importable from
cominty_sdk).
The server sends no file name. Pick one when you write the bytes.
▶
examples/13_export_message.py
Upload and download files
upload_file registers a file and returns a ConversationFile. Pass its id
in file_ids on start and send, at most 5 files per message.
filenameis 1 to 255 characters and cannot contain/.mimetypeis optional. It is guessed from the file name, withapplication/octet-streamas a fallback. An empty string is refused.- A file is at most 500 MiB. A bigger one raises
InvalidParamsbefore anything is sent. - A path that cannot be read raises
OSError. - The whole file is held in memory, in both directions.
download_file(file_id) returns the bytes of any conversation file, including
the ones the agent produced. An empty file_id raises ValueError. An unknown
file raises NotFoundError. The call is not scoped to the
client’s user_id: any file created through the API in your organization can be
downloaded.
The API has no call to list or delete uploaded files, and it enforces a daily
upload quota per organization. Another deployment can also set a lower size
limit.
httpx logs full request URLs at INFO, presigned signatures
included, so keep its logger at WARNING.examples/14_upload_files.py
max_steps
max_steps is a keyword-only argument of start and send, typed
MaxSteps = int | SERVER_DEFAULT. Import both from cominty_sdk:
examples/10_max_steps.py
client.threads
Scoped to the client’s user_id automatically. thread_id accepts a str or a
uuid.UUID.
client.memory
Scoped to a caller-chosen namespace, shared by the API key. Not scoped to
user_id. client.memory does not send user_id. Concepts:
Memory namespaces.
list() with no filter returns every file visible to the API key. Pass
namespace= to keep one namespace. Omitting namespace on create, get,
update, or delete is a TypeError. Thread, ThreadSummary, and Message
do not expose the thread’s namespace.
Migrate from per-user memory
Memory used to be scoped to the client’suser_id. It is now scoped to a
namespace you choose. client.user_id is still required and still applied to
chat and threads.
MemoryFileOut and MemoryFileSummaryOut each include namespace. Use the
value the API returns. Do not assume a namespace named default. For files
written before namespaces existed, see
Memory namespaces.
client.mcp
Lists the MCP integrations the API key can use (GET /mcp/manifest). See
MCP integrations.
enabled=True(the default) keeps the servers that are turned on.enabled=Falsekeeps the ones that are off.enabled=Nonereturns every server, in API order. The API returns all of them, and the SDK filters onis_enabled.nameis the value to store inMcpCapability.connectionsandMcpScope.connections.originis"builtin"or"remote", andscopeis"organization"or"user", today. Both are plain strings, and other values may appear.requires_oauth_connectisTruewhen someone must connect the integration with OAuth on the platform first.connection_status(for example"connected") is present when known.- If the manifest cannot be loaded, the API answers 500 and the SDK raises
ServerError.
examples/19_mcp_servers.py
client.agents
Manages custom agents and their model chain. Concepts:
Custom agents. Arguments after * are keyword-only.
scopeis"private"(the key owner only) or"organization"(every member). The type isScope.updateis partial: only the arguments you pass are sent. Acapabilitiesobject is merged capability by capability: each one you pass replaces the stored one, the others stay.create(model_ids=...)sets the model chain. Omit it for the organization’s default chain. The chain is a failover order.- On a built-in agent (
managedisTrue),set_modelschanges the chain of the whole organization for that agent.updateanddeleteraiseNotFoundError. list()returns the built-in agents, the organization agents, and the key owner’s private agents. It returnsAgentSummaryOut: noinstructions, nomodels. Callgetfor the fullAgentOut.- The legacy
execution_optionsandmemory_namespaceare not exposed. Usecapabilities(capabilities.memorysets the namespace). deleteis permanent. The API answers 204, and the SDK returnsNone.
examples/16_agents.py
client.skills
Manages custom skills. Concepts: Skills.
file is a folder Path (zipped in memory, SKILL.md at its root, entries
starting with . skipped), a .zip Path, or bytes (a zip archive). The
skill takes the key’s surface: there is no surface argument.
▶ examples/17_skills.py
client.sources
Turns local files into an indexed document source. Concepts:
Sources.
createmakes a drive, uploads each file through a presigned URL, then creates a private drag-and-drop source over it. Files upload one after the other, and the mimetype is guessed from the extension (application/octet-streamotherwise).filesis one filePath, a folderPath(its top-level files, hidden files skipped, no recursion), or an iterable of filePaths.source.pid(a string) goes to every method here.source.id(an integer) goes toIndexedDocumentsCapability(source_ids=[...])andIndexedDocumentsScope(source_ids=[...]).list()returns the organization’s sources and the ones the key created, archived ones included.get(pid)reads the list and picks, so it costs alist().add_filesandremove_filerequest a sync when the source accepts one (synced,error,out_of_sync,daily_quota_reached). The 50-file cap counts the files already in the source.download_filefetches a presigned URL valid 5 minutes, then the bytes. The whole file is held in memory.archiveis terminal. It removes the indexed documents and the files, and the source stays listed withstatus == "archived".- Only drag-and-drop sources can be created with a key.
create makes one, and a drive cannot
be deleted) and 50 files per source. A chat over a source must run with the key
owner’s user_id. See
Sources.
▶ examples/15_sources.py
client.documents
Resolves a composite document id, such as the one in a
<cite document_id="..."/>
tag. The id may contain #: the SDK URL-encodes it. An empty id raises
ValueError before any request. An unknown id raises
NotFoundError. There is no call that lists documents.
The run handle
Returned bychat.start (StartedChat) and by chat.send / chat.stream
(AssistantRun). A run is the assistant’s in-progress reply. Its stream is
single-use. Iterate it or await its result (the result is cached either
way).
One message at a time
A thread processes one message at a time. A message stayspending until
you read its events to the end, or cancel it. If you start a message and walk
away, the thread stays busy, and the next chat.send fails with
APIError (400, Cannot process multiple messages at the same time).
Leaving a stream early only releases the connection. The message keeps running
on the server. To stop reading and free the thread, cancel:
async with run: whenever you can leave the loop early. It closes the
stream cleanly. Without it, Python can print “asynchronous generator” errors
when the half-read stream is garbage collected.
If you lost the run, or an earlier call left a message unread, free the thread
by reading it. Message.live tells you which messages are still pending:
thread.live is True while any message is pending. A 400 from chat.send
that says Cannot process multiple messages at the same time always means this.
It is not the concurrency rate limit (RateLimitError with scope
concurrency, HTTP 429), which counts simultaneous chats across all threads.
Data models
All models are Pydantic. Response models ignore unknown fields, so they are forward-compatible.ThreadSummary + messages
Everything in
ThreadSummary, plus messages: list[Message].object
id: UUID · name: str · created_at: datetime · live: bool ·
agent: Agent · starred: bool · project_id: str | Noneobject
id: UUID · thread_id: UUID · role: MessageRole (user | assistant) ·
content: str · questions: list[Question] | None · live: bool ·
status: MessageStatus (pending | running | success | failed |
cancelled) · events: list[dict] | None · structured_output: dict | None ·
files: list[ConversationFile]object
prompt: str · options: list[str]object
id: str · name: strobject
id · name · size · mimetype · origin (user | agent) ·
share_links: list[ShareLink] · urlid · created_at · last_accessed_at? · access_count · revoked ·
expires_at? · expired · protected · urlobject
Returned by
memory.create, memory.get, and memory.update. path: str ·
namespace: str · purpose: str · content: str · created_at: datetime ·
updated_at: datetime · version: str (opaque, see client.memory)object
Returned by
memory.list. Everything in MemoryFileOut except content.object
Returned by
mcp.list. name: str (the value for connections) ·
title: str · description: str · is_enabled: bool · origin: str ·
scope: str · requires_oauth_connect: bool · connection_status: str | None ·
icon_uri? · mcp_url? · owner_id? · created_at? · updated_at?object
Returned by
agents.list. id: str · name: str · description: str | None ·
capabilities: AgentCapabilities | None · memory_namespace: str | None ·
managed: bool (True for built-in agents) · scope: Scope ·
owner: str (the user_id to chat with over the key’s sources) ·
execution_options: dict | None (legacy). Capabilities the SDK does not know
yet are left out.AgentSummaryOut + details
Returned by
agents.get, create, update, and set_models. Everything in
AgentSummaryOut, plus instructions: str | None ·
allow_execution_options_override: bool ·
models: list[LLMModelSummaryOut] (the chain, in failover order).object
Returned by
agents.list_models. id: str · name: strobject
Returned by
skills.list, create, update, and upload_version. id: str ·
display_name: str · active: bool · scope: Scope | None (None for a
built-in skill) · source: SkillSource · frontmatter: SkillFrontmatter ·
created_at: datetime · updated_at: datetimeobject
The YAML frontmatter of
SKILL.md, parsed by the server. name: str ·
description: str · license: str | None · compatibility: str | None ·
metadata: dict[str, str] · allowed_tools: list[str]object
Returned by the
client.sources methods that return a source. id: int (for
source_ids) · pid: str (for every client.sources method) · name: str ·
scope: Scope · created_by: str · store: SourceStore ·
status: SourceStatus · size: int · embedded_size: int ·
documents_synced: int · documents_failed: int · oversized_documents: int ·
documents_embedded: int · last_synced_at: datetime | None ·
archived_at: datetime | None · created_at: datetime · config: dict ·
drive_id (property, str | None)object
A file of a source.
id: str · name: str · size: int · mimetype: str ·
created_at: datetime · updated_at: datetimeobject
Returned by
documents.get. document_id: str · source_id: int ·
source_pid: str · source_type: SourceStore · document_name: str ·
document_uri: str | None · document_extension: str ·
document_mime_type: strLiteral
"private" (the key owner only) or "organization" (every member). Used by
agents, skills, and sources.enum
cominty (built in, read-only) or custom (uploaded by the organization).enum
draganddrop, googledrive, or sharepoint. An API key can only create
draganddrop sources.enum
pending · syncing · synced · error · out_of_sync · cancelling ·
pausing · paused · archived · disconnected · inoperative_connection ·
expired_connection · invalid_configuration · daily_quota_reachedCapabilities
Capabilities replacedisabled_tools, source_ids, document_ids, and
memory_namespace, which SDK 1.0.0 removes. Concepts and recipes:
Capabilities and
Use cases. To move old code, see
Migrate from SDK 0.5.0.
AgentCapabilities
Every field is optional and defaults to None. A field you do not pass keeps
the agent’s value. A field you pass replaces the agent’s value entirely.
activation is required on every capability. There are no shortcuts: web=True
and web="never" raise InvalidParams.
MessageScope
Every field is optional and defaults to None.
enabled: bool is required on every scope. There is no scope for skills or
memory. A scope can turn a capability on or off, or narrow a filter. It can
never widen what the thread allows.
ALL and filters
ALL is the string "*": no restriction. It is the default of every filter, so
passing it only makes the intent explicit. A filter is either ALL or a
non-empty list.
Memory
MemoryCapability(activation="always", namespace=...) attaches the thread to a
memory bag. The namespace is required and is 1 to 128 characters.
MemoryCapability(activation="never") runs the thread without memory and takes
no namespace. Without a memory entry, the thread uses the agent’s namespace, or
runs without memory if the agent has none. The bag is frozen for the thread: a
follow-up cannot change it. See Memory namespaces.
Errors
Invalid objects raiseInvalidParams as soon as you build them.
The server answers 422 (APIError) when a message widens an
allowlist, targets a capability the thread does not have, or sets
enabled=False on an on_request capability. See
Troubleshooting.
▶ examples/11_capabilities.py
Streamed events
from cominty_sdk import events
Every event has id: str, correlation_id: int, at: datetime,
status: "running" | "success" | "error", and a name. Match on the type:
Result.data.cost is a Cost: failed, input_tokens, cached_tokens,
output_tokens, input_cost, output_cost, total (all Decimal).
Exceptions
from cominty_sdk import ...
cominty_sdk.PermissionError shadows the Python built-in PermissionError once
you write from cominty_sdk import PermissionError. Import it as
cominty_sdk.PermissionError, or under another name, if your code also catches
the built-in.
SourceSyncFailed is not an APIError: sources.wait_synced raises it when the
source settles in a status other than synced, such as error, paused, or
daily_quota_reached. Read exc.source.status to see which one.
RateLimitError
Turns the terse 429 into a clear, actionable message and exposes:
scope→"organization"|"user"|"concurrency"retry_after→ seconds (from theRetry-Afterheader), orNonereset_at→datetime(fromreset_at/X-RateLimit-Reset), orNone
- Organization rate limit reached: your organization’s total request quota is exhausted. Ask an organization admin to raise your plan’s limit. Quota resets at 2026-06-30T00:00:00+00:00.
- User rate limit reached: your user request quota is exhausted. Ask an organization admin to raise your plan’s limit.
- Too many concurrent requests: your plan’s limit on simultaneous chat sessions is reached. Wait for an in-flight request to finish and retry, or raise the limit.