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

# Installation

> Add @ozura/elements to your project via npm, yarn, or a CDN script tag.

## npm / yarn

<CodeGroup>
  ```bash npm theme={null}
  npm install @ozura/elements
  ```

  ```bash yarn theme={null}
  yarn add @ozura/elements
  ```
</CodeGroup>

<Info>
  **Pre-release builds (`@next`):** The `next` dist-tag contains unreleased changes before they reach `latest`. It is intended for Ozura-coordinated testing only — production credentials will not work with it. If you have been given access to pre-release testing, contact **[ammar@ozura.com](mailto:ammar@ozura.com)** for the matching staging credentials and setup instructions.

  ```bash theme={null}
  npm install @ozura/elements@next
  ```

  `npm install @ozura/elements` (no tag) always resolves `latest` — `@next` has no impact on production installs.
</Info>

Then import in your JavaScript or TypeScript:

```ts theme={null}
import { OzVault, OzError } from '@ozura/elements';
```

For React components:

```ts theme={null}
import {
  OzElements,
  useOzElements,
  OzCard,
  OzCardNumber,
  OzExpiry,
  OzCvv,
  OzBankCard,
  OzBankAccountNumber,
  OzBankRoutingNumber,
} from '@ozura/elements/react';
```

<Note>
  `@ozura/elements/react` requires **React 17 or later** as a peer dependency. If React is not already in your project, install it first:

  ```bash theme={null}
  npm install react react-dom
  ```
</Note>

For server-side usage (Node.js, Deno, Bun):

```ts theme={null}
import { Ozura, OzuraError, getClientIp, createCardSaleHandler, createSessionHandler } from '@ozura/elements/server';
```

***

## CDN (Script Tag)

<Warning>
  **The Ozura CDN blocks `localhost` origins.** If you load the CDN script on `http://localhost`, the browser will reject it with a CORS error (`No 'Access-Control-Allow-Origin' header`). The CDN is for deployed origins only. See [Local Development](#local-development) below.
</Warning>

If you're not using a bundler, load the UMD build directly from the Ozura CDN:

```html theme={null}
<script src="https://elements.ozura.com/oz-elements.umd.js"></script>
<script>
  const { OzVault } = window.OzElements;

  // Production: pubKey required (pk_live_… / pk_prod_…).
  // Test vault keys (from a Test project at ozuravault.com): omit pubKey.
  OzVault.create({
    pubKey:     'YOUR_PUB_KEY',
    sessionUrl: '/api/oz-session',
  }).then(vault => {
    // vault is ready
  });
</script>
```

Or use the ESM build with a native module script:

```html theme={null}
<script type="module">
  import { OzVault } from 'https://elements.ozura.com/oz-elements.esm.js';

  const vault = await OzVault.create({
    pubKey:     'YOUR_PUB_KEY',         // omit for test vault keys
    sessionUrl: '/api/oz-session',
  });
</script>
```

***

## Local Development

The CDN blocks `localhost`. For local development you have two options depending on how you load the SDK:

### npm install (recommended)

If your project uses a bundler (webpack, Vite, Next.js, etc.), `@ozura/elements` is imported directly from `node_modules` — no CDN involved, no workaround needed. The iframes load from `elements.ozura.com` at runtime, but that uses `<iframe src>` which is not subject to the same CORS restriction.

```bash theme={null}
npm install @ozura/elements
```

### CDN script tag on localhost

If you want to use the CDN `<script>` tag during local development, you need to serve the SDK file from the same origin as your page. Install the npm package and add a single route to your local server:

<CodeGroup>
  ```js Express theme={null}
  app.get('/oz-elements.esm.js', (_, res) =>
    res.sendFile('node_modules/@ozura/elements/dist/oz-elements.esm.js', { root: '.' })
  );
  ```

  ```js Next.js (pages/api) theme={null}
  // pages/api/oz-elements.js
  import { createReadStream } from 'fs';
  import path from 'path';

  export default function handler(req, res) {
    res.setHeader('Content-Type', 'application/javascript');
    createReadStream(
      path.resolve('node_modules/@ozura/elements/dist/oz-elements.esm.js')
    ).pipe(res);
  }
  ```
</CodeGroup>

Then change the `<script src>` in your HTML from:

```html theme={null}
<script src="https://elements.ozura.com/oz-elements.esm.js"></script>
```

to:

```html theme={null}
<script src="/oz-elements.esm.js"></script>
```

The iframes (`/frame/tokenizer-frame.js`, `/frame/element-frame.js`) still load from `elements.ozura.com` — they are fetched via `<iframe src>`, not `fetch`, so CORS does not apply.

***

## TypeScript

The package ships with full TypeScript definitions — no `@types` package needed. Import types directly:

```ts theme={null}
import type {
  OzVault,
  OzElement,
  OzError,
  VaultOptions,
  ElementOptions,
  ElementType,
  BankElementType,
  ElementStyleConfig,
  ElementChangeEvent,
  TokenizeOptions,
  TokenResponse,
  CardMetadata,
  BillingDetails,
  BillingAddress,
  BankTokenizeOptions,
  BankTokenResponse,
  BankAccountMetadata,
  FontSource,
  Appearance,
  AppearanceVariables,
  OzTheme,
  CardSaleRequest,
  CardSaleResponseData,
} from '@ozura/elements';
```

For server-side code:

```ts theme={null}
import type {
  OzuraConfig,
  CreateSessionOptions,
  CreateSessionResult,
  CardSaleInput,
  CardSaleHandlerOptions,
  ListTransactionsInput,
  ListTransactionsResult,
} from '@ozura/elements/server';

// MintWaxKeyOptions and MintWaxKeyResult are deprecated aliases for
// CreateSessionOptions and CreateSessionResult — both still work.
```

***

## Vault Project Types and the Pub Key

When you sign up at [ozuravault.com](https://ozuravault.com), your first project is a **test project** by default. To create a production project, open the project dropdown in the top-left, select **Create new project**, and enable the **Production** switch.

**Test vault API key (test project):**

No pub key is required. You can omit `pubKey` entirely from `OzVault.create()`. This is the easiest way to get started — create an application in your test project and use that key for local development and testing.

**Production vault API key:**

A pub key is required and is tied to your registered domain. Production pub keys have domain restrictions and **will not work on `localhost`**. Contact **[ammar@ozura.com](mailto:ammar@ozura.com)** to obtain a production pub key for your domain.

<Warning>
  If you're testing with a production vault API key on localhost, `OzVault.create()` will fail due to domain restrictions — and without a `try/catch`, the failure is silent. Use a test vault API key (test project) for local development instead.
</Warning>

<Note>
  **Frame updates are automatic and backward compatible.** The SDK always loads iframe files from `https://elements.ozura.com`. Updates are delivered transparently — no npm update is required. `frameBaseUrl` is a **local development option only** — do not set it in production code.
</Note>

***

## Credentials

**Which credentials do you need?**

| Goal                                         | Required credentials                                |
| -------------------------------------------- | --------------------------------------------------- |
| Tokenize card / bank data only (no charging) | Vault API key (+ pub key for production vault keys) |
| Charge cards via OzuraPay                    | All four credentials                                |

You need credentials from the [Ozura Dashboard](https://app.ozura.com).

| Credential           | Format                     | Where it lives                             | Required for                                            |
| -------------------- | -------------------------- | ------------------------------------------ | ------------------------------------------------------- |
| **Vault pub key**    | `pk_live_…` or `pk_prod_…` | Frontend env var (safe to expose)          | Production vault keys only — omit for test/sandbox keys |
| **Vault API key**    | `key_…`                    | Server env var **only — never in browser** | Creating sessions (all integrations)                    |
| **OzuraPay API key** | `ak_…`                     | Server env var only                        | OzuraPay merchants (card charging)                      |
| **Merchant ID**      | `ozu_…`                    | Server env var only                        | OzuraPay merchants (card charging)                      |

If you are not routing payments through OzuraPay you only need the vault API key (backend), plus a pub key if you are using a production vault key.

<Note>
  **Testing?** Use a test vault key (no pub key needed) and fill fields with a test card number. See [Test Credentials](/guides/test-credentials) for card numbers and expected outcomes.
</Note>

<Warning>
  The vault API secret must never be sent to the browser. The SDK's session mechanism exists specifically so your server can create a short-lived credential without exposing the secret. See the [Server SDK](/sdks/elements/server) for setup.
</Warning>

***

## Quick Initialization

`OzVault.create()` is the public factory method — it sets up the vault and initializes the payment session. It resolves once the session is ready; you can create and mount elements immediately after. The vault is ready to tokenize once `onReady` fires (or `vault.isReady === true`).

<Warning>
  **`onReady` can fire while `OzVault.create()` is still pending.** Vault initialization has two parallel steps — session fetch and iframe load. Either can finish first. If the iframe loads before the session resolves, `onReady` fires while `await OzVault.create(...)` is still in progress and `vault` is still `undefined`. **Do not reference `vault` inside `onReady`.** Declare any readiness flags before calling `create()` so they are in scope when the callback runs.
</Warning>

<Warning>
  Always wrap `OzVault.create()` in a `try/catch`. If initialization fails (e.g. pub key blocked, session endpoint unreachable, backend error) the promise rejects. Without a catch, the failure is a silent unhandled rejection and the fields appear to hang.
</Warning>

```ts theme={null}
import { OzVault, OzError } from '@ozura/elements';

let vault;
try {
  vault = await OzVault.create({
    pubKey:     'YOUR_PUB_KEY',         // omit for test vault keys
    sessionUrl: '/api/oz-session',
  });
} catch (err) {
  if (err instanceof OzError) {
    // err.errorCode: 'auth' | 'network' | 'timeout' | 'config' | ...
    console.error('Vault init failed:', err.message, err.errorCode);
  }
  // Render a fallback UI — payment fields are not available
  document.getElementById('payment-section').textContent =
    'Payment fields could not load. Please refresh and try again.';
}
```

`sessionUrl` is the simplest integration option — just pass your session endpoint path and the SDK handles everything. See [Server SDK — Session route](/sdks/elements/server#session-route) for the matching backend route.

To provide custom headers or auth tokens, use `getSessionKey` instead:

```ts theme={null}
let vault;
try {
  vault = await OzVault.create({
    pubKey: 'YOUR_PUB_KEY',             // omit for test vault keys
    getSessionKey: async (sessionId) => {
      const res = await fetch('/api/oz-session', {
        method:  'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
        body:    JSON.stringify({ sessionId }),
      });
      if (!res.ok) throw new Error(`Session endpoint returned ${res.status}`);
      const { sessionKey } = await res.json();
      return sessionKey;
    },
  });
} catch (err) {
  console.error('Vault init failed:', err);
}
```

***

## `OzVault.create()` Options

```ts theme={null}
OzVault.create(options: VaultOptions, signal?: AbortSignal): Promise<OzVault>
```

| Option             | Type                                     | Required | Default                      | Description                                                                                                                                                                                                                              |
| ------------------ | ---------------------------------------- | -------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pubKey`           | `string`                                 | ²        | —                            | Your Vault pub key                                                                                                                                                                                                                       |
| `sessionUrl`       | `string`                                 | ✅ ¹      | —                            | URL of your session endpoint — the simplest option                                                                                                                                                                                       |
| `getSessionKey`    | `(sessionId: string) => Promise<string>` | ✅ ¹      | —                            | Custom callback for session key retrieval (custom headers, auth, etc.)                                                                                                                                                                   |
| `fetchWaxKey`      | `(sessionId: string) => Promise<string>` | ✅ ¹      | —                            | **Deprecated** — use `sessionUrl` or `getSessionKey` instead                                                                                                                                                                             |
| `frameBaseUrl`     | `string`                                 | —        | `https://elements.ozura.com` | Override where iframe assets are served from. **Local development only** — do not set in production.                                                                                                                                     |
| `fonts`            | `FontSource[]`                           | —        | `[]`                         | Custom web fonts to load inside iframes                                                                                                                                                                                                  |
| `appearance`       | `Appearance`                             | —        | —                            | Global theme and CSS variable overrides                                                                                                                                                                                                  |
| `onLoadError`      | `() => void`                             | —        | —                            | Called if the vault fails to load within `loadTimeoutMs`                                                                                                                                                                                 |
| `loadTimeoutMs`    | `number`                                 | —        | `10000`                      | **Requires `onLoadError` to be set.** Setting this without `onLoadError` is silently ignored.                                                                                                                                            |
| `onSessionRefresh` | `() => void`                             | —        | —                            | Called when the SDK silently refreshes the session during a tokenization attempt                                                                                                                                                         |
| `onReady`          | `() => void`                             | —        | —                            | Called once when the vault is ready (vanilla JS only; in React use `useOzElements().ready`)                                                                                                                                              |
| `sessionLimit`     | `number \| null`                         | —        | `3`                          | Maximum card submissions per session before auto-refresh. Pass `null` to remove the cap. **Must match `sessionLimit` in your server-side `createSession` call** — see [Server SDK → createSession](/sdks/elements/server#createsession). |
| `debug`            | `boolean`                                | —        | `false`                      | Enables `[OzVault]`-prefixed `console.log` output at every lifecycle event. Safe in production — no sensitive data is logged.                                                                                                            |

¹ Exactly one of `sessionUrl`, `getSessionKey`, or `fetchWaxKey` is required.

² `pubKey` is required when using a **production** vault API key. If your vault API key was created for the test/sandbox environment, you can omit `pubKey` entirely — see [Pub Key for Local Development](#pub-key-for-local-development).

<Warning>
  **Keep `sessionLimit` in sync between client and server.** If `VaultOptions.sessionLimit` on the client differs from the value passed to `ozura.createSession()` on your server, the proactive refresh timing will be off. The vault may reject a tokenization attempt before the client expects a refresh, causing a user-visible delay. Both default to `3` — only change one if you change the other.
</Warning>

The optional `signal` parameter is an `AbortSignal` for advanced teardown scenarios. The React `OzElements` provider handles this automatically — vanilla JS integrations rarely need it.

***

## Content Security Policy (CSP)

If your site sets a `Content-Security-Policy` header, you need to allow the Ozura iframe and tokenizer origins. Without this the iframes are blocked before they load and `OzVault.create()` will reject.

Add these directives to your CSP:

```
frame-src   https://elements.ozura.com;
connect-src https://elements.ozura.com;
```

If you use `frameBaseUrl` to point at a custom or staging deployment, substitute that origin for `https://elements.ozura.com`.

**If you load custom fonts via the `fonts` option** (e.g. Google Fonts), add the font CDN origins too:

```
style-src  'self' https://fonts.googleapis.com;
font-src   'self' https://fonts.gstatic.com;
```

**Full header example (Express):**

```js theme={null}
app.use((_, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    [
      "default-src 'self'",
      "script-src  'self' 'nonce-YOURNONCE'",
      "frame-src   'self' https://elements.ozura.com",
      "connect-src 'self' https://elements.ozura.com",
      // Add the two lines below only if using fonts: [{ cssSrc: 'https://fonts.googleapis.com/...' }]
      "style-src   'self' https://fonts.googleapis.com",
      "font-src    'self' https://fonts.gstatic.com",
    ].join('; ')
  );
  next();
});
```

<Note>
  The `connect-src` entry covers requests made by the Ozura iframe for tokenization.
</Note>

***

## Next.js App Router

When using Next.js with the App Router (`app/` directory), the vault and card fields require a **client component** boundary because they use browser APIs.

<Note>
  The examples below show a production setup with a `pubKey`. If you are integrating against a **test vault key** (from a Test project at ozuravault.com), omit `pubKey` from `OzVault.create()` and the `<OzElements>` provider — no pub key is required.
</Note>

### Client component wrapper

```tsx theme={null}
// app/checkout/CardForm.tsx
'use client';

import { useEffect, useRef } from 'react';
import { OzVault, OzError } from '@ozura/elements';

export function CardForm() {
  const cardNumberRef = useRef<HTMLDivElement>(null);
  const expiryRef     = useRef<HTMLDivElement>(null);
  const cvvRef        = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let vault: OzVault | null = null;

    OzVault.create({
      pubKey:     process.env.NEXT_PUBLIC_VAULT_PUB_KEY!,
      sessionUrl: '/api/oz-session',
    })
      .then(v => {
        vault = v;
        v.createElement('cardNumber').mount(cardNumberRef.current!);
        v.createElement('expirationDate').mount(expiryRef.current!);
        v.createElement('cvv').mount(cvvRef.current!);
      })
      .catch(err => console.error('Vault init failed:', err));

    return () => { vault?.destroy(); };
  }, []);

  return (
    <>
      <div ref={cardNumberRef} />
      <div ref={expiryRef} />
      <div ref={cvvRef} />
    </>
  );
}
```

Or use the React provider — this is the recommended approach and handles lifecycle automatically:

```tsx theme={null}
// app/checkout/CardForm.tsx
'use client';

import { OzElements, OzCard, useOzElements } from '@ozura/elements/react';

function CheckoutForm() {
  const { createToken, ready } = useOzElements();
  return (
    <form onSubmit={async (e) => {
      e.preventDefault();
      const { token, cvcSession } = await createToken();
      // send token + cvcSession to your backend
    }}>
      <OzCard />
      <button type="submit" disabled={!ready}>Pay</button>
    </form>
  );
}

export function CardForm() {
  return (
    <OzElements
      pubKey={process.env.NEXT_PUBLIC_VAULT_PUB_KEY!}
      sessionUrl="/api/oz-session"
    >
      <CheckoutForm />
    </OzElements>
  );
}
```

### Session route (`app/api/oz-session/route.ts`)

```ts theme={null}
// app/api/oz-session/route.ts
import { Ozura, createSessionHandler } from '@ozura/elements/server';

const ozura = new Ozura({
  vaultKey: process.env.VAULT_API_KEY!,
});

export const POST = createSessionHandler(ozura);
```

### Environment variables (`.env.local`)

```env theme={null}
NEXT_PUBLIC_VAULT_PUB_KEY=pk_live_...
VAULT_API_KEY=key_...
MERCHANT_API_KEY=ak_...
MERCHANT_ID=ozu_...
```

`NEXT_PUBLIC_` prefix exposes the pub key to the browser bundle. All other credentials stay server-side only.

***

## Production Checklist

Before you go live, confirm each of the following:

**Credentials**

* [ ] Production pub key registered to your domain (contact **[ammar@ozura.com](mailto:ammar@ozura.com)**)
* [ ] `VAULT_API_KEY` set in server environment — **never** in browser code or committed to git
* [ ] OzuraPay merchants: `MERCHANT_API_KEY` and `MERCHANT_ID` also set server-side

**Session route**

* [ ] `POST /api/oz-session` (or your equivalent path) is deployed and reachable
* [ ] Route uses `createSessionHandler` / `createSessionMiddleware` or manually calls `ozura.createSession()`
* [ ] `sessionLimit` on the server matches `sessionLimit` in `VaultOptions` (both default to `3` — only change one if you change the other)
* [ ] Session route is CSRF-safe (the SDK helpers enforce `POST` + `Content-Type: application/json`)

**Security**

* [ ] CSP includes `frame-src` and `connect-src` for `https://elements.ozura.com` (see [CSP](#content-security-policy-csp) above)
* [ ] Charge route reads `amount` from your database, not from the request body
* [ ] `getClientIp` used (or equivalent) when calling `ozura.cardSale()` so the OzuraPay API gets the real client IP

**Error handling**

* [ ] `OzVault.create()` is wrapped in `try/catch` with a fallback UI
* [ ] Payment form disables the submit button until `vault.isReady` and all field `'ready'` events fire
* [ ] `vault.reset()` is called on tokenization error so the customer can re-enter card data
* [ ] `frameBaseUrl` is not set in production code (local development option only)

**Testing**

* [ ] Tested end-to-end on localhost using a test vault API key (test project) — no pub key needed
* [ ] If using a production vault API key: tested with the production pub key on your staging/preview domain before going live

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Card Elements" icon="credit-card" href="/sdks/elements/card-elements">
    Mount card number, expiry, and CVV fields.
  </Card>

  <Card title="Bank Elements" icon="landmark" href="/sdks/elements/bank-elements">
    Mount account number and routing number fields.
  </Card>

  <Card title="React" icon="atom" href="/sdks/elements/react">
    OzElements provider and pre-built components.
  </Card>

  <Card title="Server SDK" icon="server" href="/sdks/elements/server">
    Process payments and query transactions on your backend.
  </Card>
</CardGroup>
