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

# Web SDK

> Install and use the official Web SDK for browser-side movement detection.

# Web SDK

Use the Web SDK in browser applications with a short-lived `tokenProvider` flow from your backend.

If you want to evaluate the runtime before you wire in browser auth, start with the guided [guest Workbench demo](https://app.kynasmith.dev/workbench?demo=squat). This page is the integration path once you are ready to connect your own frontend.

## Step 1: Install

```bash theme={null}
npm install @kynasmith/web-sdk
```

<Warning>
  The `@kynasmith/web-sdk` package is the intended public browser integration path. If the install command currently returns a 404, check the [changelog](/production/changelog) for registry availability before treating it as an integration issue.
</Warning>

## Step 2: Set up a backend token endpoint

Your browser application must not embed long-lived API keys. Instead, create a backend endpoint that exchanges your API key for a short-lived access token:

```python theme={null}
# Example backend endpoint (using any Python web framework)
import httpx

async def exchange_token():
    """Mint a short-lived Kynasmith access token for the browser client."""
    async with httpx.AsyncClient() as http:
        resp = await http.post(
            "https://api.kynasmith.dev/api/auth/access-tokens",
            json={
                "api_key_id": "key_123",
                "api_key_secret": "secret_123",
                "scopes": ["sessions:write", "movespecs:read"],
            },
        )
        resp.raise_for_status()
        return resp.json()
```

See [Authentication](/get-started/authentication) for more details on the token exchange flow and available scopes.

## Step 3: Initialize the client

```ts theme={null}
import { createClient } from "@kynasmith/web-sdk";

const client = createClient({
  tokenProvider: async () => {
    const response = await fetch("/api/kynasmith/token", { method: "POST" });
    const { access_token } = await response.json();
    return access_token;
  },
});
```

Your `tokenProvider` is called before each authenticated request, so it must always be able to return a fresh token.

## Step 4: Create and release a MoveSpec

```ts theme={null}
const movespec = await client.movespecs.create({
  name: "Squat counter",
});

await client.movespecs.updateDraft(movespec.movespec_id, {
  yamlSource: `
name: squat
version: "1.0"
detection:
  type: repetition
  movement:
    name: squat
    phases:
      - name: standing
        criteria:
          - joint: knee
            angle_min: 160
            angle_max: 180
      - name: bottom
        criteria:
          - joint: knee
            angle_min: 60
            angle_max: 100
    sequence:
      - standing
      - bottom
      - standing
`,
});

const validation = await client.movespecs.validateDraft(movespec.movespec_id);
if (!validation.ok) {
  throw new Error("Draft validation failed");
}

const version = await client.movespecs.createVersion(movespec.movespec_id);
await client.movespecs.releaseVersion(
  movespec.movespec_id,
  version.movespec_version_id,
);
```

See [MoveSpec](/movespec/overview) for the full YAML authoring guide.

## Step 5: Replay a browser video file

```ts theme={null}
const fileInput = document.querySelector("#video") as HTMLInputElement;
const videoFile = fileInput.files?.[0];

if (!videoFile) {
  throw new Error("Choose a video first");
}

const result = await client.processVideo(
  {
    movespecVersionId: version.movespec_version_id,
    mode: "replay",
  },
  videoFile,
  { batchSize: 15 },
);

console.log(`Accepted frames: ${result.acceptedFramesTotal}`);
console.log("Completed events:", result.completedEventCounts);
```

## Step 6: Run a live camera session

```ts theme={null}
const liveResult = await client.startCamera(
  {
    movespecVersionId: version.movespec_version_id,
    mode: "live",
  },
  { maxFrames: 300 },
);
```

`startCamera()` processes local camera frames until `maxFrames` is reached or the run is aborted, then finalizes the session and returns the result.

## Manual session control

Use the lower-level session helpers for full control over realtime flow:

```ts theme={null}
const session = await client.detection.createSession({
  movespecVersionId: version.movespec_version_id,
  mode: "live",
});

const realtime = client.detection.connectSession(session);

for await (const event of realtime.iterateEvents()) {
  console.log(event.type);
}
```

<Accordion title="Alternative configuration">
  ### Static access token

  If you already have a short-lived bearer token:

  ```ts theme={null}
  const client = createClient({
    accessToken: "ks_at_...",
  });
  ```

  When using a static `accessToken`, you are responsible for refresh and rotation.

  ### Explicit project ID

  When your credential is scoped to multiple projects, pass `projectId` in request payloads:

  ```ts theme={null}
  const result = await client.processVideo(
    {
      movespecVersionId: version.movespec_version_id,
      mode: "replay",
      projectId: "proj_456",
    },
    videoFile,
  );
  ```
</Accordion>

## Typed errors

The Web SDK exposes typed error classes for structured error handling:

* `AuthError` — authentication failures
* `AuthorizationError` — permission or scope errors
* `CompatibilityError` — SDK version is not compatible (update your SDK)
* `RealtimeProtocolError` — connection or session communication failure
* `SessionStateError` — session lifecycle violations
* `ValidationError` — request or MoveSpec validation failures
* `RateLimitError` — rate limit exceeded (HTTP 429)

## Browser requirements

* `tokenProvider` must be able to mint or fetch fresh short-lived bearer tokens repeatedly
* Camera helpers require `navigator.mediaDevices.getUserMedia()`
* Replay and camera helpers need a browser environment with `fetch`, `WebSocket`, and media APIs
* If you keep the default MediaPipe asset URLs, your CSP and network path must allow Google Cloud Storage and jsDelivr

## Compatibility

* `@mediapipe/tasks-vision` `0.10.32`
* Default WASM root: `@mediapipe/tasks-vision@0.10.32/wasm`
* Default model asset: `pose_landmarker_full/float16/1/pose_landmarker_full.task`
