# TypeScript client

Source: https://convergingthought.com/docs/sdk-reference/typescript

> The exact interface of the private workspace SDK, with explicit operation identity and result checks.



`@internalize/sdk` is a private workspace package in the Internalize monorepo. Its source is TypeScript and its exports point to the source modules. Use it from an authorized workspace with a TypeScript-aware toolchain. It is not a public npm installation target.

## Construct the client [#construct-the-client]

```ts
import { InternalizeClient, InternalizeError } from "@internalize/sdk";

const client = new InternalizeClient({
  baseUrl: "https://convergingthought.com",
  apiKey: process.env.INTERNALIZE_API_KEY!,
});
```

The constructor accepts `baseUrl`, `apiKey`, an optional `tenantId`, and an optional `fetch` implementation for controlled transport or tests. It stores the URL origin, so a path prefix in `baseUrl` is not preserved. HTTPS is required except for localhost addresses used in local testing.

## Select a customer [#select-a-customer]

Use `const customer = client.forTenant(authorizedTenantId)` before learning or inference. The returned client carries `X-Internalize-Tenant` on every operation, including `job`, `wait`, and `activate`. It leaves the shared client unchanged and cannot be rebound to another tenant. You can also set `tenantId` in the constructor.

If you supply `tenant_id` directly in an input body, use a client bound to that same tenant when polling the result. A body tenant and client header must agree. Bind identity in trusted server code, outside model arguments. See [Serve multiple customers](/docs/guides/multitenancy).

## Submit and inspect learning [#submit-and-inspect-learning]

```ts
// Persist this operation key in your application before the first attempt.
const operationKey = "support-policy-release-001";
const accepted = await client.internalize(
  {
    max_cost_microusd: 5_000_000,
    subject_id: "support-policy",
    content:
      "Northstar accepts returns within 30 days of delivery. Return labels are prepaid.",
    activate: true,
  },
  operationKey,
);

// Persist accepted.id immediately so another process can resume the wait.
const learned = await client.wait(accepted.id, { timeoutMs: 600_000 });
if (learned.status !== "ready" || !learned.result?.activated) {
  throw new Error(`Learning needs review: ${learned.status}`);
}
```

`wait` returns a job when `billing.settled` becomes true. It does not throw merely because the settled status is `failed` or `rejected`. Check the status and activation result explicitly, as above, and retain the original job for diagnosis.

## Ask through the same subject [#ask-through-the-same-subject]

```ts
const acceptedAnswer = await client.infer(
  {
    subject_id: "support-policy",
    messages: [{ role: "user", content: "Who pays for a return label?" }],
    max_output_tokens: 2048,
    temperature: 0,
  },
  "support-label-question-001",
);

const answer = await client.wait(acceptedAnswer.id);
if (answer.status !== "succeeded") {
  throw new Error(answer.error?.code ?? answer.status);
}
console.log(answer.result?.text);
```

The logging line is for a private local example. In a deployed application, return the answer through your authorized response path rather than writing model content to general analytics. Check `answer.adapter_version` when the exact learned release matters.

## Methods [#methods]

| Method        | Arguments                                           | Result                             |
| ------------- | --------------------------------------------------- | ---------------------------------- |
| `forTenant`   | Authorized tenant ID                                | Independent namespace-bound client |
| `internalize` | Input, explicit idempotency key                     | `{ id, status_url }`               |
| `infer`       | Input, explicit idempotency key                     | `{ id, status_url }`               |
| `job`         | Job ID, optional AbortSignal                        | Current `Job`                      |
| `wait`        | Job ID, optional wait options                       | Settled `Job`, or polling error    |
| `activate`    | Candidate version, expected current version or null | `{ version_id, revision }`         |

`wait` options are `signal`, `timeoutMs`, and `onUpdate`. The default timeout is one hour. Polling starts at one second and increases by a factor of 1.5 up to 15 seconds. `onUpdate` receives each observed job; avoid forwarding its result content into telemetry.

## Errors and cancellation [#errors-and-cancellation]

HTTP failures throw `InternalizeError` with `code`, `status`, `requestId`, and `retryable`. Transport failures can throw ordinary fetch errors. `wait` throws `poll_timeout` when its deadline expires and `reconciliation_required` when the job needs investigation.

An AbortSignal stops a read or wait; it does not cancel model execution. Neither `infer` nor `internalize` accepts an AbortSignal argument in the current client. The SDK never retries writes automatically and does not automatically retry a failed polling HTTP request.

Catch errors at your durable application boundary, store the job ID, and resume with `job` or `wait` when appropriate. Do not wrap submission in an unbounded retry that generates a fresh operation key. See [Polling and recovery](/docs/sdk-reference/polling) for the full decision flow.
