TypeScript SDK
Use the typed hlix client for workspace-scoped automation, projects, tasks, repository access, and activity.
@hlix/sdk is the first-party TypeScript client. Its request and response types come from the published OpenAPI contract, and it adds credentials, explicit workspace selection, typed errors, bounded retry, repository access, and activity.
Prerequisites
Section titled “Prerequisites”- Node.js 20 or later
- An hlix API key
- A workspace ID
- TypeScript is recommended, but the package also exposes ESM JavaScript
Install
Section titled “Install”npm install @hlix/sdkpnpm add @hlix/sdkyarn add @hlix/sdkbun add @hlix/sdkvp add @hlix/sdkPin the version in CI so a build never picks up a client you have not tested against:
npm install @hlix/sdk@3.0.0Create a client
Section titled “Create a client”import { createHlix, apiKeyCredential } from '@hlix/sdk'
const hlix = createHlix({ baseUrl: 'https://server.hlix.ai', credential: apiKeyCredential(process.env.HLIX_API_KEY!), organizationId: process.env.HLIX_WORKSPACE_ID!,})Set organizationId explicitly in server-side automation to select the Workspace for the authenticated user.
List projects and inspect a task
Section titled “List projects and inspect a task”Set TASK_ID to an existing task ID, then list projects and inspect that task:
import { createHlix, apiKeyCredential, HlixNotFoundError,} from '@hlix/sdk'
const hlix = createHlix({ baseUrl: process.env.HLIX_BASE_URL ?? 'https://server.hlix.ai', credential: apiKeyCredential(process.env.HLIX_API_KEY!), organizationId: process.env.HLIX_WORKSPACE_ID!,})
const projects = await hlix.projects.list()console.log(projects)const taskId = process.env.TASK_ID!
try { const task = await hlix.tasks.get(taskId) console.log(task)} catch (error) { if (error instanceof HlixNotFoundError) { console.error('Task is absent or not visible to this caller') } else { throw error }}Expected result: the project list is scoped to the selected workspace.
Watch a task
Section titled “Watch a task”const task = await hlix.tasks.get(taskId)
for await (const watch of hlix.live.task(task.projectId, taskId)) { console.log(watch.state, watch.task?.status)}watch.state is running until the task is over, then finished — or unmet, with a reason, when its cycle ended first. The loop ends on its own at that point.
Live: talk to a project, watch it work
Section titled “Live: talk to a project, watch it work”hlix.live uses the same live protocol as the dashboard. Set projectId to an existing project ID for these examples. Events are typed: event.type narrows to the event’s fields.
// One message to the project's team, answered as it is written.for await (const event of hlix.live.run({ threadId: `project:${projectId}`, text: 'What is left?' })) { if (event.type === 'TEXT_MESSAGE_CONTENT') process.stdout.write(event.delta) if (event.type === 'RUN_ERROR') console.error(event.message)}
// A project's state as it changes: its cycles, tasks, agents and new decisions (the board, read again every five seconds).for await (const { state } of hlix.live.project(projectId)) { console.log(state.project.status, state.tasks.length)}A conversation is named by its chat: project:<id>, its :cycle:<id> and :task:<id> forms, or workspace:<orgId>:user:<userId> for a person’s own assistant. live.run sends one turn, then yields the chat’s events until that turn ends (CUSTOM hlix.outcome for its own messageId: answered, stopped or failed, with a RUN_ERROR before a failed one). The chat is shared, so other people’s turns can appear in the same events: read your own answer from the run whose id is your messageId. A stream joined after the answer began opens with a MESSAGES_SNAPSHOT that already holds the words written so far under the id live-<messageId>-<n>.
live.follow(threadId) reads a chat without sending anything. It stays open across runs, including a run the project manager starts on its own, and reconnects with backoff after a dropped connection, resuming from the last frame it yielded so none repeats. It ends only when you abort its signal. A turn is sent once and never retried automatically: pass the same messageId if you retry a message yourself, so the server recognizes the same turn. live.send posts a turn without reading it, hlix.live.stop(threadId) stops the run in flight, live.answer(threadId, requestId, { approved }) answers an approval the agent asked for (CUSTOM hlix.approval_requested; a request someone already answered is a HlixConflictError), and live.messages(threadId, { before }) pages the saved transcript.
hlix.chat is the same chat for a caller that cannot hold a stream open (an assistant on a phone): chat.send({ threadId, message }) stores a turn and answers at once with its operationId, chat.stop({ threadId }) stops the run, and chat.reply(threadId) answers { status, text } — running while a run is in flight, complete with the newest answer after the person’s last message, or delivered while nobody has answered it yet.
A project watch stays open until you leave the loop or abort its signal. It reads the project’s board again every five seconds and yields only when something changed; key repeated activity rows by id.
Error classes
Section titled “Error classes”| Class | Meaning |
|---|---|
HlixValidationError |
400 schema failure; exposes decoded issues |
HlixBadRequestError |
other 400 response |
HlixAuthenticationError |
401 |
HlixPermissionError |
403 |
HlixNotFoundError |
404, including not visible |
HlixConflictError |
409 compare-and-swap or state conflict |
HlixRateLimitError |
429; exposes retryAfter |
HlixNotImplementedError |
501 unavailable operation |
HlixServerError |
other 5xx |
HlixTransportError |
no HTTP response |
Every HTTP error also carries the status, parsed body, and request ID when the server provides one.
Retry policy
Section titled “Retry policy”The default uses bounded exponential backoff with jitter and honors Retry-After. Only idempotent verbs are eligible. POST and PATCH are never retried automatically because the client may not know whether the server already performed the mutation.
Disable retry:
const hlix = createHlix({ baseUrl, credential, organizationId, retry: { attempts: 1 },})Repository access and local sync
Section titled “Repository access and local sync”HLIX Repos stores committed project files. The SDK exposes scoped repository access and head reads:
hlix.repos.access(projectId, repoId, 'read')requests short-lived read access.hlix.repos.head(projectId, repoId)reads the repository’s current commit.hlix.repos.protectedFiles(projectId)reads protected files when your role permits it.
Keep access tokens and protected-file responses out of logs. For local-to-cloud import and synchronization, use the CLI, which handles scanning, secret separation, and conflict detection.
Raw transport
Section titled “Raw transport”hlix.raw exposes a typed client for every published operation, including those the higher-level methods do not wrap:
const { data, error } = await hlix.raw.GET('/v1/api/projects')The raw client returns { data, error }; it does not apply the SDK’s throwing error model.
Verify your integration
Section titled “Verify your integration”HLIX_API_KEY='…' \HLIX_WORKSPACE_ID='<workspace-id>' \node dist/example.jsUse a secret manager in CI and avoid printing environment variables. Confirm the result against hlix projects list --json or the web app in the same workspace.
If the client fails
Section titled “If the client fails”Cannot find module '@hlix/sdk'— the package is not installed in this project. Runnpm install @hlix/sdk; Current versions lists the published version.HlixAuthenticationError— the key is missing or rejected. It authenticates a user, so a valid key with noorganizationIdcan still fail the next check.HlixPermissionError— the user cannot act in the selected workspace. ConfirmorganizationIdis the one you meant.HlixNotFoundErroron a resource you can see in the dashboard — check the workspace, resource ID, and your access. A missing or inaccessible resource uses this error.HlixConflictError— the resource changed or an operation is already in progress. Read its current state before deciding whether to retry.HlixTransportError— no HTTP response at all. CheckbaseUrl, DNS, and TLS before suspecting credentials.- A write returned no response — inspect the resource before submitting it again. The SDK does not automatically retry
POSTorPATCH, and a lost response does not prove the server rejected the request. - A stream disconnects —
live.followandlive.runreconnect with backoff and resume from their last frame;live.projectandlive.taskread the board on a clock and throw when a read fails. Handle the final error if reconnect attempts are exhausted.