# TypeScript SDK

`@hlix/sdk` is the first-party TypeScript client. Its request and response types come from the published OpenAPI contract, while the SDK adds credentials, explicit workspace selection, typed errors, bounded retry, bundle transfer, and Server-Sent Events.

## Prerequisites

- Node.js 20 or later
- An hlix API key
- A workspace ID
- TypeScript is recommended, but the package also exposes ESM JavaScript

## Install

```bash
npm install @hlix/sdk
```

Pin the version in CI so a build never picks up a client you have not tested against:

```bash
npm install @hlix/sdk@0.2.0
```

[Current versions](/releases/availability/) reports the same state for all three hlix packages, and names the gate each one still has to pass.

Until the package ships, **generate a typed client from the [public OpenAPI contract](/api/openapi/)** — it is the same document this SDK is built from, and it downloads without authentication. The shapes below describe the client hlix will publish; the generated client exposes the same operations with generated names.

## Create a client

```ts
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!,
})
```

An API key authenticates a user; it does not imply a workspace. Always pass `organizationId` in server-side automation so a prior browser session cannot select the tenant implicitly.

## Runnable happy path

List projects and inspect a task:

```ts
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)

try {
  const task = await hlix.tasks.get(process.env.HLIX_TASK_ID!)
  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. A hidden resource and an absent resource both produce `HlixNotFoundError`; the API does not reveal cross-tenant existence.

## Stream task status

```ts
const controller = new AbortController()

for await (const event of hlix.tasks.stream(taskId, {
  signal: controller.signal,
})) {
  console.log(event.event, JSON.parse(event.data))
}
```

Streaming does not retry automatically. A reconnected stream could replay events the consumer has already acted on, so reconnection belongs in application logic with its own checkpoint policy.

## 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 permanent refusal |
| `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

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:

```ts
const hlix = createHlix({
  baseUrl,
  credential,
  organizationId,
  retry: { attempts: 1 },
})
```

## Imports and revisions

The SDK exposes the same low-level import and revision primitives used by the CLI:

- `hlix.imports.create`, `get`, `uploadBundle`, and `finalize`
- `hlix.revisions.head`, `createUpload`, `uploadBundle`, `finalize`, `downloadBundle`, and `protectedFiles`

These are multi-step protocols. Preserve the server-issued upload identity, declare exact hashes and sizes, and finalize only with protected data that exactly matches the manifest. For ordinary local-to-cloud use, prefer the CLI—it owns scanning, snapshot construction, integrity verification, and local revision state.

## Raw transport

`hlix.raw` exposes the generated `openapi-fetch` client for published operations not wrapped by the opinionated facade:

```ts
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

```bash
HLIX_API_KEY='…' \
HLIX_WORKSPACE_ID='<workspace-id>' \
node dist/example.js
```

Use 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

- `Cannot find module '@hlix/sdk'` — the workspace package is not built. Run `bun install --frozen-lockfile` then `bun run --cwd packages/sdk build`. There is no registry install yet; see [Current versions](/releases/availability/).
- `HlixAuthenticationError` — the key is missing or rejected. It authenticates a *user*, so a valid key with no `organizationId` can still fail the next check.
- `HlixPermissionError` — the user cannot act in the selected workspace. Confirm `organizationId` is the one you meant.
- `HlixNotFoundError` on a resource you can see in the dashboard — you are pointed at a different workspace. Absent and not-visible are deliberately the same answer; do not infer cross-tenant existence from it.
- `HlixConflictError` on a revision call — the expected revision or generation is stale. Read the head again before retrying.
- `HlixTransportError` — no HTTP response at all. Check `baseUrl`, DNS, and TLS before suspecting credentials.
- **A `POST` that seems to have run twice** — it did not retry. `POST` and `PATCH` are never retried automatically, precisely because the client cannot know whether the server already applied the mutation.
- **A stream that stops silently** — streaming does not retry. Reconnection belongs in your code, with its own checkpoint policy, because a replayed event can be acted on twice.

## Next steps

[API & OpenAPI](/api/openapi/)
  [Current versions](/releases/availability/)
  [Tasks](/running/tasks/)
  [Python SDK preview](/sdk/python/)
