> Source: https://wexa.ai/docs/surfaces/typescript/install

# Install the TypeScript SDK

`@wexa-fabric/sdk` is a thin HTTP client for the Wexa gateway. Zero runtime dependencies, ESM and
CommonJS, Node 18 or later, and it also runs in browsers and edge runtimes.

## Install

```bash
npm install @wexa-fabric/sdk
```

The current published version is **0.2.3**; the Wexa repository carries **0.3.0**, which adds the
schedule, trigger and Data Table methods. The package exports its own version as `VERSION`, and
that constant is asserted equal to the one in `package.json` at build time, so what the client
reports is what you installed:

```ts
import { VERSION } from '@wexa-fabric/sdk'
console.log(VERSION)   // 0.2.3
```

## Configure

Two values, and the client reads both from the environment when you do not pass them:

```bash
export WEXA_WORKSPACE=https://fabric.wexa.ai    # your workspace URL
export WEXA_API_KEY=fab_sk_…                    # any bearer credential the gateway accepts
```

`WEXA_WORKSPACE` is the workspace URL, not the API base. The client discovers the API base itself
from `GET /v1/connection-info`, which means the gateway decides its own address and moving it needs
no change on your side. Trailing slashes are stripped for you.

`WEXA_API_KEY` takes any bearer credential — a `fab_sk_…` API key or an OAuth 2.1 access token. See
[REST authentication](/docs/surfaces/rest/authentication) for which kind you want.

## Construct the client

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()                              // reads the two environment variables
// or
const fabric = new Fabric({
  workspace: 'https://fabric.wexa.ai',
  apiKey: process.env.MY_KEY,
})
```

An explicitly-passed empty string falls through to the environment variable, and an environment
variable set to the empty string counts as absent — so a half-configured deployment fails the same
way as an unconfigured one rather than silently pointing at nothing.

### Constructor options

| Option | Default | What it does |
|---|---|---|
| `workspace` | `WEXA_WORKSPACE` | Workspace URL. |
| `apiKey` | `WEXA_API_KEY` | Bearer credential. |
| `timeout` | `70` | Seconds. Governed tool routes cap near 60s upstream, so 70 leaves the server's own deadline to fire first. |
| `retries` | `3` | Attempts when a call passes `retry: true`. **Total**, not extra. |
| `fetch` | global `fetch` | An injected `fetch`, for tests, a proxy agent, or connection pooling. |

### Discovery is deferred

`fabric.base` is `undefined` until the first request. A TypeScript constructor cannot `await`, so
the `/v1/connection-info` round trip is deferred to the first call and memoised — including its
rejection, so a bad workspace URL does not re-fetch on every call.

```ts
const fabric = new Fabric()
console.log(fabric.base)          // undefined
await fabric.whoami()
console.log(fabric.base)          // http://localhost:7123/v1
console.log(fabric.issuer)        // http://localhost:7123
```

This is the one structural difference from the Python client, which performs discovery inside its
constructor. Credential validation happens synchronously in both.

## Verify the install

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()
console.log(await fabric.whoami())
fabric.close()
```

That prints:

```json
{
  "user_id": "u_c10", "role": "OWNER",
  "org_id": "org_c10", "dept_id": "dept_c10", "project_id": "proj_c10",
  "grants": ["fabric:query.read", "fabric:docs.read", "fabric:ontology.write",
             "fabric:orchestrate.read", "fabric:orchestrate.write", "fabric:agent.run",
             "fabric:skill.write", "fabric:model.write"]
}
```

If that works, the workspace URL resolves, discovery succeeded, and your credential is accepted.

## Releasing the client

`close()` aborts any in-flight request and any approval poll. Calling it twice is a no-op, and the
client cannot be reused afterwards. It is also wired to `Symbol.asyncDispose`:

```ts
await using fabric = new Fabric()
// released at the end of the scope
```

Python has no equivalent, because `urlopen` closes per request and there is nothing to release.

## One SDK ported to the other

The TypeScript and Python clients are deliberate ports of one another, not two independent
libraries and not a stack — neither wraps the other, and the TypeScript package has no dependency on
Python. They share one tool table, one set of server-bound arguments, one error taxonomy, one retry
policy and one response envelope, so an integration written against either reads the same way.

They are not byte-identical, and pretending otherwise would cost you a handler. The documented
differences are listed on [error handling](/docs/surfaces/typescript/error-handling) and
[Python SDK — install](/docs/surfaces/python/install).

## Related

  <Card title="Usage" href="/docs/surfaces/typescript/usage">
    Calling tools, the non-tool methods, cancellation.
  </Card>
  <Card title="Error handling" href="/docs/surfaces/typescript/error-handling">
    The full hierarchy, and plain retry versus resume retry.
  </Card>
  <Card title="TypeScript quickstart" href="/docs/get-started/quickstart/typescript">
    First successful call in five minutes.
  </Card>
  <Card title="Python SDK" href="/docs/surfaces/python/install">
    The sibling package, and where it differs.
  </Card>
