# Unidy Web SDK — Headless / JS API

Everything the components do is available programmatically. Import Auth and getUnidyClient from the SDK module to read auth state, call the profile/newsletter/ticket/subscription APIs directly, and combine the results with any library — no u-* markup required.

## Setup

Every example below assumes the SDK is loaded and configured once per page:

```html
<script type="module" src="https://cdn.jsdelivr.net/npm/@unidy.io/sdk@1.8.1/dist/sdk/sdk.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@unidy.io/sdk@1.8.1/dist/sdk/sdk.css">

<u-config
  base-url="https://hammarby.staging.unidy.app"
  api-key="your-api-key"
  locale="en"
  check-signed-in="true"></u-config>
```

## Auth state from JavaScript

authState is the same reactive store the components use: read authState.authenticated for the current value and subscribe with onAuthChange for updates. Session restore runs asynchronously on page load, so reacting to the store — instead of a one-shot check — is the reliable pattern. Auth.Errors exposes stable error codes for handling specific sign-in failures.

- **authState store** — current auth state, shared with the components
- **onAuthChange()** — subscribe to sign-in/sign-out transitions
- **Auth.Errors** — stable error-code constants

```html
<div class="rounded-lg border border-border bg-background-light p-4">
  <p class="text-sm">
    Authentication status:
    <strong id="auth-status" class="font-mono">checking…</strong>
  </p>
</div>

<script type="module">
  // The same module the components use — import it for headless access
  import {
    authState,
    onAuthChange,
    Auth,
  } from "https://cdn.jsdelivr.net/npm/@unidy.io/sdk@1.8.1/dist/sdk/index.esm.js";

  const render = (authenticated) => {
    document.getElementById("auth-status").textContent = authenticated
      ? "authenticated"
      : "not authenticated";
  };

  // authState is a reactive store: read it now, subscribe for changes.
  // (Session restore is async on page load — never rely on a one-shot check.)
  render(authState.authenticated);
  onAuthChange("authenticated", render);

  // Auth.Errors holds stable error codes for the sign-in process,
  // e.g. Auth.Errors.email.NOT_FOUND === "account_not_found"
  console.log("Known email errors:", Auth.Errors.email);
</script>
```

Live demo: /headless#auth-state

## API calls with getUnidyClient

getUnidyClient() exposes typed services — profile, newsletters, tickets, subscriptions, auth. Calls return a [error, data] tuple, so error handling is a destructure instead of try/catch.

- **client.profile.get() / .update()** — read and write profile data
- **[error, data] tuples** — predictable error handling
- **All feature areas** — newsletters, tickets, subscriptions, auth

```html
<div class="flex flex-col gap-3">
  <button id="load-profile" class="btn btn-primary !min-h-0 !px-4 !py-2 w-fit text-sm">
    Load my profile via the API
  </button>
  <pre
    id="profile-output"
    class="overflow-x-auto rounded-lg bg-background-light p-4 font-mono text-xs text-text-light">(click the button — requires being signed in)</pre>
</div>

<script type="module">
  import { getUnidyClient } from "https://cdn.jsdelivr.net/npm/@unidy.io/sdk@1.8.1/dist/sdk/index.esm.js";

  document.getElementById("load-profile").addEventListener("click", async () => {
    const output = document.getElementById("profile-output");

    // getUnidyClient() reads its config from <u-config> on the page.
    // Every service call returns a [error, data] tuple — no try/catch needed.
    const client = getUnidyClient();
    const [error, profile] = await client.profile.get();

    output.textContent = error
      ? `Error: ${JSON.stringify(error, null, 2)}`
      : JSON.stringify(profile, null, 2);

    // Also available: client.newsletters, client.tickets, client.subscriptions, client.auth
  });
</script>
```

Live demo: /headless#unidy-client

## Mix with any library: profile QR code

SDK data is just data — here the signed-in user's name is fetched via the client and rendered as a QR code with a third-party library. The same pattern powers loyalty cards, wallet passes, or personalized widgets.

- **SDK + npm ecosystem** — combine with any client-side library
- **u-signed-in gating** — components and JS API work together

```html
<u-signed-in>
  <!-- Combine SDK data with any third-party library — here: a QR code
       encoding the user's name, rendered fully client-side -->
  <canvas id="profile-qr-canvas" width="200" height="200"></canvas>
</u-signed-in>

<u-signed-in not>
  <p class="text-text-light">
    Sign in on the <a href="/auth" class="text-primary underline">Auth page</a> to see your profile QR
    code.
  </p>
</u-signed-in>

<script type="module">
  import QRCode from "https://esm.sh/qrcode@1.5.4";
  import {
    authState,
    onAuthChange,
    getUnidyClient,
  } from "https://cdn.jsdelivr.net/npm/@unidy.io/sdk@1.8.1/dist/sdk/index.esm.js";

  async function renderQr() {
    const [error, profile] = await getUnidyClient().profile.get();
    if (error) return;
    const name = [profile.first_name?.value, profile.last_name?.value].filter(Boolean).join(" ");
    QRCode.toCanvas(
      document.getElementById("profile-qr-canvas"),
      `Hello, ${name || "Unidy user"}!`
    );
  }

  // Session restore is async on page load, so react to auth state instead
  // of checking once
  if (authState.authenticated) renderQr();
  onAuthChange("authenticated", (authenticated) => authenticated && renderQr());
</script>
```

Live demo: /headless#profile-qr

## Further reading

- Full component reference: https://github.com/UnidyID/unidy-sdk/blob/HEAD/packages/sdk/readme.md
- Official quick-start examples: https://github.com/UnidyID/unidy-sdk/blob/HEAD/packages/sdk/quick-start-examples.md
- Package: https://www.npmjs.com/package/@unidy.io/sdk
- All guides: /llms.txt
