
# Use the library

The npm package that ships the CLI also exports the client the CLI is built on. Use it in workers, scheduled jobs and any Node.js code that needs Zoho Books.

```bash
npm install @finopsbricks/fob-zb
```

```js
import { fobZb } from '@finopsbricks/fob-zb';

const zb = fobZb({
  client_id: process.env.FOB_ZB_CLIENT_ID,
  client_secret: process.env.FOB_ZB_CLIENT_SECRET,
  refresh_token: process.env.FOB_ZB_REFRESH_TOKEN,
  organization_id: process.env.FOB_ZB_ORGANIZATION_ID,
  region: process.env.FOB_ZB_REGION, // optional, default 'com'
});

const { data: overdue, page_context } = await zb.invoices.list({ status: 'overdue' });
const everyOpenBill = await zb.bills.getAll({ status: 'open' });
const invoice = await zb.invoices.get(overdue[0].invoice_id);
```

The package is an ES module and needs Node.js 18 or later.

## Credentials

The library never reads environment variables or the CLI's config file. You pass credentials in, which keeps a worker's credentials explicit.

| Field | Required | Notes |
| --- | --- | --- |
| `client_id`, `client_secret` | Yes | From your Self Client ([how](/docs/zoho-books/zoho-credentials)) |
| `refresh_token` | Yes | The quickest way to get one is with the CLI: add a profile, then copy `refresh_token` from `~/.fob/fob-zb/config.yml` |
| `organization_id` | Yes | From `fob-zb organizations list`, or `zb.organizations.list()` |
| `region` | No | `com` (default), `eu`, `in`, `com.au`, `jp`, `ca`, `com.cn` or `sa`. See [Regions](/docs/zoho-books/regions) |
| `persistToken` | No | A callback that receives `{ access_token, access_token_expires_at, api_domain }` after each refresh, if you want to cache the access token |

Access tokens last an hour. The client refreshes them from the refresh token when needed, and retries a request once if Zoho reports the token as invalid.

Several organizations means several clients: `fobZb(credsA)` and `fobZb(credsB)`. They can share one refresh token ([why](/docs/zoho-books/orgs-and-tokens)).

## Resources

Each resource is a property on the client, named in camelCase: `invoices`, `bills`, `contacts`, `bankTransactions`, `chartOfAccounts`, `vendorPayments` and so on, covering the same 27 resources as the CLI.

| Method | Returns |
| --- | --- |
| `list(params)` | One page: `{ data, page_context }`. `params` are Zoho's query parameters, for example `{ status: 'overdue', page: 2 }` |
| `getAll(params)` | Every row, following pagination (up to 15,000 rows) |
| `get(id)` | One record, or `null` |
| `create(body)`, `update(id, body)`, `delete(id)` | On resources with writes. `body` uses Zoho's field names |
| Actions such as `invoices.markSent(id)`, `invoices.email(id, body)`, `bankTransactions.exclude(id)` | On resources that have them |

Request and response fields are Zoho's own. See the [Zoho Books API reference](https://www.zoho.com/books/api/v3/). Every function has JSDoc types, so your editor autocompletes them.

## Errors

| Error | When |
| --- | --- |
| `OAuthError` | Token exchange or refresh failed. `code` is Zoho's error, such as `invalid_grant`. The message links to [troubleshooting](/docs/zoho-books/troubleshooting) |
| `ApiError` | Zoho rejected the request. `code` is Zoho's application error code and `status` is the HTTP status |

Rate limits (HTTP 429) are retried automatically, up to three times, honouring `Retry-After`.

```js
import { fobZb, ApiError, OAuthError } from '@finopsbricks/fob-zb';

try {
  await zb.invoices.markSent(id);
} catch (err) {
  if (err instanceof OAuthError) alertOps('Zoho connection needs re-authorising', err);
  else if (err instanceof ApiError) log.warn({ code: err.code }, err.message);
  else throw err;
}
```

## Lower-level helpers

For endpoints fob-zb doesn't wrap yet, the package also exports `createTransport`, plus the OAuth helpers `exchangeGrantCode`, `refreshAccessToken`, `revokeRefreshToken`, `regionOf` and `apiConsoleUrl`.
