npm package: @polygonlabs/bpn-rest-api
Description: BPN REST API — OpenAPI spec, Zod schemas, and TypeScript client
Latest version: 0.3.0

# @polygonlabs/bpn-rest-api

TypeScript client, Zod schemas, and OpenAPI spec for the BPN (Borderless Payment Network) REST API.

## Installation

```bash
npm install @polygonlabs/bpn-rest-api
# or
pnpm add @polygonlabs/bpn-rest-api
```

Zod schemas and TanStack Query hooks are optional — install the relevant peer dependencies
only if you use those exports:

```bash
pnpm add zod                      # for ./zod
pnpm add @tanstack/react-query    # for ./query
```

## Exports

| Import path | Contents |
|---|---|
| `@polygonlabs/bpn-rest-api` | TypeScript types, SDK functions, and default `client` singleton |
| `@polygonlabs/bpn-rest-api/factory` | `createClient` and related types — for advanced use cases (see below) |
| `@polygonlabs/bpn-rest-api/query` | TanStack Query v5 options factories and query keys (requires `@tanstack/react-query`) |
| `@polygonlabs/bpn-rest-api/zod` | Zod schemas for every request/response model (requires `zod`) |
| `@polygonlabs/bpn-rest-api/openapi.json` | Raw OpenAPI 3.0 spec |

## Usage

### Configure and authenticate

The package exports a pre-built `client` singleton. Configure it once at startup — all SDK
functions use it by default so you never need to pass it explicitly.

```ts
import { client, authorize } from '@polygonlabs/bpn-rest-api';

// Point at your BPN instance
client.setConfig({ baseUrl: 'https://api.example.com/api/v1' });

// authorize wraps the generated SDK function — on success it automatically calls
// client.setConfig({ headers: { Authorization: 'Bearer <token>' } }) so you don't have to.
const { data } = await authorize({ body: { username: 'my-partner-id', password: 'my-secret' } });

// In never-throw mode both `data` and `error` are optional — check `data` to confirm success.
if (!data) throw new Error('Auth failed');
```

### Call API methods

With the singleton configured, all SDK functions work without passing `client`:

```ts
import { listCorridors, createCustomer } from '@polygonlabs/bpn-rest-api';

const { data: corridors } = await listCorridors();

const { data: customer } = await createCustomer({
  body: {
    email: 'alice@example.com',
    fullName: { firstName: 'Alice', lastName: 'Smith' },
    address: {
      line1: '123 Main St',
      city: 'New York',
      state: 'NY',
      postalCode: '10001',
      country: 'US',
    },
    phoneNumber: { countryCode: '+1', number: '5550100' },
    dateOfBirth: '1990-01-15',
    ssn: '123-45-6789',
  },
});
```

### TanStack Query

The `./query` export provides `queryOptions` and `infiniteQueryOptions` factories for every
endpoint, plus query key helpers for cache invalidation. List endpoints with cursor
pagination (`startingAfter`/`endingBefore`) get both standard and infinite variants.

```tsx
import { useQuery, useInfiniteQuery } from '@tanstack/react-query';
import { listCustomersOptions, listCustomersInfiniteOptions } from '@polygonlabs/bpn-rest-api/query';

// Standard query — fetches one page
function CustomerList() {
  const { data, error, isPending } = useQuery(listCustomersOptions());

  if (isPending) return <span>Loading…</span>;
  if (error) return <span>Error: {error.message}</span>;

  return <ul>{data?.data?.map(c => <li key={c.id}>{c.email}</li>)}</ul>;
}

// Infinite query — cursor-based pagination
function AllCustomers() {
  const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
    ...listCustomersInfiniteOptions(),
    getNextPageParam: (lastPage) => lastPage.data?.at(-1)?.id,
    initialPageParam: undefined,
  });

  return (
    <>
      {data?.pages.flatMap(p => p.data ?? []).map(c => (
        <div key={c.id}>{c.email}</div>
      ))}
      {hasNextPage && <button onClick={() => fetchNextPage()}>Load more</button>}
    </>
  );
}
```

### Validate responses with Zod

Zod schemas are exported with a `z` prefix matching each type name:

```ts
import { zCustomer } from '@polygonlabs/bpn-rest-api/zod';

const result = zCustomer.safeParse(customer);
if (!result.success) {
  console.error(result.error.issues);
}
```

### Use the raw OpenAPI spec

```ts
import spec from '@polygonlabs/bpn-rest-api/openapi.json' with { type: 'json' };

console.log(spec.info.version);
```

## Error handling

The client supports two error handling styles. The default is the **never-throw** pattern.

### Default: `{ data, error }` (never-throw)

Every SDK function returns `{ data, error }` and never throws, even on network failures or
non-2xx responses. This is the hey-api default — it makes the error path explicit at every
call site and avoids uncaught promise rejections when an `await` is not wrapped in
try/catch.

```ts
const { data: corridors, error } = await listCorridors();

if (error) {
  // handle error — data is undefined
} else {
  // use corridors — error is undefined
}
```

### Alternative: `throwOnError` (conventional async/await)

Set `throwOnError: true` via `client.setConfig()` and SDK functions throw on error instead
of returning `{ error }`. Use this if you prefer centralised error handling via try/catch
rather than checking `error` at every call site.

```ts
client.setConfig({ throwOnError: true });

try {
  const { data: corridors } = await listCorridors();
  // data is CorridorList — error is never present, an error would have thrown instead
} catch (err) {
  // handle error
}
```

`throwOnError` can also be set per-call to override the client default.

## Advanced: multiple client instances

The singleton covers most use cases. If you need isolated client instances — for example,
in a server-side context where each request carries a different auth token — use
`createClient` from the `./factory` export:

```ts
import { createClient } from '@polygonlabs/bpn-rest-api/factory';
import { listCorridors } from '@polygonlabs/bpn-rest-api';

const requestClient = createClient({
  baseUrl: 'https://api.example.com/api/v1',
  headers: { Authorization: `Bearer ${token}` },
});

const { data } = await listCorridors({ client: requestClient });
```

## License

MIT