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

# Quickstart

> Install @endstate-sdk/reader and turn a physical tap into an identified chip your backend can pair or verify.

`@endstate-sdk/reader` turns a physical tap into an **identified chip** in your
browser application: every tap yields the same `chip_id` and `e` value the
[chip endpoints](/concepts/chips) speak, so the output feeds the API without
any parsing on your side.

The package is TypeScript-first, has **zero runtime dependencies**, and is
licensed Apache-2.0.

## Install

<CodeGroup>
  ```bash npm theme={null}
  npm install @endstate-sdk/reader
  ```

  ```bash bun theme={null}
  bun add @endstate-sdk/reader
  ```

  ```bash pnpm theme={null}
  pnpm add @endstate-sdk/reader
  ```
</CodeGroup>

## Read a tap

`pickReader()` selects the best reader available in the current browser. Call
`connect()` and `start()` from a click handler: the first visit may open a
browser device or permission prompt. After that the browser remembers the
reader and later connects are silent - see
[Readers and environments](/sdks/reader/platforms#connecting-once-not-every-time).

```ts theme={null}
import { pickReader } from "@endstate-sdk/reader";

const reader = pickReader(); // best available reader, or null
if (!reader) throw new Error("No supported NFC reader in this browser.");

await reader.connect();
await reader.start({
  onTap: ({ url, chipId, e, c }) => {
    if (!chipId || !e) return; // the tag carried a non-Endstate URL
    // Hand { chipId, e, c } to your backend - see below.
  },
  onError: (message) => {
    // Operator-actionable messages: permission denied, unreadable tag, …
  },
});
```

Each tap delivers:

<ResponseField name="url" type="string">
  The full URL read from the tag.
</ResponseField>

<ResponseField name="chipId" type="^[0-9A-F]{10}$">
  The chip's public identifier - the API's `chip_id`.
</ResponseField>

<ResponseField name="e" type="^[0-9A-F]{32}$">
  The single-use tap credential - [treat it like a
  password](/concepts/chips#the-e-value) and use it promptly.
</ResponseField>

<ResponseField name="c" type="^[0-9A-F]{16}$">
  Tap verification code, included when the tag provides one - forward it to your
  backend alongside `e`.
</ResponseField>

`chipId` and `e` are present whenever the tag carries an Endstate chip URL,
and absent otherwise - check for them before calling your backend.

## Keep API keys out of the browser

Reader hardware runs in the browser; **secret keys (`end_sk_…`) never do**.
Pairing is always two-legged: the reader identifies the chip
client-side, and your backend - the only place your
[API key](/credentials) lives - makes the pairing call.

```ts theme={null}
// Operator UI (browser): tap → hand the identified chip to YOUR backend
onTap: ({ chipId, e, c }) =>
  fetch("/api/pair", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ chipId, e, c, unitId }),
  });
```

Your backend then pairs the chip with `POST /v1/chips` (`chip_id` + `e`), or
records a tap with `POST /v1/taps` (`chip_id` + `e` in the body). The
[Core SDK](/sdks/core/quickstart) takes this reader's output unchanged:

```ts theme={null}
// Your /api/pair handler - secret key stays here.
import { EndstateClient, secretKey } from "@endstate-sdk/core";

const endstate = new EndstateClient({
  apiKey: secretKey(process.env.ENDSTATE_API_KEY),
});

await endstate.chips.pair({ unit_id: unitId, chip_id: chipId, e, c });
```

Core is optional - this package emits the raw values the REST API speaks, so
any HTTP client works. See the [API reference](/api-reference/introduction).

`POST /v1/taps` is the one call a page can make on its own: send a
[publishable key](/settings/publishable-keys) (`end_pk_live_…`) instead of the
API key and the browser talks to Endstate directly - useful when the tap
happens on a customer's phone rather than an operator's station.

## Next

* [Pair chips from your own application](/guides/pair-chips-from-your-app) -
  the full operator flow this quickstart feeds into.
* [Readers and environments](/sdks/reader/platforms) - supported hardware,
  the environment matrix, and connecting once rather than every mount.
* [React](/sdks/reader/react) - the `useNfcReader` hook and mock mode.
* [Troubleshooting](/sdks/reader/troubleshooting) - permission prompts,
  per-origin NFC behavior, and console noise explained.
