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

# Auth Component

> A drop-in React component for secure credential capture, source selection, and authentication.

<video controls playsInline preload="none" poster="/images/auth-component-video-poster.jpg" src="https://mintcdn.com/decksoftwareinc/03XZIojHijunkB0_/images/auth-component-video.mp4?fit=max&auto=format&n=03XZIojHijunkB0_&q=85&s=b436bbc0eba811e092baf10ab7927702" data-path="images/auth-component-video.mp4" />

The Auth Component is a pre-built React component that collects end-user credentials, stores them in the [Credential Vault](/platform/credential-management), and creates a [Credential](/concepts/credentials). Credentials never touch your systems.

It handles source selection, username/password capture, [interactions](/guides/interactions) (MFA, security questions), and error states out of the box.

<Tip>
  Want full customization and control over the UX? See [Building your auth flow](/guides/building-auth-ui) to build your own using the API directly.
</Tip>

## How it works

```mermaid theme={null}
sequenceDiagram
    participant Backend as Your Server
    participant Frontend as Your Frontend
    participant Component as Auth Component
    participant API as Deck API
    participant Vault as Credential Vault

    Backend->>API: POST /v2/token
    API-->>Backend: session token
    Backend-->>Frontend: pass token
    Frontend->>Component: Mount <DeckAuthComponent />
    Component->>Component: User selects source & enters credentials
    Component->>API: Credentials (encrypted)
    API->>Vault: Encrypt & store
    API-->>Component: Credential created
    Component-->>Frontend: onSuccess({ credentialId })
```

1. **Your server** creates a session token via `POST /v2/token`
2. **Your frontend** mounts `<DeckAuthComponent />` with the token and all configuration
3. **The component** renders a secure UI (hosted at `auth.components.deck.co`) where the user selects a source and enters credentials
4. **Credentials** go from the component directly to the Deck API and into the Credential Vault. They never touch your page
5. **On success**, the component returns the new Credential ID via `onSuccess`

## Restrictions

* Collects credentials via `username_password` (default) or `source_fields` (opt in per source through [`sourceConfig`](#source-fields)). No other auth method is supported.
* Does not support chaining to tasks with inputs

## Installation

```bash theme={null}
npm install @deckco/auth
```

```tsx theme={null}
import { DeckAuthComponent } from '@deckco/auth';
```

React 18+ is a peer dependency.

## Quick example

Create a session token on your server:

<CodeGroup>
  ```typescript Node theme={null}
  const response = await fetch('https://api.deck.co/v2/token', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.DECK_SECRET_KEY}`,
    },
  });

  const { token } = await response.json();
  ```

  ```bash curl theme={null}
  curl -X POST https://api.deck.co/v2/token \
    -H "Authorization: Bearer $DECK_SECRET_KEY"
  ```

  ```python Python theme={null}
  import os, requests

  response = requests.post(
      "https://api.deck.co/v2/token",
      headers={"Authorization": f"Bearer {os.environ['DECK_SECRET_KEY']}"},
  )
  token = response.json()["token"]
  ```
</CodeGroup>

Mount the component in your React app:

```tsx Client theme={null}
<DeckAuthComponent
  token={token}
  sourceId="src_abc123"
  externalId="user_12345"
  taskId="task_x9y8z7..."
  onSuccess={({ credentialId, externalId, taskRunId, verified }) => {
    console.log('Credential created:', credentialId);
  }}
  onError={({ type, code, message }) => {
    console.error('Auth failed:', code, message);
  }}
  onClose={() => {
    // User dismissed the component. Unmount, navigate, etc.
  }}
/>
```

## Session token

Every auth component session starts with a server-side token. The token is authentication only. It proves the request came from your backend. All configuration (sourceId, appearance, etc.) is passed on the component, not the token.

### `POST /v2/token`

Creates a session token for the Auth Component.

**Authentication:** `Authorization: Bearer sk_live_...`

**Request body:** None

**Response (201):**

```json theme={null}
{
  "id": "tok_abc123...",
  "object": "token",
  "token": "tk_abc123...",
  "expires_at": "2026-04-10T12:30:00Z",
  "created_at": "2026-04-10T12:00:00Z",
  "request_id": "req_xyz789"
}
```

| Field        | Type     | Description                                              |
| ------------ | -------- | -------------------------------------------------------- |
| `id`         | string   | Token identifier, prefixed `tok_`                        |
| `object`     | string   | Always `token`                                           |
| `token`      | string   | The token value to pass to the component, prefixed `tk_` |
| `expires_at` | ISO 8601 | 30 minutes from creation                                 |
| `created_at` | ISO 8601 | Creation timestamp                                       |
| `request_id` | string   | Request ID for support                                   |

### Token lifecycle

* Tokens expire **30 minutes** after creation
* Expired tokens return a clear error to `onError`

## Component props

| Prop              | Type                           | Description                                                                                                                                                                                                                 |
| ----------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`           | `string`                       | **Required.** Session token from `POST /v2/token`.                                                                                                                                                                          |
| `mode`            | `"auth" \| "interaction"`      | `"auth"` (default) runs the full flow. `"interaction"` skips source selection and credential capture and runs a task directly. See [Interaction mode](#interaction-mode).                                                   |
| `sourceId`        | `string \| string[]`           | Pre-select a single source (skips picker) or filter to multiple.                                                                                                                                                            |
| `sourceConfig`    | `Record<string, SourceConfig>` | Extra [source fields](#source-fields) to collect per source, keyed by source ID (or `"*"` as a default).                                                                                                                    |
| `credentialId`    | `string`                       | If provided, enters [Update Mode](#update-mode). Re-collects credentials for an existing credential.                                                                                                                        |
| `externalId`      | `string`                       | Your user ID. Passed back in `onSuccess` and persisted on the credential.                                                                                                                                                   |
| `taskId`          | `string`                       | If provided, fires a verification [task run](#task-linking) after credential creation/update.                                                                                                                               |
| `appearance`      | `AppearanceConfig`             | Full theming control. See [Appearance](#appearance).                                                                                                                                                                        |
| `language`        | `"en-US" \| "fr-CA"`           | Language for all component-rendered copy. Defaults to `"en-US"`. See [Language](#language).                                                                                                                                 |
| `taskRunView`     | `"status" \| "live"`           | How the task run is displayed while executing. Defaults to `"status"`.                                                                                                                                                      |
| `onSuccess`       | `(data) => void`               | **Required.** Called when credential creation/update succeeds.                                                                                                                                                              |
| `onCancel`        | `(data) => void`               | Called when the flow is canceled. See [`onCancel`](#oncancel).                                                                                                                                                              |
| `onError`         | `(data) => void`               | Called on failure. See [Error handling](#error-handling).                                                                                                                                                                   |
| `onClose`         | `() => void`                   | Called when the user dismisses the flow — the close (X) icon, Escape, or a Cancel/Close button. Its presence also shows the X unless `showCloseButton` overrides it.                                                        |
| `showCloseButton` | `boolean`                      | Show or hide the persistent close (X) icon, independently of `onClose`. Defaults to `onClose != null`. Pass `onClose` together with `showCloseButton={false}` to hide the X while keeping the Cancel/Close buttons working. |

The component does not own its lifecycle. Your app does. When `onClose` fires, you decide what happens (unmount, navigate, show a confirmation, etc.).

## Callbacks

### `onSuccess`

```typescript theme={null}
onSuccess: (data: {
  credentialId: string;
  externalId: string | null;
  taskRunId: string | null;
  verified: boolean | null;
  sessionId: string | null;
}) => void;
```

| Field          | Type              | Description                                                                      |
| -------------- | ----------------- | -------------------------------------------------------------------------------- |
| `credentialId` | `string`          | The created or updated credential                                                |
| `externalId`   | `string \| null`  | Your external ID, if provided                                                    |
| `taskRunId`    | `string \| null`  | Verification task run ID. `null` if no `taskId` was passed                       |
| `verified`     | `boolean \| null` | `true` if run succeeded, `false` if run failed, `null` if no `taskId` was passed |
| `sessionId`    | `string \| null`  | Session ID of the verification run. `null` if no `taskId` was passed             |

### `onCancel`

```typescript theme={null}
onCancel: (data: {
  credentialId: string | null;
  taskRunId: string | null;
}) => void;
```

Fires when a verification task run is canceled out-of-band (for example, you call the [cancel endpoint](/api-reference/task-runs/cancel-a-task-run) while the run is in progress). This is not user dismissal of the component, which is [`onClose`](#onclose).

### `onError`

```typescript theme={null}
onError: (data: {
  type: string;
  code: string;
  message: string;
}) => void;
```

`type` is the error category. `code` is the machine-readable identifier. `message` is human-readable and may change. Switch on `type` and `code`, not `message`. See [Error handling](#error-handling) for all error codes.

### `onClose`

```typescript theme={null}
onClose: () => void;
```

`onClose` fires when the user dismisses the flow — via the persistent close (X) icon, the Escape key, or a Cancel/Close button on the canceled or error screen. You handle the teardown.

By default the X is shown whenever `onClose` is provided and hidden when it is omitted. To control the two independently — for example, to embed the component in your own modal that already has a close control — pass [`showCloseButton`](#showclosebutton):

```tsx theme={null}
// Hide the persistent X, but keep the Cancel/Close buttons working:
<DeckAuthComponent token={token} onClose={handleClose} showCloseButton={false} />
```

Omitting `onClose` entirely hides the X *and* leaves the Cancel/Close buttons with no handler, so prefer `showCloseButton={false}` when you still want those buttons to work.

If a task run is in progress when the user closes the component, the task run continues running in the background. To cancel it, use the [cancel endpoint](/api-reference/task-runs/cancel-a-task-run) from your `onClose` handler.

### `showCloseButton`

```typescript theme={null}
showCloseButton?: boolean;
```

Controls whether the persistent close (X) icon is rendered, independently of `onClose`. Defaults to `onClose != null`, so existing integrations are unaffected. Pass `onClose` together with `showCloseButton={false}` to hide the X while keeping the Cancel/Close buttons (which share the same close handler) functional.

## Source selection

The `sourceId` prop controls what the user sees before credential capture.

| `sourceId`                       | Behavior                                                                       |
| -------------------------------- | ------------------------------------------------------------------------------ |
| Not provided                     | Full source search. User searches across all sources in your organization.     |
| Single ID (`"src_abc123"`)       | Skips source selection. Goes straight to credential capture.                   |
| Array (`["src_abc", "src_def"]`) | Filtered selection. User picks from the provided sources in your organization. |

## Source fields

Some sources need values beyond username/password: an account number, a company ID, etc. The `sourceConfig` prop attaches **source fields** to a source's credential form, keyed by source ID (or `"*"` as a default for any source without its own entry). By default these fields are collected alongside username/password, but a source can also authenticate on source fields alone (see [Auth method](#auth-method)). Collected values are stored on the credential under [`auth_credentials.source_fields`](/concepts/credentials#source-fields); fields marked `tokenized` are [vaulted](/concepts/credentials#tokenizing-source-fields).

Only sources that need custom fields get an entry. Every other source renders the standard username/password form.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  sourceId="src_toronto_hydro"
  language="en-US"
  sourceConfig={{
    src_toronto_hydro: {
      fields: {
        account_number: {
          label: { 'en-US': 'Account number', 'fr-CA': 'Numéro de compte' },
          placeholder: 'On your bill, e.g. 100 123 456 7',
        },
        account_type: {
          label: 'Account type',
          type: 'select',
          options: [
            { value: 'residential', label: 'Residential' },
            { value: 'commercial', label: 'Commercial' },
          ],
        },
        customer_number: { label: 'Customer number', tokenized: true },
        // Plain string = silent pass-through: stored, never shown to the user.
        integration_source: 'partner_x',
      },
    },
  }}
  onSuccess={handleSuccess}
/>
```

Each field is either a **config object** (presented to the user) or a **plain string** (a silent pass-through: stored on the credential, never shown).

### Entry config

Each entry in `sourceConfig` is keyed by source ID (or `"*"`) and takes:

| Key          | Type                                     | Description                                                                                       |
| ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `fields`     | `Record<string, FieldConfig \| string>`  | **Required.** The source fields to collect. See [Field config](#field-config).                    |
| `authMethod` | `"username_password" \| "source_fields"` | How the source authenticates. Defaults to `"username_password"`. See [Auth method](#auth-method). |

### Auth method

`authMethod` controls whether the entry's [fields](#field-config) sit alongside username/password or replace them.

| Value                 | Behavior                                                                                                                                   |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `"username_password"` | Default. The component renders the standard username/password form, and the entry's fields are collected alongside it.                     |
| `"source_fields"`     | The component renders only the entry's fields. No username/password inputs appear, and the source authenticates on the field values alone. |

A `source_fields` entry must declare at least one field; one with none fails fast at mount with [`source_fields_auth_requires_fields`](#validation-errors), and an unknown `authMethod` fails with [`auth_method_invalid`](#validation-errors). If the source doesn't support the selected method, the flow fails at runtime with [`auth_method_not_supported`](#runtime-errors).

```tsx theme={null}
<DeckAuthComponent
  token={token}
  sourceId="src_acme_portal"
  sourceConfig={{
    src_acme_portal: {
      authMethod: 'source_fields',
      fields: {
        account_number: { label: 'Account number' },
        access_token: { label: 'Access token', tokenized: true },
      },
    },
  }}
  onSuccess={handleSuccess}
/>
```

### Field config

Every display string (`label`, `placeholder`, and each option `label`) is a `LocalizedString`: either a plain string, rendered as-is in every locale, or a `{ "en-US": string; "fr-CA"?: string }` map picked by [`language`](#language), falling back to `"en-US"`. The two forms can be mixed within one config.

| Key           | Type                                               | Description                                                                               |
| ------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `label`       | `LocalizedString`                                  | **Required.** Rendered exactly as provided; keys are never converted to labels.           |
| `type`        | `"string" \| "select" \| "boolean"`                | Defaults to `"string"`. `string` → text input, `select` → dropdown, `boolean` → checkbox. |
| `options`     | `Array<{ value: string; label: LocalizedString }>` | `select` only. The chosen `value` is stored.                                              |
| `placeholder` | `LocalizedString`                                  | `string` only.                                                                            |
| `value`       | `string \| boolean`                                | Prefill; the user can edit it.                                                            |
| `required`    | `boolean`                                          | Defaults to `true`.                                                                       |
| `tokenized`   | `boolean`                                          | `string` only. Masks the input and vaults the value (never returned on read).             |

### Behavior

* **Scoping** is per source: the entry matching the selected source applies, and `"*"` covers any source without its own entry.
* **Stored values** are always strings: selects store the option `value`, booleans store `"true"` / `"false"`. Option values and stored values are data and are never localized.
* **Reconnect** ([Update Mode](#update-mode)) prefills non-tokenized fields from the stored credential and keeps them editable. Tokenized fields render empty (their values are never returned) and are treated as optional even when `required`: leave one blank to keep the stored value, or re-enter it to change it, so users aren't forced to re-enter vaulted values.

<Note>
  Invalid `sourceConfig` fails fast: the component fires `onError` at mount with a `source_field_*` code rather than erroring at credential creation. See [Error handling](#error-handling).
</Note>

## Update mode

Pass a `credentialId` to re-collect credentials for an existing credential instead of creating a new one.

| `credentialId` | `sourceId` | Behavior                                                       |
| -------------- | ---------- | -------------------------------------------------------------- |
| absent         | absent     | Create mode. Full source search.                               |
| absent         | present    | Create mode. Skip/filter source selection.                     |
| present        | ignored    | Update mode. Skip source selection, patch existing credential. |

```tsx theme={null}
<DeckAuthComponent
  token={token}
  credentialId="cred_existing_456"
  externalId="user_12345"
  onSuccess={({ credentialId, externalId }) => {
    console.log('Credentials updated for:', credentialId);
  }}
/>
```

## Task linking

Pass a `taskId` to verify credentials immediately after creation. The component fires a [task run](/concepts/task-runs) using the new credential, and the task run view stays visible while it executes.

| `taskId` | Auth outcome | Run outcome            | Credential status | Callback                                                    |
| -------- | ------------ | ---------------------- | ----------------- | ----------------------------------------------------------- |
| absent   | n/a          | n/a                    | `unverified`      | `onSuccess` with `{ ..., taskRunId: null, verified: null }` |
| present  | succeeds     | run succeeds           | `verified`        | `onSuccess` with `{ ..., taskRunId, verified: true }`       |
| present  | succeeds     | run fails (no error)   | `verified`        | `onSuccess` with `{ ..., taskRunId, verified: false }`      |
| present  | succeeds     | run fails (with error) | `verified`        | `onError` with the run's error                              |
| present  | fails        | n/a                    | `invalid`         | `onError` with `auth_invalid`                               |

Credential status reflects whether the agent could authenticate, not whether the run finished successfully. As long as auth succeeds, the credential becomes `verified` even if the run later fails. A failed run does **not** delete or invalidate the credential. If auth itself fails, the credential becomes `invalid`.

<Tip>
  Do not rely on `onSuccess` with `verified: false` to detect a failed verification. When the run fails with an error (including an authentication failure, `auth_invalid`), the component fires `onError`, not `onSuccess`. `onSuccess` with `verified: false` only occurs for a run that failed without returning an error. Handle verification failures in both `onError` and the `verified` field of `onSuccess`.
</Tip>

<Warning>
  The task you pass as `taskId` must actually authenticate with the source. A task that only visits a public page will not challenge the credential, and running it will not move the credential from `unverified` to `verified`. Pick (or create) a task whose first step is logging in.
</Warning>

### `taskRunView`

With `taskId` present, the task run view is shown while the run executes:

* `"status"`: spinner with status text (e.g., "Connecting...", "Verifying credentials...")
* `"live"`: live view of the task run

Without `taskId`, no task run view is shown.

## Interaction mode

Set `mode="interaction"` to collect interaction inputs (MFA code, security question, MFA method) from the user during a task run against an existing `credentialId` or `sourceId`. The component skips source selection and credential capture, triggers the task run, and renders any pending [interaction](#interactions) in place.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  mode="interaction"
  credentialId="cred_abc123"
  taskId="task_x9y8z7..."
  taskRunView="status"
  onSuccess={({ taskRunId, verified }) => {
    // The run completed. Any interaction was submitted along the way.
  }}
  onError={({ type, code, message }) => {
    // e.g. task_id_required, credential_or_source_required
  }}
/>
```

### Required props

Interaction mode needs `taskId` **plus** one of:

| Provided               | Run type                                                                                                         |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `credentialId`         | Authenticated run against the existing credential.                                                               |
| `sourceId` (single ID) | No-auth, source-only run. An array `sourceId` does not identify one source and does not satisfy the requirement. |

Both are validated before the component mounts:

* Omitting `taskId` fires `onError` with `task_id_required`.
* Providing neither a `credentialId` nor a single `sourceId` fires `onError` with `credential_or_source_required`.

## Appearance

Customize the component's look via the `appearance` prop.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  appearance={{
    theme: 'dark',
    variables: {
      colorPrimary: '#0066FF',
      borderRadius: 8,
    },
    layout: {
      logoUrl: 'https://content.cdn.deck.co/logos/your-org/logo.png',
    },
  }}
  onSuccess={handleSuccess}
/>
```

### Theme

| Value       | Description                      |
| ----------- | -------------------------------- |
| `"default"` | Light theme with neutral styling |
| `"dark"`    | Dark theme                       |

### Variables

| Variable                 | Description                             |
| ------------------------ | --------------------------------------- |
| `colorPrimary`           | Primary button and accent color         |
| `colorBackground`        | Card background                         |
| `colorForeground`        | Main text color                         |
| `colorPrimaryForeground` | Text on primary buttons                 |
| `colorNeutral`           | Neutral accent                          |
| `colorDanger`            | Error states                            |
| `colorSuccess`           | Success states                          |
| `colorMuted`             | Muted backgrounds (hover, etc.)         |
| `colorMutedForeground`   | Secondary text                          |
| `colorBorder`            | Border color                            |
| `colorInput`             | Input border color                      |
| `colorRing`              | Focus ring color                        |
| `fontFamily`             | Font stack                              |
| `borderRadius`           | Border radius in pixels (e.g., `8`)     |
| `shadow`                 | Shadow token for card/elevated surfaces |

### Branding

| Variable        | Description                   |
| --------------- | ----------------------------- |
| `logoUrl`       | Logo shown in the card header |
| `logoPlacement` | `"top"` or `"left"`           |

Upload a logo in the Deck console under **Settings → Branding**. Deck hosts it and returns a CDN URL on `content.cdn.deck.co`. Pass that as `logoUrl`. Accepted formats are PNG, JPG, JPEG, GIF, and WebP (no SVG). The logo renders into a 40×40 box and is hidden if it fails to load.

<Note>
  For security, the component only loads images from Deck's content CDN, `data:` URIs, and `cdn.brandfetch.io`. Arbitrary external URLs (for example, `https://your-company.com/logo.png`) are blocked by its content security policy and will not render.
</Note>

## Language

Set the language for all component-rendered copy via the `language` prop.

```tsx theme={null}
<DeckAuthComponent
  token={token}
  sourceId="src_abc123"
  language="fr-CA"
  onSuccess={handleSuccess}
/>
```

| Value     | Description          |
| --------- | -------------------- |
| `"en-US"` | US English. Default. |
| `"fr-CA"` | Canadian French.     |

`language` applies to copy the component owns: source picker, credential capture labels and helper text, interaction prompts (generic labels and submit/continue buttons), in-UI error messages, the task run status view (`taskRunView: "status"`), and the close button and accessibility labels.

Content returned by the source itself (source names, interaction field labels, and other source-defined strings) is not translated by the component and is rendered as the source returns it.

## Interactions

Sources may require additional input mid-flow: an MFA code, a security question, an account selection. The component handles these automatically as **interactions**: a generic pause-and-prompt primitive.

When an interaction is required:

1. The component transitions to an interaction step and renders the fields from the source's response
2. The user fills in the fields and submits
3. The source validates. On accept, the flow continues
4. Some sources require multiple sequential interactions (e.g., MFA code then a security question)

The component does not enforce its own interaction timeout. Timeout windows are set by the source and handled by the backend. The user cannot go back to credential entry from an interaction step (credentials have already been created).

See [Interactions](/guides/interactions) for more on how Deck models interactions.

## Error handling

All errors surface through `onError` with a uniform `{ type, code, message }` shape. `code` falls into two groups by where it comes from.

### Validation errors

Raised by the component itself before any network request, from checking the token and the prop combination. These are deterministic: a given misconfiguration always produces the same code.

| Type      | Code                                 | Description                                                                                                                               |
| --------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `request` | `token_invalid`                      | Token is missing, malformed, or not an embed token (`tk_`). Also fires if the API later rejects the token as expired.                     |
| `request` | `task_id_required`                   | Interaction mode without a `taskId`.                                                                                                      |
| `request` | `credential_or_source_required`      | Interaction mode without a `credentialId` or a single `sourceId`.                                                                         |
| `request` | `source_field_key_invalid`           | A `sourceConfig` field key doesn't match `^[a-z][a-z0-9_]{0,63}$`.                                                                        |
| `request` | `source_field_key_reserved`          | A `sourceConfig` field key is a reserved name (`username` / `password`).                                                                  |
| `request` | `source_fields_limit_exceeded`       | A source declares more than 10 source fields.                                                                                             |
| `request` | `source_field_config_invalid`        | A `sourceConfig` field is malformed (bad `options`, `tokenized` on a non-string, or a `LocalizedString` map missing its `"en-US"` entry). |
| `request` | `auth_method_invalid`                | A `sourceConfig` entry sets an unknown `authMethod`.                                                                                      |
| `request` | `source_fields_auth_requires_fields` | A `sourceConfig` entry sets `authMethod: "source_fields"` but declares no fields.                                                         |

### Runtime errors

Surfaced by the Deck API and the source as the flow runs. The component forwards the API's `type`, `code`, and `message` (mapping a couple of generic not-found codes to the specific ones below). Treat this as the set you are most likely to encounter, not an exhaustive list, and handle any unrecognized `code` as a generic failure.

| Type          | Code                        | Description                                             |
| ------------- | --------------------------- | ------------------------------------------------------- |
| `auth`        | `auth_invalid`              | Credentials rejected by the source.                     |
| `auth`        | `auth_method_not_supported` | Source does not support the attempted auth method.      |
| `auth`        | `password_reset_required`   | Source is forcing a password reset.                     |
| `auth`        | `account_locked`            | Account is locked at the source.                        |
| `request`     | `credential_not_found`      | Update mode: the `credentialId` does not exist.         |
| `source`      | `source_not_found`          | The `sourceId` does not exist.                          |
| `source`      | `source_not_available`      | Source is temporarily unreachable.                      |
| `source`      | `source_error`              | Source returned an unexpected failure.                  |
| `interaction` | `interaction_timeout`       | Interaction window expired (source-defined).            |
| `request`     | `interaction_input_invalid` | Submitted input didn't match the declared field schema. |

### Error behavior

* **Validation errors** fire before the iframe mounts. Fix the token or props and remount.
* **Token expiry** (`token_invalid`) terminates the session. Create a new token and remount.
* **Credential and source errors** (`auth_invalid`, `account_locked`, etc.) terminate the session.
* **Interaction errors** (`interaction_timeout`) terminate the session. Wrong user input is not an error; the source emits another prompt and the user retries in place.

## Embed by URL

For clients that can't run the React SDK, load the component's URL in a `WebView` (for example, in a React Native app) or an `<iframe>` and pass configuration as query parameters:

```bash theme={null}
https://auth.components.deck.co/?token=tk_abc123&sourceId=src_abc123&externalId=user_12345
```

Each parameter does what its [prop](#component-props) does, and the flow is otherwise the same as the React component.

### URL parameters

| Parameter         | Maps to prop       | Notes                                                                                                                                                                                                                                                                                                        |
| ----------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `token`           | `token`            | **Required.** Without it the component stays idle.                                                                                                                                                                                                                                                           |
| `mode`            | `mode`             | `auth` (default) or `interaction`. `interaction` also needs `taskId` plus a `credentialId` or `sourceId`. See [Interaction mode](#interaction-mode).                                                                                                                                                         |
| `sourceId`        | `sourceId`         | Pass multiple by repeating the param (`?sourceId=a&sourceId=b`) or comma-separating (`?sourceId=a,b`).                                                                                                                                                                                                       |
| `sourceConfig`    | `sourceConfig`     | URL-encoded JSON. Subject to URL-length limits; for anything beyond a few small fields, send it over [postMessage](#configuring-via-postmessage) instead.                                                                                                                                                    |
| `credentialId`    | `credentialId`     | Enters [Update Mode](#update-mode). `sourceId` is ignored when set.                                                                                                                                                                                                                                          |
| `externalId`      | `externalId`       | Echoed back in the result.                                                                                                                                                                                                                                                                                   |
| `taskId`          | `taskId`           | Fires a verification [task run](#task-linking) after capture.                                                                                                                                                                                                                                                |
| `appearance`      | `appearance`       | URL-encoded JSON. See below.                                                                                                                                                                                                                                                                                 |
| `theme`           | `appearance.theme` | Shorthand for `appearance={{ theme }}`. Ignored if `appearance` is also provided.                                                                                                                                                                                                                            |
| `language`        | `language`         | `en-US` (default) or `fr-CA`.                                                                                                                                                                                                                                                                                |
| `taskRunView`     | `taskRunView`      | `status` (default) or `live`.                                                                                                                                                                                                                                                                                |
| `showCloseButton` | `showCloseButton`  | `true` renders the persistent close (X) icon and enables Escape-to-close. Defaults to `false` when omitted — there is no `onClose` callback over the hosted URL, so unlike the React prop it has no `onClose`-derived default. Dismissal is delivered as a [`CLOSE`](#receiving-results) message either way. |

### Appearance parameter

`appearance` takes the same [`AppearanceConfig`](#appearance) object as the prop, encoded as a URL-safe JSON string:

```js theme={null}
const url =
  'https://auth.components.deck.co/?token=tk_abc123' +
  '&appearance=' +
  encodeURIComponent(JSON.stringify({
    theme: 'default',
    layout: { logoUrl: 'https://content.cdn.deck.co/logos/your-org/logo.png' },
  }));
```

Malformed JSON is ignored and theming falls back to the default. For a theme-only change, use the `theme` shorthand instead.

### Examples

```bash theme={null}
# Show the source picker
https://auth.components.deck.co/?token=tk_abc123

# Pre-select a source, verify after capture, live view, French, with a close button
https://auth.components.deck.co/?token=tk_abc123&sourceId=src_abc123&taskId=task_x9y8z7&taskRunView=live&language=fr-CA&showCloseButton=true

# Update mode (re-collect an existing credential)
https://auth.components.deck.co/?token=tk_abc123&credentialId=cred_abc123&externalId=user_12345

# Interaction mode (run a task against an existing credential)
https://auth.components.deck.co/?token=tk_abc123&mode=interaction&credentialId=cred_abc123&taskId=task_x9y8z7
```

### Configuring via postMessage

URL parameters are subject to URL-length limits, so a large `sourceConfig` (or any object config you'd rather not URL-encode) is best delivered over `postMessage`, which has no encoding step and no length limit. The React component uses this same handshake internally.

Load the component with **no configuration in the URL** (just the bare origin), wait for the `READY` message, then post an `INIT` message with the full configuration object:

```js theme={null}
const AUTH_ORIGIN = 'https://auth.components.deck.co';
const iframe = document.getElementById('deck-auth'); // <iframe src="https://auth.components.deck.co/">

window.addEventListener('message', (e) => {
  if (e.origin !== AUTH_ORIGIN) return;
  if (e.data?.type === 'READY') {
    iframe.contentWindow.postMessage(
      {
        type: 'INIT',
        payload: {
          token: 'tk_abc123',
          sourceId: 'src_toronto_hydro',
          sourceConfig: {
            /* full object, no encoding, no length limit */
          },
          // Send the whole shape; use null for anything you're not setting.
          credentialId: null,
          externalId: null,
          taskId: null,
          appearance: null,
          taskRunView: 'status',
          language: 'en-US',
          showCloseButton: false,
          mode: 'auth',
        },
      },
      AUTH_ORIGIN,
    );
  }
  // Handle SUCCESS / ERROR / CLOSE here. See Receiving results below.
});
```

In a React Native `WebView`, run the same `window.postMessage({ type: 'INIT', ... })` (via `injectJavaScript`) once you receive `READY` through `onMessage`.

<Warning>
  Pick one channel. Don't put a `token` in the URL if you're going to `postMessage`: the component self-initializes from the URL first, and the two configurations would conflict. Configure via URL parameters **or** via `postMessage`, not both.
</Warning>

### Receiving results

Without React callbacks, the component delivers results as messages to the client:

* **React Native `WebView`:** messages arrive via `window.ReactNativeWebView.postMessage` as JSON strings. Read them with the `WebView`'s `onMessage` handler.
* **`<iframe>`:** the same messages arrive via `window.postMessage` to the parent frame.

Every message the component emits is forwarded, not only the terminal results. Switch on `type` and ignore any you don't handle:

| `type`         | Payload                                                        | Meaning                                                 |
| -------------- | -------------------------------------------------------------- | ------------------------------------------------------- |
| `SUCCESS`      | `{ credentialId, externalId, taskRunId, verified, sessionId }` | Credential created or updated.                          |
| `CANCELED`     | `{ credentialId, taskRunId }`                                  | Flow canceled.                                          |
| `ERROR`        | `{ type, code, message }`                                      | Failure. See [Error handling](#error-handling).         |
| `CLOSE`        | None                                                           | User dismissed the component.                           |
| `READY`        | None                                                           | Component loaded.                                       |
| `STEP_CHANGED` | `{ step }`                                                     | User moved through the flow.                            |
| `RESIZE`       | `{ height }`                                                   | Content height, for sizing the `WebView` or `<iframe>`. |

Each message is `{ type, payload }`:

```json theme={null}
{
  "type": "SUCCESS",
  "payload": {
    "credentialId": "cred_abc123",
    "externalId": "user_12345",
    "taskRunId": "run_xyz789",
    "verified": true,
    "sessionId": "sess_def456"
  }
}
```
