> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# @gavai/sdk-typescript

> Low-level signed client for the gavAI admin and control-plane surface.

export const StatusBadge = ({label, value, tone = "violet"}) => <span className={`gv-badge gv-badge--${tone}`}>
    <span className="gv-badge__label">{label}</span>
    <span className="gv-badge__sep" aria-hidden="true">·</span>
    <span className="gv-badge__value">{value}</span>
  </span>;

export const Def = ({term, type, defaultValue, required, children}) => <>
    <dt className="gv-def-list__term">
      <span className="gv-def-list__name">{term}</span>
      {type && <span className="gv-def-list__type">{type}</span>}
      {required && <span className="gv-def-list__required">required</span>}
      {defaultValue !== undefined && <span className="gv-def-list__default">
          default <code>{defaultValue}</code>
        </span>}
    </dt>
    <dd className="gv-def-list__description">{children}</dd>
  </>;

export const DefList = ({children}) => <dl className="gv-def-list">{children}</dl>;

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const FeatureTable = ({columns, rows, featuredRow, caption, variant}) => <div className={`gv-feature-table ${variant ? `gv-feature-table--${variant}` : ""}`}>
    <table>
      {caption && <caption className="gv-feature-table__caption">{caption}</caption>}
      <thead>
        <tr>
          {columns.map(col => <th key={col.key} scope="col">
              <span className="gv-feature-table__header">{col.label}</span>
            </th>)}
        </tr>
      </thead>
      <tbody>
        {rows.map((row, i) => {
  const rowClass = [i === featuredRow ? "gv-feature-table__row--featured" : "", row.chosen ? "gv-feature-table__row--chosen" : ""].filter(Boolean).join(" ");
  return <tr key={i} className={rowClass}>
              {columns.map(col => {
    const v = row[col.key];
    if (variant === "tradeoff" && Array.isArray(v)) {
      const pill = col.key === "pros" ? "gv-feature-table__pill--pro" : col.key === "cons" ? "gv-feature-table__pill--con" : "";
      return <td key={col.key}>
                      {v.map((item, j) => <span key={j} className={`gv-feature-table__pill ${pill}`}>{item}</span>)}
                    </td>;
    }
    return <td key={col.key}>{v}</td>;
  })}
            </tr>;
})}
      </tbody>
    </table>
  </div>;

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="SDK" />

`@gavai/sdk-typescript` targets the **signed** `/api/v1/*` admin surface — a different base path and a different auth scheme from the bearer-token public API. By the end of this page you will know how to install it, sign every request with HMAC-SHA256, verify response signatures before reading the payload, and map every error class to the failure mode that produced it.

<Warning>
  Most integrators want `@gavai/sdk`, not this package. Reach for `@gavai/sdk-typescript` only when you need mutual HMAC signing or are building platform tooling against the admin surface.
</Warning>

## Install

<CodeGroup>
  ```sh bun theme={null}
  bun add @gavai/sdk-typescript
  ```

  ```sh npm theme={null}
  npm install @gavai/sdk-typescript
  ```
</CodeGroup>

## Runtime support

The package has no native dependencies. It uses Web Crypto and `globalThis.fetch`, so it runs anywhere both are available.

<ArrowList>
  <ArrowItem>Bun</ArrowItem>
  <ArrowItem>Node.js 18 and later</ArrowItem>
  <ArrowItem>Deno</ArrowItem>
  <ArrowItem>Edge runtimes with Web Crypto support</ArrowItem>
  <ArrowItem>Browsers (where you control the CORS policy)</ArrowItem>
</ArrowList>

## Minimal example

Read your workspace identity, invite a member with an auto-minted idempotency key, then invite a second member with a caller-supplied key so the call is safe to retry.

```ts admin.ts theme={null}
import { GavaiClient } from "@gavai/sdk-typescript";

const client = new GavaiClient(process.env.GAVAI_API_KEY!);

// Read
const me = await client.workspaces.me();

// Write — the SDK mints a UUID v4 idempotency key for you
await client.workspaces.members.invite("acme", {
  email: "alice@example.com",
  role: "member",
});

// Write — caller-supplied key, safe to retry from any client
await client.workspaces.members.invite(
  "acme",
  { email: "bob@example.com", role: "member" },
  { idempotencyKey: "invite-bob-2026-05-10" },
);
```

## How signing works

Every request is HMAC-SHA256 signed and sent with the `X-Gavai-Signature` header. Every response carries an `X-Gavai-Response-Signature` header, and the SDK verifies it **before** the promise resolves to the caller. If the signature is missing or fails to verify, the SDK throws `GavaiSignatureError` and the response payload is never returned. There is no opt-out.

The practical effect: by the time you have a result in hand, the server is cryptographically attested. Code downstream of an awaited call can treat the payload as trusted without re-checking.

## API key format

Keys for this SDK are distinct from the bearer-token keys used by `@gavai/sdk`.

```text format theme={null}
gavai_<env>_<keyid>_<secret>
       |     |       |
       |     |       64 lowercase hex chars (256-bit CSPRNG secret)
       |     12 lowercase hex chars (public lookup id)
       opaque environment slug
```

Generate keys at [https://console.gavai.app/settings/api-keys](https://console.gavai.app/settings/api-keys). Treat the secret like a password — store it in your secret manager, never commit it to git.

## Constructor options

```ts options.ts theme={null}
const client = new GavaiClient(apiKey, {
  baseUrl: "https://staging.console.gavai.app",
  fetch: customFetch, // any spec-compliant fetch implementation
});
```

<DefList>
  <Def term="apiKey" type="string" required>
    Full key string in the `gavai_<env>_<keyid>_<secret>` format. Pass as the first positional argument.
  </Def>

  <Def term="baseUrl" type="string" defaultValue="https://console.gavai.app">
    Override the admin surface base URL.
  </Def>

  <Def term="fetch" type="typeof fetch">
    Inject a custom fetch implementation. Use for tests, proxies, or runtimes without native `fetch`.
  </Def>
</DefList>

## Errors

Every error extends `GavaiError`. Pattern-match with `instanceof` to handle a specific failure without swallowing the rest.

<FeatureTable
  columns={[
{ key: "cls", label: "Class" },
{ key: "trigger", label: "Trigger" }
]}
  rows={[
{
  cls: "`GavaiError`",
  trigger: "Base class. Thrown for any unmapped envelope error code."
},
{
  cls: "`GavaiSignatureError`",
  trigger: "Response signature did not verify, or the server omitted the `X-Gavai-Response-Signature` header."
},
{
  cls: "`GavaiRateLimitError`",
  trigger: "Envelope `error.code === \"RATE_LIMITED\"`. Carries a `retryAfterSec` field."
},
{
  cls: "`GavaiValidationError`",
  trigger: "Envelope `error.code === \"VALIDATION_FAILED\"`. Carries a `fields` map."
},
{
  cls: "`GavaiProtocolError`",
  trigger: "Envelope shape did not match — usually a version skew between client and server."
}
]}
/>

```ts retry.ts theme={null}
import { GavaiRateLimitError } from "@gavai/sdk-typescript/errors";

try {
  await client.workspaces.me();
} catch (err) {
  if (err instanceof GavaiRateLimitError) {
    await sleep(err.retryAfterSec * 1000);
    // retry...
  }
  throw err;
}
```

A `GavaiSignatureError` is never safe to retry without investigation — the response could not be authenticated, and a second attempt against the same compromised path will produce the same outcome.

## Status

<StatusBadge label="VERSION" value="0.0.0" tone="violet" />

Surfaces are stable, but response types are `unknown` until the companion `@gavai/schema/api/responses.ts` ships typed schemas. The originating route handler is referenced in JSDoc on each method — check it when you need a concrete shape.

## Next steps

<Columns cols={2}>
  <BrandCard title="@gavai/sdk" href="/sdks/typescript-sdk">
    Want the high-level client with bearer-token auth instead? Start here.
  </BrandCard>

  <BrandCard title="Error handling" href="/sdks/error-handling">
    Full retry patterns and what to log when a signed request fails.
  </BrandCard>
</Columns>
