datalysenpm
v3.1.3
Published
Datalyse Node.js APIs and native frontend client for remote browser sessions
Readme
Datalyse SDK
datalysenpm provides one Node.js Datalyse instance for the platform API, AI generation, agent text/voice WebSockets, remote browsers and MCP tools. Node.js 20+, ESM, CommonJS and TypeScript are supported. The separate datalysenpm/browser entry uses native fetch and WebSocket for remote browser sessions in web applications. It does not capture a microphone or bundle the Node.js AI, agent and MCP clients into the web entry.
Install
npm install datalysenpmimport Datalyse from 'datalysenpm';
const datalyse = new Datalyse({apiKey: process.env.DATALYSE_API_TOKEN});CommonJS: const {Datalyse} = require('datalysenpm').
One instance, three namespaces
| Access | Service | Use |
| --- | --- | --- |
| datalyse.to(path, input, options) | https://api.datalyse.io | General platform API |
| datalyse.ai.to(path, input, options) | https://ai.datalyse.io | AI API, including legacy AI routes |
| datalyse.ai.models, .chat, .audio, .agents, etc. | https://ai.datalyse.io | Typed AI helpers and WebSockets |
| datalyse.mcp.tools | https://mcp.datalyse.io/mcp | Discover and explicitly execute MCP tools |
| datalyse.call(operationId, input, options) | Declared OpenAPI server | Any documented operation |
All namespaces share apiKey. Set restBaseURL for a compatible platform API origin, baseURL for an AI origin or an /api/ai/v1 base, and mcpURL for an MCP endpoint. Each override affects only its service. The agent URL defaults to wss://ai.datalyse.io and can be set with agentWebSocketURL. Public agent sessions use an agent ID instead of a company API token. Constructing the client does not open connections. Redirects are rejected.
General REST and generated operations
const result = await datalyse.call('entities_get_schemas', {body: {}});
console.log(result);
console.log(datalyse.operations); // operationId, method, path from the contract
// Equivalent explicit platform request:
await datalyse.to('/api/1.0/entities/schemas.json', {
method: 'POST', auth: 'body', body: {},
});
// Explicit AI request:
const models = await datalyse.ai.to('/api/ai/v1/models');call(operationId, input, options) resolves parameters, token placement, schemas and the destination from the generated devs/openapi.json snapshot included in the package. Input supports path, query, header (documented header parameters), headers, body, mediaType, and response. Required parameters are checked before sending. Use Node's FormData/Blob for multipart files; fetch generates the boundary. datalyse.ai.call and datalyse.ai.operations are limited to operations whose server is the AI service. The old voice catalog and agent administration stay on the platform API according to their contracts.
to accepts full root-relative paths. They never switch services: a known route belonging to the other namespace is rejected before sending. For raw requests, choose the endpoint's documented authentication placement: body, query, bearer (default) or none. Absolute URLs are rejected. response: 'auto' returns parsed JSON, text, an SSE async iterator, or a Fetch Response for binary data. response: 'response' preserves headers and bytes. REST {status:'error'} results throw DatalyseAPIError even when HTTP status is 200.
New operations appear in datalyse.operations after regenerating the portal contract and packaging a new SDK snapshot. The SDK does not fetch or regenerate contracts at runtime. Undocumented routes can be used through the appropriate namespace's to method.
MCP tools
try {
const page = await datalyse.mcp.tools.list();
console.log(page.tools, page.nextCursor);
// Inspect a tool's name and inputSchema before explicitly calling it:
// const result = await datalyse.mcp.tools.call({name: 'TOOL_NAME', arguments: {}});
} finally {
datalyse.mcp.close();
}The first explicit MCP operation initializes the connection using Streamable HTTP with Bearer authentication. JSON and SSE responses, protocol negotiation and optional session headers are supported. Pass a returned nextCursor to tools.list({cursor}) to fetch the next page. tools.call({name, arguments}, options) executes only that requested tool; tools returned by AI models are never executed automatically. A tool result with isError: true remains a tool result for the caller to handle. JSON-RPC and HTTP failures throw DatalyseAPIError.
Use mcp.initialize(options) to negotiate explicitly, or mcpAuth: 'query' only for an endpoint documented to require a token query parameter. mcp.close() aborts pending local requests and closes this namespace; it does not undo an accepted remote tool action. The browser configuration URL https://mcp.datalyse.io/?token=… remains separate from the SDK's /mcp transport endpoint.
AI generation
const {data: models} = await datalyse.ai.models.list();
const model = models.find(item => item.datalyse_model === process.env.DATALYSE_MODEL);
if (!model?.capabilities.llm) throw new Error('Select an available LLM model.');
console.log(model.datalyse_model, model.pricing);
const result = await datalyse.ai.chat.completions.create({
model: model.datalyse_model, messages: [{role: 'user', content: 'Write a short welcome.'}],
});
console.log(result.choices[0].message.content, result.cost_credits);The AI namespace includes: models, chat.completions, embeddings, audio.voices, audio.transcriptions, audio.speech, images.generate, audio.generations, videos, lipsync, jobs, realtime, and remote browser automation. SDK_OPERATIONS lists the specialized AI methods; openapi.json and TypeScript declarations describe their contracts.
Await streaming requests before iterating. Chat chunks use choices[].delta; usage and credits can arrive separately. Media SSE may end with response.completed instead of DONE. Consume the final result. A progress stream does not imply partial media or native realtime: inspect the chosen model's capabilities.
TTS and job content return a Fetch Response:
import {Readable} from 'node:stream';
import {pipeline} from 'node:stream/promises';
import {createWriteStream} from 'node:fs';
const audio = await datalyse.ai.audio.speech.create({
model: process.env.DATALYSE_TTS_MODEL, voice: process.env.DATALYSE_VOICE_ID,
input: 'Hello.', response_format: 'mp3',
});
await pipeline(Readable.fromWeb(audio.body), createWriteStream('reply.mp3'));
console.log(audio.headers.get('x-cost-credits'));Select a compatible voice with datalyse.ai.audio.voices.list({model}). toFile(bytesOrStream,{filename,mimeType}) accepts a Blob, Buffer, typed array or async iterable, not a path. jobs.wait(id,{signal,timeoutMs,intervalMs}) polls only when explicitly called.
Prices and cost_credits are already customer credits. Display euros as credits ×4.02; do not apply that conversion to request values or balances. Missing usage is not zero consumption.
Remote browsers in Node.js and web applications
Datalyse hosts browser sessions in E2B Desktop with Chrome. Clients authenticate to Datalyse; provider credentials remain in the backend. The web entry controls the remote session, not the visitor's local browser.
Node.js uses datalyse.ai.browser (the root datalyse.browser alias remains available):
const browser = datalyse.ai.browser;
console.log(await browser.config()); // pricing in credits and current limits
const session = await browser.sessions.create({
url: 'https://example.com', allowed_origins: ['https://example.com'],
viewport: {width: 1280, height: 720},
});
try {
const result = await browser.sessions.actions(session.id, {
action: {type: 'extract', selector: 'h1'},
});
console.log(result.session.url, result.snapshot.text, result.cost_credits);
const screenshot = await browser.sessions.screenshot(session.id);
// screenshot is a Fetch Response with image/jpeg bytes, not JSON.
await import('node:fs/promises').then(fs => screenshot.arrayBuffer()
.then(bytes => fs.writeFile('browser.jpg', new Uint8Array(bytes))));
} finally {
await browser.sessions.delete(session.id);
}BrowserSession does not guarantee a viewport property. Keep the requested dimensions or use frame.image.width and frame.image.height. Actions return {session, snapshot, cost_credits, currency}. A session continues consuming browser time until its owner closes it or it expires, even after its viewer disconnects.
Persistent browser profiles and document state
The Node and web clients expose the same profiles.list, create, retrieve, update, delete and open methods. Profiles belong to the authenticated company. Additional assignments explicitly share one Chrome profile; only one session can use the profile at a time. Assignments accept agent, superagent, skill or crm_superagent with an id. Existing internal assignments can also have type project.
const profile = await browser.profiles.create({
name: 'CRM demonstration',
assignments: [{type: 'crm_superagent', id: crmSuperagentId}],
});
const session = await browser.profiles.open(profile.id, {url: 'https://example.com'});
// The equivalent session creation selects exactly one profile or assignment:
// await browser.sessions.create({profile_id: profile.id});
// await browser.sessions.create({assignment: {type: 'crm_superagent', id: crmSuperagentId}});
await browser.profiles.update(profile.id, {
name: 'Renamed demonstration', assignments: profile.assignments.filter(item => item.type !== 'project'), version: profile.version,
});Updates require the current version, name and full assignments list. allowed_origins is set on creation and cannot be changed by an update. Closing a session preserves its persistent profile; deleting the profile permanently removes its desktop. Reopening does not replay actions and produces a new session: create new viewer shares for it.
session.profile_id identifies a persistent profile when present. session.navigation describes the current main document: state is loading, ready, blocked, challenge or error; http_status is a number or null, and reason is an optional sanitized code. A ready session can still show a blocked document. Preserve its image and human control instead of treating an external block as expiration. The SDK does not retry navigation, opening or mutations automatically.
Recording requires a usable document. Pause with sessions.recording(id, {enabled: false}); resume the same demonstration with {enabled: true, reset: false} to retain existing steps and parameters. Omitting reset starts a fresh demonstration. Recording a blocked or unresolved document returns the recoverable browser_page_not_ready error.
Native web entry
import {createBrowserClient} from 'datalysenpm/browser';
// Direct module URL without a bundler: /devs/sdk/browser.mjs
const browser = createBrowserClient({baseURL: 'https://ai.datalyse.io'});createBrowserClient({apiKey?, baseURL?, timeoutMs?, fetch?, WebSocket?}) exposes baseURL, config, sessions, profiles, skills, and viewerURL. It has no Node.js imports or global Buffer requirement. The default timeout is 60000 ms. Every browser request and WebSocket requires the real Datalyse API token: provide the authenticated user's token from application memory and never embed a company token in distributed source. Share tokens are not accepted for authentication.
// apiKey is the signed-in user's real API token, kept in memory.
const browser = createBrowserClient({apiKey});
const socket = browser.sessions.connect(sessionId, {signal});
try {
await socket.ready; // the server has authenticated the API token
await socket.send({type: 'screenshot'});
for await (const event of socket) {
if (event.type === 'error') throw new Error(event.error.message);
if (event.type === 'frame') {
image.src = 'data:' + event.image.mime_type + ';base64,' + event.image.data;
image.width = event.image.width;
image.height = event.image.height;
}
}
} finally {
socket.close();
const {code, reason, wasClean} = await socket.closed;
console.log({code, reason, wasClean});
}Frames are JSON events: {type:'frame', image:{mime_type:'image/jpeg', data:'BASE64', width, height}}. The SDK sends authenticate as the first WebSocket message, always using the API token; credentials never enter the socket URL. close(), abort and iterator cleanup release the local connection. closed resolves with {code, reason, wasClean}. There is no automatic reconnect or action replay.
viewerURL({sessionId}) returns the Datalyse viewer URL; the viewer, like every browser request, requires the real API token. sessions.shares.* is deprecated: share tokens no longer authenticate. Send takeover with mode:'human' and wait for a session.updated event confirming human mode before sending an action.
Skills, recording and cleanup
skills.list/create/retrieve/update/delete manage saved action sequences. sessions.recording(id,{enabled:true}) starts recording; stop it with enabled:false, then call sessions.skills.createFromSession(id,{name}). Typed values become parameters; supply secret values only when replaying.
const skill = await datalyse.ai.browser.skills.create({
name: 'Read page heading', parameters: [],
allowed_origins: ['https://example.com'],
steps: [{type: 'extract', selector: 'h1'}],
});
// session is an open session restricted to the skill's allowed origins.
const events = await datalyse.ai.browser.sessions.run(session.id, {
skill_id: skill.id, variables: {}, stream: true,
});
for await (const event of events) {
if (event.type === 'error') throw new Error(event.error.message);
if (event.type === 'run.completed') console.log(event.response);
}Deterministic skills require no model. Instruction runs require model and instruction. stream:true returns an async iterator after awaiting sessions.run; otherwise it returns a BrowserRun. Browser time and LLM tokens are billed separately according to the published credit rates. Close sessions in finally, revoke shares when access ends, and remove temporary demonstration skills when no longer needed. examples/browser.mjs records an explicit demonstration; examples/browser-session.mjs exercises REST/JPEG/skills with cleanup, and examples/browser-viewer.mjs is an inert web module activated by your application.
Two AI WebSocket protocols
| Connection | Authentication | Envelope | Audio |
| --- | --- | --- | --- |
| datalyse.ai.realtime.connect() | session.authenticate + company token | {type,...} | PCM input for ASR; JSON/base64 output |
| datalyse.ai.agents.connect({agentId}) | register_session + public agent ID | {event,payload} | Raw PCM input; framed binary output |
These protocols are separate. Agent registration does not start a voice call. Chat/voice generation starts through explicit startChat(), startVoice() or corresponding sent events. The SDK never captures a microphone by itself.
Agent text
const agent = datalyse.ai.agents.connect({agentId: process.env.DATALYSE_AGENT_ID});
try {
await agent.ready;
await agent.startChat();
await agent.sendText('Hello');
for await (const event of agent) {
if (event.event === 'ia_message') console.log(event.payload.text);
if (event.event === 'chat_error') console.error(event.payload);
}
} finally { agent.close(); }examples/agent-chat.mjs is an interactive terminal client. EOF/Ctrl+C closes the socket. A configured welcome message can arrive after startChat(); the first message need not answer the next user prompt.
Agent voice
await agent.ready;
const ready = await agent.startVoice();
console.log(ready.audio);
await agent.sendAudio(pcmBuffer);
await agent.sendAudioStream(pcmStream, {signal});
await agent.textToVoice('Explain this in one sentence.');
await agent.stopVoice();stopVoice() waits for the server to confirm the end of the voice session. A new startVoice() waits for that confirmation too; late setup events cannot restart a stopped session.
Input: signed PCM16 little endian, mono, 16000 Hz, without WAV headers. sendAudio accepts at most 65536 bytes. sendAudioStream accepts Node Readable/async iterable, splits chunks and preserves samples across odd byte boundaries. The source controls realtime pacing. On abort it destroys a Node Readable to release pending reads. Socket closure also cancels pending streams. Your application must terminate external capture processes.
Consume output concurrently. voice_ready reports the output rate. The SDK requests the PCM input path; it does not negotiate Opus input. Server frames contain [type,flags,generation_hi,generation_lo] followed by audio. The decoded audio event exposes a Buffer, encoding, sampleRate, generation, flags and backchannel. Type 1 is PCM16, type 2 is an Opus packet; no decoder or speaker driver is bundled.
Handle tts_pause, tts_resume and tts_cancel in your player. Flush queued audio when generation changes. Agent text arrives as ia_message; recognized speech as user_transcript. textToVoice sends text through the active agent's LLM and speech synthesis. Voice setup preserves server validation/balance errors. muteInput/muteOutput map to mute_asr/mute_tts.
Explicit microphone example
Linux prerequisites: Node.js 20+, ALSA arecord and a working input device. Optional playback requires aplay; use headphones to avoid feedback.
DATALYSE_AGENT_ID=YOUR_AGENT_ID node node_modules/datalysenpm/examples/agent-microphone.mjs --microphone
# Optional speaker output:
DATALYSE_AGENT_ID=YOUR_AGENT_ID node node_modules/datalysenpm/examples/agent-microphone.mjs --microphone --playbackThe example spawns arecord only after voice setup and the explicit flag. Responses are saved in separate PCM files per generation, with their rate in the filename. It logs text/transcripts. Ctrl+C closes the socket, destroys input and terminates capture/playback. Replace arecord with another Node Readable supplying the same PCM format if needed.
AI realtime and LLM-to-TTS
const socket = datalyse.ai.realtime.connect({signal});
await socket.ready;
await socket.send({type:'session.create',kind:'tts',request:{
model:process.env.DATALYSE_TTS_MODEL,voice:process.env.DATALYSE_VOICE_ID,
sample_rate:16000,response_format:'pcm',
}});
// Wait for session.created in the event iterator, then:
await socket.send({type:'text.append',text:'Hello.'});
await socket.send({type:'text.commit'});examples/llm-to-tts.mjs streams LLM text over HTTP SSE into a separate TTS realtime session. Configure DATALYSE_API_TOKEN, DATALYSE_MODEL, DATALYSE_TTS_MODEL and DATALYSE_VOICE_ID. Buffered models emit audio after text.commit; incremental delivery depends on the model. One realtime connection allows one active operation. request.cancel, session.finish and close() end their respective lifecycles.
Errors and cleanup
HTTP methods accept {signal,timeoutMs} last; default timeout is 60000 ms. DatalyseAPIError exposes message/code/status/type/param/requestId and safe headers. Tokens are redacted. Generations and writes are never automatically retried.
Socket iterators have bounded queues. Read promptly; backpressure rejects excessive input. close(), iterator return() and abort release timers/readers. Agent sockets heartbeat every 15 seconds and do not reconnect automatically. API HTTP/SSE payloads are capped at 64 MiB, MCP responses at 16 MiB and pending agent events at 16 MiB.
Compatibility with 0.1, 0.2 and 0.3
DatalyseAI remains an alias of Datalyse, including the default import. Root-level AI helpers (datalyse.models, .chat, .agents, etc.) remain available; use datalyse.ai for new integrations. The legacy DatalyseREST export and datalyse.rest accessor are retained. datalyse.rest.call still routes all documented operations, including AI, while restBaseURL now overrides only the platform origin. Migrate datalyse.rest.call(...) to datalyse.call(...), or to datalyse.ai.call(...) for AI-only code. Raw new requests use datalyse.to or datalyse.ai.to explicitly.
Development
npm install
npm test
npm run test:types
npm packprepack first runs sync:openapi, which copies the repository’s current DEVwebsite/devs/openapi.json into both packaged contract snapshots, then runs mocked protocol and ESM/CommonJS type checks. Run packaging from the repository SDK directory after the backend has generated the contract. Synchronization does not edit the backend or source contract. Tests never generate media or capture a microphone. Packaging does not publish to npm. The generated portal contract is the source of truth; no endpoint count or service split is hardcoded.
Browser transport (since 1.4.2)
Node and web connections keep receiving frames after recoverable action errors once authenticated. closed resolves to { code, reason, wasClean }; the reason redacts credentials. HTTP actions are never retried automatically. After an ambiguous result, retrieve the same authorized session and inspect its screenshot before deciding on another action.
Abort signals cancel JSON and binary readers. Calling return() releases an SSE stream even before its first next(). Browser runs finish on run.completed, without requiring the server to close the connection.
License
Copyright (c) 2026 Datalyse Group Ltd. Proprietary licence: you may use this SDK only to integrate your applications with Datalyse products and services. See LICENSE.
