TypeScript SDK
The official Coherence SDK for TypeScript and JavaScript — fully typed, zero dependencies
@coherenceos/sdk is the official TypeScript / JavaScript client for the Coherence API. It wraps the same REST endpoints documented in this reference — records, modules, search, activity, and agents — with full types, zero runtime dependencies, and both ESM and CommonJS builds.
Install
npm install @coherenceos/sdkRequires Node 18+ — the SDK uses the built-in fetch and ships no runtime dependencies. Licensed MIT.
Quickstart
import { CoherenceClient } from '@coherenceos/sdk';
const coherence = new CoherenceClient({ apiKey: process.env.COHERENCE_API_KEY! });
// Who am I? (identity + scopes — a good key health check)
const me = await coherence.me();
// List records, with pagination preserved
const { records, total } = await coherence.records.list('contacts', { pageSize: 50 });
// Create a record — resolves to the record itself
const contact = await coherence.records.create('contacts', {
displayName: 'Jane Doe',
fields: { email: '[email protected]', status: 'active' },
});
// Cross-module semantic + keyword search
const hits = await coherence.search({ query: 'acme', limit: 10 });Authentication
Create an API key at Settings → API Keys in your workspace. Keys use the sk_live_ prefix and may be scoped — workspace:read, records:read, records:write, and agents:write. The SDK sends the key as Authorization: Bearer sk_live_... on every request.
Never ship an API key to the browser or commit it to source control. Construct the client on a server and read the key from an environment variable, as in the quickstart above.
A method whose scope your key lacks fails with a 403 and code: "forbidden". See Authentication for the full scope model.
API surface
Every method takes an optional trailing AbortSignal.
List-style responses are unwrapped for ergonomics — modules.list() hands you a Module[], not a { modules } envelope. records.list() is the exception: it returns the full payload so pagination is preserved.
| Method | Does | Returns |
|---|---|---|
me() | Identity and scopes the key resolves to | Identity |
search(params) | Cross-module semantic + keyword search | SearchResult |
openapi() | The raw OpenAPI 3.1 document for the API | Record<string, unknown> |
modules.list() | List the workspace's modules | Module[] |
modules.get(slug) | A module's full schema (fields, views, references) | ModuleSchema |
modules.fields(slug) | List a module's fields | Field[] |
modules.views(slug) | List a module's views | Array<Record<string, unknown>> |
records.list(slug, params?) | List records — filtering, sorting, field selection, pagination | { records, total, page, pageSize } |
records.get(slug, recordId) | Get one record | CoherenceRecord |
records.create(slug, body) | Create a record | CoherenceRecord |
records.update(slug, recordId, body) | Partially update a record | CoherenceRecord |
records.delete(slug, recordId) | Soft-delete a record (204) | void |
records.bulkUpdate(slug, body) | Bulk add/remove labels and types (max 100) | CoherenceRecord[] |
records.bulkDelete(slug, { recordIds }) | Bulk soft-delete records (max 100) | { deletedCount } |
references.list(slug) | List a module's reference (relationship) fields | Array<Record<string, unknown>> |
references.linkedIds(slug, refSlug, recordId) | Linked record IDs for a reference field | LinkedRecordIds |
references.picker(slug, refSlug, params?) | Search records available to link | ReferencePicker |
activity.workspace(params?) | Workspace-wide activity feed | Activity[] |
activity.module(slug, params?) | Activity feed for a module | Activity[] |
activity.record(slug, recordId, params?) | Grouped activity feed for a record | Activity[] |
agents.message(body) | Send a message to an agent, await the final reply | AgentResponse |
agents.stream(body) | Stream an agent run as typed events | AsyncGenerator<AgentStreamEvent> |
The package exports CoherenceClient, CoherenceError, SUPPORTED_OPERATIONS, and the types above.
This is the complete surface. Richer operations — sending email, drafting and scheduling outreach, creating reminders, posting to social — have no public REST endpoint; they run through agents.message / agents.stream, governed by your workspace's approval rules.
Agents
Send a natural-language message to a Coherence agent (Nash by default) and await its final answer. The agent runs its multi-step tool loop server-side.
const result = await coherence.agents.message({
message: 'How many open deals do we have over $50k, and who owns them?',
});
console.log(result.response); // final answer
console.log(result.toolCalls, result.durationMs);Pass an optional agentId to target a specific agent with its own system prompt.
Streaming
agents.stream() returns an async iterable of typed events, so you can render the answer token-by-token and surface tool activity as the run happens:
let answer = '';
for await (const event of coherence.agents.stream({ message: 'Summarize my pipeline' })) {
switch (event.type) {
case 'open':
// Stream established. event.agentId is the resolved agent, or null for the default.
break;
case 'token':
answer += event.text; // concatenate token chunks for the full answer
process.stdout.write(event.text);
break;
case 'tool_call':
console.error(`\n[${event.label}]`); // e.g. "Searching records"
break;
case 'done':
// Terminal success — same shape as the synchronous response.
console.error(`\n(${event.toolCalls} tools, ${event.durationMs}ms)`);
break;
case 'error':
throw new Error(event.message); // terminal failure
}
}The full answer is available two ways: accumulate token events, or read response from the final done event — they resolve to the same text. Under the hood this is the SSE mode of POST /agents/messages; the SDK parses the frames for you. See the Agents API for the wire format.
Error handling
Any non-2xx response throws a CoherenceError. Branch on the machine-readable .code rather than the human-readable message — codes are a stable, additive-only contract.
import { CoherenceClient, CoherenceError } from '@coherenceos/sdk';
try {
await coherence.records.get('contacts', 'missing-id');
} catch (err) {
if (err instanceof CoherenceError) {
switch (err.code) {
case 'not_found':
console.error('No such record');
break;
case 'validation_error':
console.error(err.issues); // one entry per failed field
break;
case 'rate_limit_exceeded':
console.error(`Retry after ${err.retryAfter}s`);
break;
default:
console.error(err.code, err.statusCode, err.message);
}
}
}| Property | Description |
|---|---|
code | Machine-readable error code — not_found, validation_error, rate_limit_exceeded, … |
statusCode | The HTTP status |
issues | Present on validation errors — one entry per failed field |
data | Additional context on some errors |
retryAfter | Seconds from the Retry-After header, on 429 responses that send one |
isRetryable | Getter — true for 429 and 5xx |
Retrying with backoff
isRetryable tells you when a retry is worth attempting; retryAfter tells you how long to wait when the server says so.
async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
for (let i = 0; ; i++) {
try {
return await fn();
} catch (err) {
if (!(err instanceof CoherenceError) || !err.isRetryable || i >= attempts - 1) throw err;
const waitMs = err.retryAfter ? err.retryAfter * 1000 : 2 ** i * 1000;
await new Promise((r) => setTimeout(r, waitMs));
}
}
}
const contacts = await withRetry(() => coherence.records.list('contacts'));See Rate Limits and the error code catalog.
Configuration
new CoherenceClient({
apiKey: 'sk_live_...',
baseUrl: 'https://api.getcoherence.io/v1', // default
timeoutMs: 60_000, // per-request; 0 disables. Streams are exempt.
fetch: customFetch, // optional custom fetch
});| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Required. Your workspace API key (sk_live_...). |
baseUrl | string | https://api.getcoherence.io/v1 | Override the API origin. |
timeoutMs | number | 60000 | Per-request timeout. 0 disables it. Agent streams are exempt — they stay open for the length of the run. |
fetch | typeof fetch | Node's built-in fetch | Supply your own fetch implementation (proxying, instrumentation, tests). |
Cancel any in-flight call by passing an AbortSignal as the last argument:
const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);
const records = await coherence.records.list('contacts', { pageSize: 100 }, controller.signal);The SDK cannot drift from the API. The client exports SUPPORTED_OPERATIONS — the exact set of (method, path) pairs it implements — and a contract test asserts that set matches the canonical OpenAPI spec. If the API gains or changes an endpoint, the SDK's test suite fails until the client is updated to match.
Related: API Overview | Authentication | Agents API | MCP Server