@cominty-ai/sdk, types included:
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
userIdis mandatory, validated at construction (^user_[A-Za-z0-9]{20,}$). Chat and thread requests send it. Memory requests do not. Resource methods never takeuserId.- A missing key or user id, a malformed user id, or a browser without
dangerouslyAllowBrowserthrows a plainError, not aComintyError. 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 throwsAPIConnectionError(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 senduserIdeither.
Resources
client.chat
messageis at most 30,000 characters.fileIdsholds at most 5 ids.startreturns aStartedChat, a run whose.threadis always set.threadCapabilitiesis optional and only accepted here. Passing it tosendthrowsInvalidParams.messageScopeis accepted by both. See Capabilities.maxStepscaps the tool rounds of one message. SeemaxStepsbelow and Limiting agent steps.sendreturns anAssistantRunwhose.threadisundefined. You hold thethreadId, 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 throw
InvalidParamsbefore any request is sent, naming every bad argument at once. signalcancels the request and the run’s stream. It only closes your connection: the agent keeps running on the server. To stop the agent, callcancel.
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.
filenameis 1 to 255 characters and cannot contain/.mimetypeis optional. Left out, it is theBlob’s type, then a guess from the file name, thenapplication/octet-stream.- A file is at most 500 MiB (
UPLOAD_MAX_BYTES). A bigger one throwsInvalidParamsbefore anything is sent. - The client’s
timeoutapplies 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.
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.
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.
list()with no filter returns every file visible to the API key. Passnamespaceto keep one namespace. Without it, the same path can appear once per namespace, so key a file by both.createthrowsConflictErrorif the path already exists in the namespace.purposeis 1 to 100 characters once trimmed, andcontentis at most 1 MiB. The API checks these and answers with anAPIError.Thread,ThreadSummary, andMessagedo 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: falsekeeps the ones that are off.enabled: nullreturns every server, in API order. The API returns all of them, and the SDK filters onis_enabled.nameis the value to store inmcp.connections, inthreadCapabilities,messageScope, or an agent’scapabilities.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 throws
ServerError.
client.agents
Manages custom agents and their model chain. Concepts:
Custom agents.
scopeis'private'(the default, the key owner only) or'organization'(every member). The type isScope.updateis partial: only the fields you pass are sent. Acapabilitiesobject is merged capability by capability: each one you pass replaces the stored one, the others stay.- An agent’s
capabilitiescome back in the camelCase shape you pass, so a read can go straight back intoupdate. 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 (
managedistrue),setModelschanges the chain of the whole organization for that agent.updateanddeletethrowNotFoundError. list()returns the built-in agents, the organization agents, and the key owner’s private agents. It returnsAgentSummary: noinstructions, nomodels. Callgetfor the fullAgentDetail.- Use
capabilities(capabilities.memorysets the namespace), not the legacyexecution_optionsandmemory_namespacefields of a read. deleteis permanent. The API answers 204, and the SDK resolves withundefined.
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
SkillFilesobject: the skill’s files by/-separated path, withSKILL.mdat 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).
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. If a step fails, the source is not created.source.pid(a string) goes to every method here.source.id(a number) goes toindexedDocuments.sourceIdsinthreadCapabilities,messageScope, or an agent’scapabilities.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().addFilesandremoveFilerequest 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.downloadFilefetches a presigned URL valid 5 minutes, then the bytes. The whole file is held in memory.waitSyncedreads the source everyinterval(default5000) milliseconds, for at mosttimeout(default600000, 10 minutes) milliseconds. A timeout does not stop the sync: wait again to keep following it.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 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 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 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:
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 throwsAPIConnectionError. 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:Data types
All response types are plain objects withsnake_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 | nullobject
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[] · urlid · created_at · last_accessed_at · access_count · revoked ·
expires_at · expired · protected · urlobject
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: stringobject
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: stringobject
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: stringobject
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: stringstring 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_reachedCapabilities
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 callchat.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
resultevent is not the end of the stream. More events can follow it. The stream ends when the final message arrives, which is when yourfor awaitloop exits. event.data.costholdstotal,input_cost, andoutput_costas decimal strings, plus token counts. Keep them as strings, or use a decimal library: converting tonumberloses precision when you add them up.
Errors
Every error the SDK throws while talking to the API extendsComintyError. All
classes are exported, and error.name is the class name.
ComintyError:
- A configuration mistake in
new Cominty()throws a plainError. - Aborting your own
AbortSignalthrows the standardAbortError.
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'|nullretryAfter→ seconds (from theRetry-Afterheader), ornullresetAt→ aDate(fromreset_at/X-RateLimit-Reset), ornull
'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.