Skip to main content
This page documents the SDK’s public surface. For task-oriented examples, see the Quickstart and the guides. For the underlying HTTP endpoints, see the API reference.

Client

  • user_id is mandatory, validated at construction (^user_[A-Za-z0-9]{20,}$). Chat and thread requests send it. Memory requests do not. Resource methods never take user_id.
  • Use it as an async context manager (async with) or call await 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 take user_id either.
See Configuration for the full settings table and resolution order.

Resources

client.chat

  • start returns a StartedChat, a run whose .thread is guaranteed present. thread_capabilities is optional and only accepted here. Passing it to send is a TypeError. message_scope is accepted by both. See Capabilities.
  • max_steps caps the tool rounds of one message. See max_steps below and Limiting agent steps.
  • send returns an AssistantRun with no .thread. You hold the thread_id, and threads.get fetches the rest. Use it to answer the agent’s questions. Read the previous message to the end, or cancel it, before you call send: see One message at a time.
  • Invalid arguments raise InvalidParams before 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.
  • filename is 1 to 255 characters and cannot contain /.
  • mimetype is optional. It is guessed from the file name, with application/octet-stream as a fallback. An empty string is refused.
  • A file is at most 500 MiB. A bigger one raises InvalidParams before 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.
upload_file makes 3 calls: it requests a presigned upload permission, sends your bytes directly to the storage host, then confirms the upload with Cominty. download_file also fetches from a presigned URL. Your API key is only sent to the Cominty API, never to the storage host. Storage calls never follow redirects, and a presigned signature never appears in an error message. A storage refusal raises APIError with the storage reason as its message, for example EntityTooLarge. The multi-step flow only matters if you call the HTTP endpoints yourself. See Files and exports.
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.
namespace is required on create, get, update, and delete. It is a str of at most 128 characters, checked locally. path may have at most one folder segment. "notes/todo.md" is fine, "a/b/todo.md" isn’t. The SDK raises InvalidParams before any request. content may be an empty string. There’s no minimum length.
update is partial: only the fields you pass are sent. There is currently no way to clear content or purpose once set. The API silently ignores an explicit null (200, value unchanged), so the SDK raises InvalidParams locally for content=None or purpose=None instead of sending a request that looks like it succeeded but did nothing. version is required and must be the value from your last read of the file; a stale one raises ConflictError (409), a malformed one raises APIError (422). Treat version as an opaque token. Round-trip it, never parse or compare it.
delete is not idempotent: deleting an already-deleted (or never-existing) path raises NotFoundError (404) rather than succeeding again.
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’s user_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=False keeps the ones that are off. enabled=None returns every server, in API order. The API returns all of them, and the SDK filters on is_enabled.
  • name is the value to store in McpCapability.connections and McpScope.connections.
  • origin is "builtin" or "remote", and scope is "organization" or "user", today. Both are plain strings, and other values may appear.
  • requires_oauth_connect is True when 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.
  • scope is "private" (the key owner only) or "organization" (every member). The type is Scope.
  • update is partial: only the arguments you pass are sent. A capabilities object 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 (managed is True), set_models changes the chain of the whole organization for that agent. update and delete raise NotFoundError.
  • list() returns the built-in agents, the organization agents, and the key owner’s private agents. It returns AgentSummaryOut: no instructions, no models. Call get for the full AgentOut.
  • The legacy execution_options and memory_namespace are not exposed. Use capabilities (capabilities.memory sets the namespace).
  • delete is permanent. The API answers 204, and the SDK returns None.
▶ 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.
  • create makes 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-stream otherwise).
  • files is one file Path, a folder Path (its top-level files, hidden files skipped, no recursion), or an iterable of file Paths.
  • source.pid (a string) goes to every method here. source.id (an integer) goes to IndexedDocumentsCapability(source_ids=[...]) and IndexedDocumentsScope(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 a list().
  • add_files and remove_file request 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_file fetches a presigned URL valid 5 minutes, then the bytes. The whole file is held in memory.
  • archive is terminal. It removes the indexed documents and the files, and the source stays listed with status == "archived".
  • Only drag-and-drop sources can be created with a key.
Server limits: 100 drives per user (each 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 by chat.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).
A run raises StreamInterrupted (carrying .partial) if the server shuts down mid-stream.

One message at a time

A thread processes one message at a time. A message stays pending 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:
Use 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 | None
object
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: str
object
id · name · size · mimetype · origin (user | agent) · share_links: list[ShareLink] · url
id · created_at · last_accessed_at? · access_count · revoked · expires_at? · expired · protected · url
object
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: str
object
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: datetime
object
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: datetime
object
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: str
Literal
"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_reached

Capabilities

Capabilities replace disabled_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 raise InvalidParams 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 the Retry-After header), or None
  • reset_at → datetime (from reset_at / X-RateLimit-Reset), or None
Example messages:
  • 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.