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

# JavaScript client

> Use the typed Boxpressd API client in ESM and CommonJS Node.js applications.

`@boxpressd/api-client` is the official server-side TypeScript and JavaScript client for the Boxpressd Developer API. It provides typed REST and GraphQL helpers, OAuth forwarding, billing attribution, response metadata, timeouts, and typed errors.

Version `0.1.4` and later supports both ECMAScript modules (ESM) and CommonJS (CJS). The package requires Node.js 18 or newer.

<Warning>
  Keep the developer key and this client on a trusted server. Do not include them in browser, Expo, React Native, or other distributable application code.
</Warning>

## Install the package

Install the client and its `graphql` peer dependency:

<CodeGroup>
  ```bash npm theme={null}
  npm install @boxpressd/api-client graphql
  ```

  ```bash pnpm theme={null}
  pnpm add @boxpressd/api-client graphql
  ```

  ```bash Yarn theme={null}
  yarn add @boxpressd/api-client graphql
  ```
</CodeGroup>

## Choose a module system

The package uses conditional exports. Node and compatible bundlers select the correct build from the package-root import.

| Your application | File or package setting | Boxpressd syntax |
| - | - | - |
| ESM | `.mjs` or `"type": "module"` | `import { BoxpressdClient } from "@boxpressd/api-client"` |
| CommonJS | `.cjs`, or `.js` without `"type": "module"` | `const { BoxpressdClient } = require("@boxpressd/api-client")` |

The package publishes shared TypeScript declarations, the ESM runtime for the `import` condition, and the CommonJS runtime for the `require` condition. Import from `@boxpressd/api-client`; internal `dist` paths are not public exports.

### TypeScript configuration

TypeScript applications can use normal named imports in either output mode. Set the compiler module mode to match the Node.js application.

<CodeGroup>
  ```json ESM tsconfig.json theme={null}
  {
    "compilerOptions": {
      "target": "ES2022",
      "module": "NodeNext",
      "moduleResolution": "NodeNext",
      "strict": true
    }
  }
  ```

  ```json CommonJS tsconfig.json theme={null}
  {
    "compilerOptions": {
      "target": "ES2022",
      "module": "CommonJS",
      "moduleResolution": "Node",
      "strict": true
    }
  }
  ```
</CodeGroup>

For ESM output, set `"type": "module"` in your application’s `package.json` or use `.mts`. For CommonJS output, set `"type": "commonjs"`, omit `type`, or use `.cts`. TypeScript compiles the named import to `require()` for CommonJS output while preserving the package’s types.

## Create a client

Store your key in `BOXPRESSD_DEVELOPER_API_KEY`, then create one client for your backend process.

<CodeGroup>
  ```javascript ESM theme={null}
  import { BoxpressdClient } from "@boxpressd/api-client";

  export const boxpressd = new BoxpressdClient({
    apiKey: process.env.BOXPRESSD_DEVELOPER_API_KEY,
  });
  ```

  ```javascript CommonJS theme={null}
  const { BoxpressdClient } = require("@boxpressd/api-client");

  const boxpressd = new BoxpressdClient({
    apiKey: process.env.BOXPRESSD_DEVELOPER_API_KEY,
  });

  module.exports = { boxpressd };
  ```
</CodeGroup>

The client uses `https://api.boxpressd.com` and a 15-second timeout by default. When you set `apiBaseUrl`, the GraphQL URL defaults to `${apiBaseUrl}/graphql`.

```javascript theme={null}
const boxpressd = new BoxpressdClient({
  apiKey: process.env.BOXPRESSD_DEVELOPER_API_KEY,
  apiBaseUrl: "https://dev-api.boxpressd.com",
  timeoutMs: 20_000,
});
```

## Search with REST

REST helpers return the API response body, including its `data` and pagination fields.

<CodeGroup>
  ```javascript ESM theme={null}
  import { BoxpressdClient } from "@boxpressd/api-client";

  const boxpressd = new BoxpressdClient({
    apiKey: process.env.BOXPRESSD_DEVELOPER_API_KEY,
  });

  const result = await boxpressd.searchCigars({
    query: "Liga Privada No. 9",
    first: 5,
  });

  for (const cigar of result.data) {
    console.log(cigar.id, cigar.brandName, cigar.name);
  }
  ```

  ```javascript CommonJS theme={null}
  const { BoxpressdClient } = require("@boxpressd/api-client");

  const boxpressd = new BoxpressdClient({
    apiKey: process.env.BOXPRESSD_DEVELOPER_API_KEY,
  });

  async function main() {
    const result = await boxpressd.searchCigars({
      query: "Liga Privada No. 9",
      first: 5,
    });

    for (const cigar of result.data) {
      console.log(cigar.id, cigar.brandName, cigar.name);
    }
  }

  main().catch(console.error);
  ```
</CodeGroup>

Common REST helpers include `searchCigars`, `getCigar`, `resolveCigar`, `getProduct`, `lookupBarcode`, and `getMe`.

## Query GraphQL

GraphQL helpers return the GraphQL `data` object directly.

<CodeGroup>
  ```javascript ESM theme={null}
  import { BoxpressdClient } from "@boxpressd/api-client";

  const boxpressd = new BoxpressdClient({
    apiKey: process.env.BOXPRESSD_DEVELOPER_API_KEY,
  });

  const result = await boxpressd.searchCigarsGraphQL({
    query: "Liga Privada No. 9",
    first: 5,
  });

  for (const { node } of result.searchCigars.edges) {
    console.log(node.id, node.brand?.name, node.name);
  }
  ```

  ```javascript CommonJS theme={null}
  const { BoxpressdClient } = require("@boxpressd/api-client");

  const boxpressd = new BoxpressdClient({
    apiKey: process.env.BOXPRESSD_DEVELOPER_API_KEY,
  });

  async function main() {
    const result = await boxpressd.searchCigarsGraphQL({
      query: "Liga Privada No. 9",
      first: 5,
    });

    for (const { node } of result.searchCigars.edges) {
      console.log(node.id, node.brand?.name, node.name);
    }
  }

  main().catch(console.error);
  ```
</CodeGroup>

Use `graphql()` when you need a query that does not have a dedicated helper. It accepts a GraphQL string or a `DocumentNode` from the `graphql` package.

## Call AI endpoints

Use `request()` for API routes without a dedicated helper. The client adds `x-boxpressd-key`; you supply the paid operation’s idempotency key.

<CodeGroup>
  ```javascript ESM theme={null}
  import { randomUUID } from "node:crypto";
  import { BoxpressdClient } from "@boxpressd/api-client";

  const boxpressd = new BoxpressdClient({
    apiKey: process.env.BOXPRESSD_DEVELOPER_API_KEY,
  });

  const match = await boxpressd.request("/v1/matches", {
    method: "POST",
    headers: { "Idempotency-Key": randomUUID() },
    body: JSON.stringify({
      imageUrl: process.env.CIGAR_BAND_IMAGE_URL,
      researchMode: "auto",
      suggestionLimit: 5,
    }),
  });

  console.log(match);
  ```

  ```javascript CommonJS theme={null}
  const { randomUUID } = require("node:crypto");
  const { BoxpressdClient } = require("@boxpressd/api-client");

  const boxpressd = new BoxpressdClient({
    apiKey: process.env.BOXPRESSD_DEVELOPER_API_KEY,
  });

  async function main() {
    const match = await boxpressd.request("/v1/matches", {
      method: "POST",
      headers: { "Idempotency-Key": randomUUID() },
      body: JSON.stringify({
        imageUrl: process.env.CIGAR_BAND_IMAGE_URL,
        researchMode: "auto",
        suggestionLimit: 5,
      }),
    });

    console.log(match);
  }

  main().catch(console.error);
  ```
</CodeGroup>

See [AI and cigar matching](/developer-api/ai) for endpoint inputs, credit behavior, and safe retry rules.

## Check billing

The client includes helpers for the non-billable billing reads:

```javascript theme={null}
const status = await boxpressd.getBillingStatus();
const usage = await boxpressd.getBillingUsage();
const plans = await boxpressd.listBillingPlans();
const creditPacks = await boxpressd.listCreditPacks();
```

Pass a stable opaque `endUserId` from your backend when you want billing usage attributed to one of your application users:

```javascript theme={null}
const status = await boxpressd.getBillingStatus({
  endUserId: internalCustomer.id,
});
```

The API hashes this value before storage. It does not authenticate or authorize a Boxpressd user.

## Handle errors

The same named error classes are available from both module formats.

<CodeGroup>
  ```javascript ESM theme={null}
  import {
    BoxpressdGraphQLError,
    BoxpressdHttpError,
    BoxpressdTimeoutError,
  } from "@boxpressd/api-client";

  try {
    await boxpressd.getCigar("cigar_k9P4m");
  } catch (error) {
    if (error instanceof BoxpressdHttpError) {
      console.error(error.status, error.requestId, error.body);
    } else if (error instanceof BoxpressdGraphQLError) {
      console.error(error.status, error.requestId, error.errors);
    } else if (error instanceof BoxpressdTimeoutError) {
      console.error(error.timeoutMs);
    }
  }
  ```

  ```javascript CommonJS theme={null}
  const {
    BoxpressdGraphQLError,
    BoxpressdHttpError,
    BoxpressdTimeoutError,
  } = require("@boxpressd/api-client");

  async function main() {
    try {
      await boxpressd.getCigar("cigar_k9P4m");
    } catch (error) {
      if (error instanceof BoxpressdHttpError) {
        console.error(error.status, error.requestId, error.body);
      } else if (error instanceof BoxpressdGraphQLError) {
        console.error(error.status, error.requestId, error.errors);
      } else if (error instanceof BoxpressdTimeoutError) {
        console.error(error.timeoutMs);
      }
    }
  }

  main().catch(console.error);
  ```
</CodeGroup>

Use `requestWithResponse()` or `graphqlWithResponse()` when you also need response headers, the request ID, rate-limit metadata, or GraphQL cost.

## Troubleshoot module loading

| Symptom | Fix |
| - | - |
| `require is not defined in ES module scope` | Use `import`, rename the file to `.cjs`, or remove `"type": "module"` from the nearest package when the application is CommonJS. |
| `Cannot use import statement outside a module` | Rename the file to `.mjs` or set `"type": "module"` in the application package. |
| A deep `dist/...` import fails | Import from `@boxpressd/api-client` so conditional exports can select the build. |
| An older lockfile resolves an ESM-only release | Update `@boxpressd/api-client` to `0.1.4` or later and reinstall dependencies. |
