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. Every name on this page is exported from @cominty-ai/sdk, types included:
Arguments you pass use camelCase (agentId, fileIds). Response objects keep the API’s snake_case field names (thread_id, created_at). Every method takes an optional signal (an AbortSignal) in its last argument.

Client

  • userId 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 userId.
  • A missing key or user id, a malformed user id, or a browser without dangerouslyAllowBrowser throws a plain Error, not a ComintyError. These are configuration mistakes to fix.
  • The client holds no persistent connection. client.close() aborts every request and stream in flight. await using client = new Cominty() calls it when the client goes out of scope. A closed client stays closed: every later call throws APIConnectionError (client closed). Create a new client to continue.
  • Properties: client.userId, client.baseUrl.
  • Resources: client.chat, client.threads, client.memory, client.mcp, client.agents, client.skills, client.sources, client.documents. The last five do not send userId either.
See Configuration for the full settings table and resolution order.

Resources

client.chat

  • message is at most 30,000 characters. fileIds holds at most 5 ids.
  • start returns a StartedChat, a run whose .thread is always set. threadCapabilities is optional and only accepted here. Passing it to send throws InvalidParams. messageScope is accepted by both. See Capabilities.
  • maxSteps caps the tool rounds of one message. See maxSteps below and Limiting agent steps.
  • send returns an AssistantRun whose .thread is undefined. You hold the threadId, 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 throw InvalidParams before any request is sent, naming every bad argument at once.
  • signal cancels the request and the run’s stream. It only closes your connection: the agent keeps running on the server. To stop the agent, call cancel.
sourceIds, documentIds, and disabledTools (on start and send) and memoryNamespace (on start) still work but are deprecated. Use messageScope and threadCapabilities.memory. Mixing them with the new arguments throws InvalidParams: messageScope with any of the first three, or memoryNamespace with threadCapabilities.memory.

Stop a message

cancel stops an assistant message that is still being generated. It resolves with the message, its 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.

Export a message: export

export(messageId, { format }) renders a message to a document and returns the raw bytes. format is 'pdf' or 'docx' (type ExportFormat). The server sends no file name. Pick one when you write the bytes.

Upload and download files

uploadFile registers a file and resolves with a ConversationFile. Pass its id in fileIds on start and send, at most 5 files per message.
  • filename is 1 to 255 characters and cannot contain /.
  • mimetype is optional. Left out, it is the Blob’s type, then a guess from the file name, then application/octet-stream.
  • A file is at most 500 MiB (UPLOAD_MAX_BYTES). A bigger one throws InvalidParams before anything is sent.
  • The client’s timeout applies to each of the three requests of an upload.
downloadFile(fileId) returns the bytes of any conversation file, including the ones the agent produced. An empty fileId throws InvalidParams. An unknown file throws NotFoundError. The call is not scoped to the client’s userId: any file created through the API in your organization can be downloaded. The whole file is held in memory.
uploadFile makes 3 calls: it requests a presigned upload permission, sends your bytes directly to the storage host, then confirms the upload with Cominty. downloadFile also fetches from a presigned URL. Your API key is only sent to the Cominty API, never to the storage host. Error messages leave out the presigned URL’s signature. A storage refusal, or a redirect, throws the APIError for its status, with the storage reason as detail, for example EntityTooLarge. An answer the SDK cannot use (no ETag, no presigned key) throws SDKError. 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.

maxSteps

maxSteps is an optional number on start and send.
The value is not stored and not returned. Keep it yourself if you need it.

client.threads

Scoped to the client’s userId automatically. limit defaults to 50, at most 100. page is zero-based. To read everything, request pages until one comes back shorter than limit.

client.memory

Scoped to a caller-chosen namespace, shared by the API key. Not scoped to userId. client.memory does not send userId. Concepts: Memory namespaces.
namespace is required on create, get, update, and delete. It is a string of at most 128 characters, checked locally, and never trimmed. path may have at most one folder segment. "notes/todo.md" is fine, "a/b/todo.md" isn’t. The SDK throws 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, and at least one of content or purpose is required. 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 throws InvalidParams locally for content: null or purpose: null 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 throws ConflictError (409). A malformed one throws 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 throws NotFoundError (404) rather than succeeding again.
  • list() with no filter returns every file visible to the API key. Pass namespace to keep one namespace. Without it, the same path can appear once per namespace, so key a file by both.
  • create throws ConflictError if the path already exists in the namespace.
  • purpose is 1 to 100 characters once trimmed, and content is at most 1 MiB. The API checks these and answers with an APIError.
  • Thread, ThreadSummary, and Message do not expose the thread’s namespace.

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: null 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 mcp.connections, in threadCapabilities, messageScope, or an agent’s capabilities.
  • 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 throws ServerError.

client.agents

Manages custom agents and their model chain. Concepts: Custom agents.
  • scope is 'private' (the default, the key owner only) or 'organization' (every member). The type is Scope.
  • update is partial: only the fields you pass are sent. A capabilities object is merged capability by capability: each one you pass replaces the stored one, the others stay.
  • An agent’s capabilities come back in the camelCase shape you pass, so a read can go straight back into update. A capability this SDK version does not know is left out of the read, and the API keeps it on update.
  • create({ modelIds }) sets the model chain. Leave it out for the organization’s default chain. The chain is a failover order.
  • On a built-in agent (managed is true), setModels changes the chain of the whole organization for that agent. update and delete throw NotFoundError.
  • list() returns the built-in agents, the organization agents, and the key owner’s private agents. It returns AgentSummary: no instructions, no models. Call get for the full AgentDetail.
  • Use capabilities (capabilities.memory sets the namespace), not the legacy execution_options and memory_namespace fields of a read.
  • delete is permanent. The API answers 204, and the SDK resolves with undefined.

client.skills

Manages custom skills. Concepts: Skills. file is a SkillArchive, one of:
  • a zip archive (Blob, File, ArrayBuffer, Uint8Array), sent as it is;
  • a SkillFiles object: the skill’s files by /-separated path, with SKILL.md at the root. Values are strings (written as UTF-8) or bytes. The SDK zips them in memory and leaves out entries with a path part starting with ..
The skill takes the key’s surface: there is no surface argument. delete is permanent, every version included.

client.sources

Turns files into an indexed document source. Concepts: Sources. files is one SourceFileInput or an array of them: a File (its name is used), or a NamedFile { filename, content, mimetype? } whose content is a Blob, ArrayBuffer, or Uint8Array. In Node, wrap a file’s bytes in a File: new File([await readFile(path)], name).
  • 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. If a step fails, the source is not created.
  • source.pid (a string) goes to every method here. source.id (a number) goes to indexedDocuments.sourceIds in threadCapabilities, messageScope, or an agent’s capabilities.
  • 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().
  • addFiles and removeFile 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.
  • downloadFile fetches a presigned URL valid 5 minutes, then the bytes. The whole file is held in memory.
  • waitSynced reads the source every interval (default 5000) milliseconds, for at most timeout (default 600000, 10 minutes) milliseconds. A timeout does not stop the sync: wait again to keep following it.
  • 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 userId. See Sources.

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 throws InvalidParams before any request. An unknown id throws 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 throws StreamInterrupted (carrying .partial, the message as far as it got) if the server shuts down mid-stream. A dropped connection throws APIConnectionError. A run that ends with status: 'failed' does not throw: check status on run.result().

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 throws 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:
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.

Resume a dropped stream

If the connection drops mid-run, the stream throws APIConnectionError. The run is still in progress on the server. chat.stream(messageId, { lastEventId }) opens a new stream that starts after that event:
chat.stream makes no request until the run is consumed. It is also how you attach to a run from another process: store run.messageId, then open the stream wherever you need it.

Relay progress to a browser

The SDK is server-side only, because it holds your API key. To show live progress in a web app, read the run on your server and relay the events, for example as server-sent events:
Relay only what your UI needs. Events can include tool names, model names, and cost, which you may not want to show to end users. If the browser disconnects, the message keeps running: cancel it if your UI means “stop”.

Data types

All response types are plain objects with snake_case fields. The API may return fields not listed here, and they are kept on the object, so the types are forward-compatible. Timestamps are ISO-8601 strings.
ThreadSummary + messages
Everything in ThreadSummary, plus messages: Message[].
object
id: string · name: string · created_at: string · live: boolean · agent: { id: string; name: string } · starred: boolean · project_id?: string | null
object
id: string · thread_id: string · role: 'user' | 'assistant' · content: string · questions: Question[] | null · live: boolean · status: 'pending' | 'running' | 'success' | 'failed' | 'cancelled' · events: Record<string, unknown>[] | null (the raw stored log, not the typed stream events) · structured_output: Record<string, unknown> | null · files: ConversationFile[]
object
prompt: string · options: string[]. Answer with one of the options, or any free text, through chat.send.
object
id · name · size (bytes) · mimetype · origin ('user' | 'agent') · share_links: 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: string · namespace: string · purpose: string · content: string · created_at: string · updated_at: string · version: string (opaque, see client.memory)
object
Returned by memory.list. Everything in MemoryFile except content.
object
Returned by mcp.list. name: string (the value for connections) · title: string · description: string · is_enabled: boolean · origin: string · scope: string · requires_oauth_connect: boolean · connection_status? · icon_uri? · mcp_url? · owner_id? · created_at? · updated_at?
object
Returned by agents.list. id: string · name: string · description: string | null · capabilities: AgentCapabilities | null (camelCase, the shape you pass) · managed: boolean (true for built-in agents) · scope: Scope · owner: string (the userId to chat with over the key’s sources) · execution_options and memory_namespace (legacy, use capabilities).
AgentSummary + details
Returned by agents.get, create, update, and setModels. Everything in AgentSummary, plus instructions: string | null · allow_execution_options_override: boolean · models: LlmModel[] (the chain, in failover order).
object
Returned by agents.listModels. id: string · name: string
object
Returned by skills.list, create, update, and uploadVersion. id: string (the value for skills.include) · display_name: string · active: boolean · scope: Scope | null (null for a built-in skill) · source: 'cominty' | 'custom' · frontmatter (name, description, license, compatibility, metadata, allowed_tools, parsed by the server from SKILL.md) · created_at: string · updated_at: string
object
Returned by the client.sources methods that return a source. id: number (for sourceIds) · pid: string (for every client.sources method) · name: string · scope: Scope · created_by: string · store: SourceStore · status: SourceStatus · size: number · embedded_size: number · documents_synced: number · documents_failed: number · oversized_documents: number · documents_embedded: number · last_synced_at: string | null · archived_at: string | null · created_at: string · config (values.drive_id names the drive behind the source)
object
A file of a source. id: string · name: string · size: number · mimetype: string · created_at: string · updated_at: string
object
Returned by documents.get. document_id: string · source_id: number · source_pid: string · source_type: SourceStore · document_name: string · document_uri: string | null · document_extension: string · document_mime_type: string
string union
'private' (the key owner only) or 'organization' (every member). Used by agents, skills, and sources.
string union
'draganddrop', 'googledrive', or 'sharepoint'. An API key can only create draganddrop sources.
string union
pending · syncing · synced · error · out_of_sync · cancelling · pausing · paused · archived · disconnected · inoperative_connection · expired_connection · invalid_configuration · daily_quota_reached

Capabilities

Concepts and recipes: Capabilities and Use cases. Capabilities and scopes are plain objects with camelCase keys. The SDK sends them in snake_case.

AgentCapabilities

Every key is optional. A key you do not pass keeps the agent’s value. A key you pass replaces the agent’s value entirely. activation is required on every capability. There are no shortcuts: web: true and web: 'never' throw InvalidParams.

MessageScope

Every key is optional. enabled: boolean 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 array. sourceIds are numbers, the other ids strings.

Memory

memory: { activation: 'always', namespace } attaches the thread to a memory bag. The namespace is required and is 1 to 128 characters. memory: { 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.

Validation errors

Capabilities are plain objects, so the SDK checks them when you call chat.start or chat.send, before any request. Every problem is named in one InvalidParams. 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.

Streamed events

Events are a discriminated union (AnyEvent) that you narrow on event.name. Call isKnownEvent(event) first: it skips event types this SDK version does not know, and lets TypeScript type event.data in each branch. Every event has id: string, correlation_id: number, at: string, status: 'running' | 'success' | 'failed' | 'error', and a name. A step emits a running event, then one with a final status, both with the same correlation_id. Use isTerminalStatus(status) and isFailureStatus(status) rather than comparing strings.
  • The result event is not the end of the stream. More events can follow it. The stream ends when the final message arrives, which is when your for await loop exits.
  • event.data.cost holds total, input_cost, and output_cost as decimal strings, plus token counts. Keep them as strings, or use a decimal library: converting to number loses precision when you add them up.

Errors

Every error the SDK throws while talking to the API extends ComintyError. All classes are exported, and error.name is the class name.
Two kinds of error are not a ComintyError:
  • A configuration mistake in new Cominty() throws a plain Error.
  • Aborting your own AbortSignal throws the standard AbortError.
InvalidParams.errors lists every problem at once. Each entry has param, message, and, where useful, the offending input. The SDK never retries on its own. Sending a message is not idempotent: retrying a chat.start that reached the server would start a second run. Retry a ServerError or a RateLimitError with scope 'concurrency' after a delay. Re-read the file after a ConflictError on memory.update. Resume a stream after an APIConnectionError while streaming. Fix the request for an AuthError, PermissionError, NotFoundError, or InvalidParams.

RateLimitError

Turns the terse 429 into a clear, actionable message and exposes:
  • scope → 'organization' | 'user' | 'concurrency' | null
  • retryAfter → seconds (from the Retry-After header), or null
  • resetAt → a Date (from reset_at / X-RateLimit-Reset), or null
'concurrency' is transient: retry once an in-flight run finishes. 'organization' and 'user' mean a quota is exhausted, and an admin must raise the plan’s limit. Example messages:
  • Organization rate limit reached: your organization’s total request quota is exhausted. Ask an organization admin to raise your plan’s limit.
  • 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. Ask an organization admin to raise your plan’s limit.