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

# RP SDK (Browser)

> Reference for @vibechecklabs/array-identity-rp. Build a sign-in request, render it as a QR or deep link, receive the same-device response.

<Note>
  **Maturity:** Limited preview (`0.3.0`). Browser transport only.
</Note>

The browser half of a relying-party (RP) integration. It builds a sign-in
request, renders it, and receives the response for hand-off to your server.

**It holds no seed, never mints, and cannot verify.** That is structural: the
only runtime dependency is the dependency-free wire contract, so verification
code cannot end up in your bundle. Verification belongs on your server. See
[RP server](/build/sdks/rp-server).

```bash theme={null}
pnpm add @vibechecklabs/array-identity-rp
```

## Exports

| Export | Signature |
| - | - |
| `createSignInRequest(opts)` | `(CreateSignInRequestOptions) => string`. Returns **unsigned JSON claims**, not the QR payload |
| `receiveFragmentResponse(win?)` | `() => FragmentResponse \| null`. Scrubs the fragment |
| `handOffToBackend(url, response, fetchImpl?)` | `(string, FragmentResponse) => Promise<Response>` |
| `encodeRequestFrames(payload, chunkBytes?)` | `(string, number = 256) => string[]`. Not needed today, see below |
| `animateFrames(frames, render, opts?)` | `(string[], (frame, i) => void, { intervalMs? }?) => () => void`. Returns a stop function |
| `buildDeepLink(payload, scheme)` | `(string, string) => string`. `scheme` is required |
| `DEEP_LINK_ROUTE` | `"array-siop"` |
| `FRAME_PREFIX` | `"array-req/1/"` |

## createSignInRequest

Builds the unsigned claim object. It is crypto-free, which is why it can live
in a browser package. Run it on your backend anyway, since the result must be
signed there and the nonce must be issued where the burn happens.

```ts theme={null}
interface CreateSignInRequestOptions {
  clientId: string;          // your registered client id
  nonce: string;             // single-use, issued by your backend
  responseEndpoint: string;  // must be on your registered allowlist
  scope?: string;            // "arrayid" for disclosed; omit for pairwise
  state?: string;            // session binding for cross-device flows
  idFacetProof?: boolean | { slot: number };
  capabilities?: string[];   // capability grants, see /build/capability-grants
  standingSeconds?: number;
  ttlSeconds?: number;       // default 300
  nowSeconds?: number;       // test clock override
}
```

Sign the result with `createPresentationRequestSigner`. The browser only
encodes the opaque compact JSON Web Signature (JWS) that comes back.

`idFacetProof` is a **request, not a decision**: the device proves the family
its subject mode implies whatever slot you name, and your verifier pins the
slot from its own registry on the way back.

<Warning>
  The `idFacetProof` wire shape is provisional and may change without a major
  version bump until the first external relying party integrates.
</Warning>

## Rendering

A sign-in request fits in a single QR code, so render one static code. Encode
the **deep link**, not the bare payload, because that is what lets the phone's
native camera open the app without an in-app scanner. A bare payload is a
meaningless string to a camera.

```ts theme={null}
const link = buildDeepLink(request, "vibecheck");
await QRCode.toCanvas(canvas, link, { scale: 4, margin: 4, errorCorrectionLevel: "L" });
```

`buildDeepLink` returns `<scheme>://array-siop?request=<payload>`. The scheme
is required and belongs to the installed app build: `vibecheck`,
`vibecheck-testnet` or `vibecheck-dev`. These are registered URL schemes rather
than product names, so they still read `vibecheck` although the app is called
Alive. There is no default, because any
default would be the wrong app for someone. The same link carries rung-0 claim
requests, and the app dispatches on the request's `typ`.

A compact JSON Web Signature is URL-safe in every character, so it goes in the
query verbatim. Anything else is base64url-wrapped. The device applies the same
test, so both shapes work against a single app build.

<Tip>
  Three QR settings decide whether a code scans first time, and none is about
  capacity. Fix an **integer** `scale` rather than a target width, because a
  width makes the renderer derive a fractional scale and nearest-neighbour
  resample at it, giving an irregular mix of 2px and 3px modules. Use
  error-correction level **`L`**, since a code on a clean display is not
  defending against physical damage and the redundancy costs about 14% more
  modules. Keep the full 4-module quiet zone inside the canvas instead of
  relying on the surrounding CSS padding, or restyling the page silently
  degrades the code.
</Tip>

### The frame helpers

`encodeRequestFrames` and `animateFrames` implement an animated multi-frame
transport for payloads too large for one code, splitting into strings of the
form `array-req/1/<index>/<total>/<chunk>`. You do not need them. Today's
requests fit in one QR, and the Alive app does not scan multi-frame codes:
reassembling frames would mean building a scanner inside your own app instead
of using the native camera. They are here for a future request shape that grows
past a single symbol.

## Receiving the response

Cross-device responses skip the browser entirely, because the device posts
directly to your registered endpoint. Same-device responses arrive in the URL
fragment, never the query string, so they never reach a server log.

`receiveFragmentResponse` reads the fragment and immediately scrubs it from the
address bar and history before returning. After the call the token exists only
in the returned object.

```ts theme={null}
const response = receiveFragmentResponse();
if (response) await handOffToBackend("/auth/siop-response", response);
```

`handOffToBackend` posts `id_token` and `state` form-encoded, and throws if
your backend does not answer 2xx.

<Warning>
  **Do not host the callback page alongside untrusted third-party scripts.** The
  helper scrubs the fragment on receipt, but anything running in that document
  beforehand can read the URL.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.