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/sdk

Requires 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.

MethodDoesReturns
me()Identity and scopes the key resolves toIdentity
search(params)Cross-module semantic + keyword searchSearchResult
openapi()The raw OpenAPI 3.1 document for the APIRecord<string, unknown>
modules.list()List the workspace's modulesModule[]
modules.get(slug)A module's full schema (fields, views, references)ModuleSchema
modules.fields(slug)List a module's fieldsField[]
modules.views(slug)List a module's viewsArray<Record<string, unknown>>
records.list(slug, params?)List records — filtering, sorting, field selection, pagination{ records, total, page, pageSize }
records.get(slug, recordId)Get one recordCoherenceRecord
records.create(slug, body)Create a recordCoherenceRecord
records.update(slug, recordId, body)Partially update a recordCoherenceRecord
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) fieldsArray<Record<string, unknown>>
references.linkedIds(slug, refSlug, recordId)Linked record IDs for a reference fieldLinkedRecordIds
references.picker(slug, refSlug, params?)Search records available to linkReferencePicker
activity.workspace(params?)Workspace-wide activity feedActivity[]
activity.module(slug, params?)Activity feed for a moduleActivity[]
activity.record(slug, recordId, params?)Grouped activity feed for a recordActivity[]
agents.message(body)Send a message to an agent, await the final replyAgentResponse
agents.stream(body)Stream an agent run as typed eventsAsyncGenerator<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);
    }
  }
}
PropertyDescription
codeMachine-readable error code — not_found, validation_error, rate_limit_exceeded, …
statusCodeThe HTTP status
issuesPresent on validation errors — one entry per failed field
dataAdditional context on some errors
retryAfterSeconds from the Retry-After header, on 429 responses that send one
isRetryableGetter — 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
});
OptionTypeDefaultDescription
apiKeystringRequired. Your workspace API key (sk_live_...).
baseUrlstringhttps://api.getcoherence.io/v1Override the API origin.
timeoutMsnumber60000Per-request timeout. 0 disables it. Agent streams are exempt — they stay open for the length of the run.
fetchtypeof fetchNode's built-in fetchSupply 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