## Prezly JavaScript SDK

### Getting started

```sh
npm install --save @prezly/sdk
# or yarn
yarn add @prezly/sdk
```

### Using the code

Using ES Modules:

```js
import { createPrezlyClient } from '@prezly/sdk';

const prezlyClient = createPrezlyClient({
    accessToken: 'your-access-token',
});
```

Or Using CommonJS:

```js
const { createPrezlyClient } = require('@prezly/sdk');

const prezlyClient = createPrezlyClient({
    accessToken: 'your-access-token',
});
```

### Bulk contact tags with personal API tokens

`contacts.bulkTag()` and `contacts.bulkUntag()` wait for a background job by polling
`/v2/jobs/:id`. Personal API tokens cannot access that endpoint. Use the explicit
submission methods when authenticating with a personal API token:

```js
const response = await prezlyClient.contacts.bulkTagWithoutPolling(
    { query: { id: { $in: [42, 43] } } },
    ['Customer'],
);

if (response.status === 202) {
    // Accepted for background processing; this token cannot poll job completion.
    console.log(response.payload.progress.id);
}
```

`bulkUntagWithoutPolling()` has the same contract. An explicit authorization or
validation rejection throws an `ApiError` and means the write was not accepted.
A network failure does not establish whether the server accepted the request.
A 202 response confirms acceptance only. Do not retry an accepted request just
because job polling is unavailable; read the contacts later to check the
resulting tags. API keys with Jobs API access can continue using `bulkTag()` and
`bulkUntag()` to await completion.

## Requirements

### API token

At this moment, the UI does not support issuing API tokens. Please contact support to issue one for you.

### [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API support

`@prezly/sdk` is using `fetch` to create requests. We assume that the environment running the code supports it.

We understand that some of the environments, such as node.js or old browsers, do not support `fetch`. This can be resolved by including a polyfill.

#### Polyfilling in browsers using [`whatwg-fetch`](https://www.npmjs.com/package/whatwg-fetch)

```sh
npm install --save whatwg-fetch
# or yarn
yarn add whatwg-fetch
```

```js
import 'whatwg-fetch';
// ...
import { createPrezlyClient } from '@prezly/sdk';
```

We recommend referring to the [official `whatwg-fetch` module documentation](https://www.npmjs.com/package/whatwg-fetch) for more information.

#### Polyfilling in browsers using [`node-fetch`](https://www.npmjs.com/package/node-fetch)

```sh
npm install --save node-fetch
# or yarn
yarn add node-fetch
```

```js
global.fetch = require('node-fetch');
// ...
const { createPrezlyClient } = require('@prezly/sdk');
```

We recommend referring to the [official `node-fetch` module documentation](https://www.npmjs.com/package/node-fetch) for more information.

#### Platform-agnostic polyfill using [`cross-fetch`](https://www.npmjs.com/package/cross-fetch)

```sh
npm install --save cross-fetch
# or yarn
yarn add cross-fetch
```

Using ES Modules:

```js
import 'cross-fetch/polyfill';
// ...
import { createPrezlyClient } from '@prezly/sdk';
```

Or Using CommonJS:

```js
require('cross-fetch/polyfill');
// ...
const { createPrezlyClient } = require('@prezly/sdk');
```

We recommend referring to the [official `cross-fetch` module documentation](https://www.npmjs.com/package/cross-fetch) for more information.

#### Custom `fetch` implementation

Additionally, you can initialize the API client with your own implementation of `fetch`:

```js
import { createPrezlyClient } = from '@prezly/sdk';

const prezlyClient = createPrezlyClient({
    accessToken: 'your-access-token',
    fetch: customFetch,
});
```

## Building and testing

The published SDK supports Node.js 22 and later. Development uses the backend
workspace's Node 22 environment and pnpm installation. Vite and Vitest require
Node.js 22.12+ on the Node 22 line. Run `pnpm install --frozen-lockfile` from
`apps/backend` to install the workspace dependencies.

Run these commands from `apps/backend/packages/prezly-sdk`, or use
`pnpm --dir apps/backend --filter @prezly/sdk <command>` from the repository root:

| Command | Purpose |
| --- | --- |
| `pnpm test` | Run unit tests directly from source; no build needed. |
| `pnpm test:watch` | Watch source and tests and rerun affected unit tests. |
| `pnpm typecheck` | Check source, tests, scripts and configuration without emitting files. |
| `pnpm check` | Check lint, formatting and types for the SDK. |
| `pnpm lint` / `pnpm lint:fix` | Report lint issues / apply lint fixes. |
| `pnpm format` / `pnpm format:fix` | Report formatting issues / write formatting fixes. |
| `pnpm test:package` | Run all checks, build, test both entrypoints, run unit tests and inspect package contents. |

`test:unit` and `format:check` remain aliases for `test` and `format`.
PR CI and the publishing workflow run `test:package`, which includes `check`.
Use `test:package` for full release verification.

`pnpm build` produces the ESM (`dist/index.js`) and CommonJS (`dist/index.cjs`)
entrypoints with Vite, then generates TypeScript declarations with `tsc`.
The query parser is bundled into both outputs so CommonJS works on Node.js 22.0
without ESM loading flags. Bundled dependency licenses are included in
`dist/DEPENDENCY_LICENSES.md`; parser updates require a new SDK build and release.

`pnpm test:build` checks both built entrypoints, including query handling and HTTP
behavior, with `require(esm)` disabled. It can also be run under another Node
version using `node --test scripts/test-entrypoints.mjs` after building. Vitest
upgrades affect development tooling and do not change the SDK's runtime minimum.

## Updating newsroom tracking policy

`newsrooms.update(id, { tracking_policy })` accepts `normal`, `strict`, or `lenient`
only when the newsroom's active theme has the `strict_tracking_policy` feature.
Omit `tracking_policy` to leave it unchanged; explicit `null` is not a valid update.
The newsroom read model may still return `default`, `consent-to-identify`, or
`disabled` for legacy newsrooms. These values are retained in the read enum but
cannot be written through the newsroom update API.
