> ## Documentation Index
> Fetch the complete documentation index at: https://loyalty-interchange.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> The idiomatic application interface for LIP

`@loyalty-interchange/sdk` validates requests before network I/O, validates successful responses, creates protocol context automatically, and exposes stable typed errors.

## Client

```ts theme={null}
import { LipClient } from "@loyalty-interchange/sdk";

const lip = new LipClient({
  baseUrl: "https://loyalty.example.com",
  apiKey: process.env.LIP_API_KEY!,
  source: { system: "restaurant-ordering", instance: "production" }
});

const member = await lip.members.enroll({
  program_id: "brand-program",
  identity: { type: "token", value: guestToken }
});
```

The client supplies `protocol_version`, `profile`, `request_id`, `idempotency_key`, `occurred_at`, and `source` on every operation.

## Operations

```ts theme={null}
lip.discover();
lip.capabilities();
lip.programs.get(...);
lip.accounts.get(...);
lip.ledger.list(...);
lip.members.lookup(...);
lip.members.enroll(...);
lip.orders.evaluate(...);
lip.orders.adjust(...);
lip.accruals.post(...);
lip.redemptions.reserve(...);
lip.redemptions.capture(...);
lip.redemptions.reverse(...);
```

Reads use bounded retries for network errors, HTTP 429, and server errors. Mutations are never silently retried. When retrying a mutation after losing the response, supply the same business identifier and `idempotencyKey`:

```ts theme={null}
await lip.accruals.post(
  { member_id: memberId, order },
  { idempotencyKey: `accrual:${order.order_id}` }
);
```

Render a member's loyalty home in one round of parallel reads:

```ts theme={null}
const [{ program }, account, history] = await Promise.all([
  lip.programs.get({ program_id: programId }),
  lip.accounts.get({ program_id: programId, member_id: memberId }),
  lip.ledger.list({ program_id: programId, member_id: memberId, limit: 25 })
]);
```

## Idempotent retries

To retry a mutation safely, reuse the same `idempotencyKey` across attempts:

```ts theme={null}
const opts = { idempotencyKey: `${orderId}-accrue` };
await lip.accruals.post(request, opts);   // first attempt
await lip.accruals.post(request, opts);   // retry — returns the original result
```

You do not need to pin `request_id` or `occurred_at`; the provider treats the business payload as the request identity and echoes your retry's `request_id` on the replayed response. Reusing a key with a different business payload returns `409 idempotency_conflict`.

## Errors

| Error                | Meaning                                                                                                       |
| -------------------- | ------------------------------------------------------------------------------------------------------------- |
| `LipValidationError` | Local request or successful server response did not match the negotiated contract; includes JSON-path issues. |
| `LipApiError`        | Server returned RFC 9457 problem details; includes HTTP status, stable code, and the original problem.        |
| `LipTransportError`  | Request could not be completed after allowed retries.                                                         |

## Exact money

```ts theme={null}
import { money, moneyFromDecimal, formatMoney } from "@loyalty-interchange/sdk";

money(1234, "USD");               // 1234 minor units
moneyFromDecimal("12.34", "USD"); // exact conversion, no binary float
formatMoney({ amount: 1234, currency: "USD" });
```

Decimal conversion rejects excess precision instead of silently rounding.

## Restaurant order builder

`FoodserviceOrderBuilder` calculates line subtotals and order totals, allocates required zero values, and runs full foodservice reconciliation before returning an order. Invalid parent lines, currencies, quantities, and paid tenders fail locally.

Run the complete example against a local sandbox:

```bash theme={null}
npm run example:sdk
```

## Generated low-level client

Use the generated client when an adapter needs direct access to the OpenAPI operations and is prepared to construct complete protocol requests:

```ts theme={null}
import { createLipOpenApiClient } from "@loyalty-interchange/sdk";

const openapi = createLipOpenApiClient({
  baseUrl: "https://loyalty.example.com",
  apiKey: process.env.LIP_API_KEY!
});

const health = await openapi.getHealth();
```

Most applications should prefer `LipClient` for context, validation, safe retries, and domain-oriented methods.

## Webhook verification

Verify the raw body before calling `JSON.parse`:

```ts theme={null}
import { verifyWebhook } from "@loyalty-interchange/sdk";

await verifyWebhook({
  payload: rawRequestBody,
  secret: process.env.LIP_WEBHOOK_SECRET!,
  timestamp: request.headers["lip-webhook-timestamp"],
  signature: request.headers["lip-webhook-signature"]
});
```

Verification checks timestamp tolerance and all `v1` signatures using constant-time comparison. See [Webhooks](/guides/webhooks) for delivery semantics.
