# License

> Configure Exchange export authorization, query remaining quota, and handle license errors.

- Human documentation: [https://docs.univer.ai/server/import-export/license](https://docs.univer.ai/server/import-export/license)

- Agent Markdown: [https://docs.univer.ai/server/import-export/license.md](https://docs.univer.ai/server/import-export/license.md)

- Requested language: `en-US`

- Content language: `en-US`

- Documentation version: `1.4.0`

- Source: [import-export/license.mdx](https://github.com/dream-num/documentation/blob/dev/content/server/import-export/license.mdx)

---

Exchange reads the License in the server process running `@univerjs-pro/exchange-node`. See [Office file import and export](https://docs.univer.ai/server/import-export/node.md) for conversion.

## Configure authorization

Provide the License through the `UNIVER_LICENSE` and `UNIVER_LICENSE_KEY` environment variables. Calling `configureExchange` is optional: the SDK reads the environment once on the first export or quota query and reuses that configuration. Without environment credentials, Free applies. Supplied but invalid credentials return an error and cannot be used for export.

For explicit configuration, `configureExchange({ license, licenseKey })` returns `Promise<void>` and completely ignores the environment. Await it before starting exports that should use the new credentials.

```ts
import { configureExchange, getExportQuota } from '@univerjs-pro/exchange-node'

await configureExchange({
  license: '<license content>',
  licenseKey: '<licenseKey content>',
})

const quota = await getExportQuota()
```

Use the complete `license` and `licenseKey` contents supplied by your license provider, not file paths. Configure both together through your application's secret management system.

| Input | Behavior |
| --- | --- |
| Do not call `configureExchange` | Read both environment variables on first use; use Free when neither is set. |
| `configureExchange({ license, licenseKey })` | Use only the explicit credentials; ignore the environment. |
| `configureExchange({})` | Explicitly select Free; ignore the environment. |
| Only one credential is supplied in the selected source | Return `INVALID_ARGUMENT`; never fill the other field from a different source. |

Once a complete environment configuration is read, it remains fixed even if a later operation fails. To replace the default credentials at runtime, explicitly call and await `configureExchange`. Failed configuration leaves the previous configuration in effect; handle the error before starting new exports.

ESM and CommonJS share configuration within the same JavaScript context. Each `worker_threads` Worker must configure itself or initialize from its environment. Exports use the configuration captured when the public export method is called; later credential changes do not affect exports already started.

### Provide authorization for one operation

Pass a complete `license` and `licenseKey` pair in the options for an export API or `getExportQuota` to authorize only that operation without changing the default configuration. For example, query quota and export with the same credentials:

```ts
import { UniverInstanceType } from '@univerjs/core'
import { ExchangeFormat, exportToBuffer, getExportQuota } from '@univerjs-pro/exchange-node'

const credentials = { license: '<license content>', licenseKey: '<licenseKey content>' }
const quota = await getExportQuota(credentials)
const output = await exportToBuffer(workbook, {
  type: UniverInstanceType.UNIVER_SHEET,
  format: ExchangeFormat.XLSX,
  ...credentials,
})
```

This also applies to file exports, Snapshot exports, and optional PDF exports. Omitting both fields uses the default configuration; supplying only one returns `INVALID_ARGUMENT`. An explicit pair is never completed from the default configuration or environment, and empty strings are not treated as omitted. Invalid credentials return `LICENSE_INVALID` without falling back to the default commercial license.

### Optional PDF support

Install the PDF extension package and import its entry before configuring authorization:

```sh
pnpm add @univerjs-pro/exchange-node @univerjs-pro/exchange-pdf-node-binding
```

```ts
import '@univerjs-pro/exchange-node/pdf'
import { configureExchange, getExportQuota } from '@univerjs-pro/exchange-node'

await configureExchange({
  license: '<license content>',
  licenseKey: '<licenseKey content>',
})

const quota = await getExportQuota()
```

This entry registers PDF support; native components load when used. PDF uses the same `importBuffer`, `importFile`, `exportToBuffer`, `exportToFile`, and corresponding Snapshot APIs with `UniverInstanceType.UNIVER_PDF` and `ExchangeFormat.PDF`. A PDF-only application does not need to load the Office native component. Environment-based configuration also applies to PDF.

## Query remaining quota

`getExportQuota({ license, licenseKey })` returns `Promise<ExportQuota>` for the authorization's shared Office and PDF quota. Omitting credentials queries the default configuration. It accepts no Unit type and does not check permission to export a particular type; that check occurs during export.

Queries neither consume nor reserve quota, and do not check the SDK version, activation time, or export type entitlement. A zero balance still returns a successful snapshot. Other requests may consume quota afterward, so handle actual export errors as well.

Before authorization starts, a limited-quota query returns the first authorization month with a future `periodStart`. A successful query or a `valid` status does not grant permission to export. Version, activation, type entitlement, available quota, and concurrency checks occur during export.

| Field | Type | Meaning |
| --- | --- | --- |
| `licenseStatus` | `'not_configured' \| 'expired' \| 'valid'` | Export subscription is unconfigured, expired, or not expired. `valid` does not mean export admission checks have passed. Invalid credentials return an error. |
| `expiresAt` | `string \| null` | Export subscription expiry as a UTC ISO timestamp; `null` when unconfigured or no expiry applies. |
| `observedAt` | `string` | UTC ISO timestamp of this snapshot. |
| `limit` | `number \| null` | Current monthly export limit; `null` when unlimited. |
| `used` | `number \| null` | Successful exports in the current month; `null` when unlimited. |
| `reserved` | `number \| null` | In-flight exports occupying the current month's quota; `null` when unlimited. |
| `remaining` | `number \| null` | Available exports: `max(0, limit - used - reserved)`; `null` when unlimited. |
| `periodStart` | `string \| null` | Inclusive UTC ISO start of the queried monthly quota window; the first period starts in the future before activation. `null` when unlimited. |
| `periodEnd` | `string \| null` | Exclusive UTC ISO end of the queried monthly quota window; `null` when unlimited. |

`periodEnd` marks the start of the next monthly quota window; `expiresAt` marks the end of the export subscription. A valid subscription always uses commercial quota. Exhaustion never selects Free, and a new commercial quota window begins the following month. Only subscription expiry selects the existing remaining Free quota, without resetting or granting it again.

Example of a limited-quota snapshot:

```json
{
  "licenseStatus": "valid",
  "expiresAt": "2027-10-19T23:59:59Z",
  "observedAt": "2026-10-21T09:00:00Z",
  "limit": 500,
  "used": 20,
  "reserved": 1,
  "remaining": 479,
  "periodStart": "2026-10-19T00:00:00Z",
  "periodEnd": "2026-11-19T00:00:00Z"
}
```

## Use export quota

Free allows 10 successful exports per authorization month. Commercial export quota, concurrency, and version entitlements depend on the purchased license. Neither Free nor commercial licenses impose file-size or cell-count limits. Use the query response for the current period and remaining quota. Unused monthly quota does not roll over.

Imports do not require export authorization and do not consume export quota. Office and PDF exports using the same commercial license within a deployment share quota. See [Office file import and export](https://docs.univer.ai/server/import-export/node.md) for Exchange conversion formats.

Each successful conversion consumes one export. Returning a Buffer counts as success. A later file-write, upload, or download failure does not refund that export; another successful conversion consumes another export. When the current period has no remaining quota, new exports are rejected. Wait for the next authorization period or provide a valid license with a higher quota; an exhausted valid commercial license does not use Free quota.

## Handle errors

Use the structured `code` to identify errors instead of parsing `message` text. Your UI can map error codes to localized messages. Retain diagnostic information for technical support.

```ts
import { UniverInstanceType } from '@univerjs/core'
import { ExchangeError, ExchangeFormat, exportToBuffer } from '@univerjs-pro/exchange-node'

try {
  const output = await exportToBuffer(workbook, {
    type: UniverInstanceType.UNIVER_SHEET,
    format: ExchangeFormat.XLSX,
  })
} catch (error) {
  if (error instanceof ExchangeError) {
    // Display the message for error.code, e.g. LICENSE_EXPORT_QUOTA_EXCEEDED when the quota runs out
    console.error(error.code)
  } else {
    throw error
  }
}
```

The table distinguishes quota queries (`getExportQuota`) from actual exports. Both APIs return Promises; handle failures with `await` and `try/catch`.

| Code | Operation | Meaning and response |
| --- | --- | --- |
| `LICENSE_EXPORT_QUOTA_EXCEEDED` | Export only | Insufficient export quota for the current period. Query quota or contact sales to adjust the license. |
| `LICENSE_CONCURRENCY_TIMEOUT` | Export only | Waiting for available export concurrency timed out. Reduce concurrent exports before submitting again. |
| `LICENSE_UNIT_TYPE_REJECTED` | Export only | This export type is not licensed or its type entitlement has expired. Provide a valid license covering the type. |
| `LICENSE_INVALID` | Query, export | The license is invalid. Check the complete credentials or contact the license provider for an update. |
| `LICENSE_POLICY_INVALID` | Query, export | The authorization configuration does not meet SDK requirements. Contact the license provider for an update. |
| `LICENSE_NOT_YET_EFFECTIVE` | Export only | Authorization has not started yet. Use it after its effective date. |
| `LICENSE_VERSION_NOT_ALLOWED` | Export only | The current SDK version is outside the licensed range. Use a matching version or update the license. |
| `UNSUPPORTED_FORMAT` | Export only | The format is unsupported or incompatible with the Unit type. Check export options. |
| `INVALID_FILE` | Export only | The conversion input is invalid. Check its contents. |
| `INCOMPLETE_SNAPSHOT` | Export only | Export data is incomplete. Provide complete data or a complete Snapshot. |
| `CONVERSION_FAILED` | Export only | Conversion failed. Retain the error and check the input contents. |
| `IO_ERROR` | Export only | File input/output failed. An output-write failure after successful conversion has already consumed an export. |
| `INVALID_ARGUMENT` | Query, export | Incomplete arguments or credentials. Check the configuration. |
| `NATIVE_LOAD_FAILED` | Query, export | A native SDK component could not load. Check package and runtime compatibility. |
| `LICENSE_USAGE_STATE_INVALID` | Query, export | License usage status cannot be verified. Preserve the error and contact technical support. |
| `LICENSE_USAGE_STORAGE_ERROR` | Query, export | License usage processing failed. Preserve the error, contact technical support, and avoid automatic resubmission. |

Applications can derive expiry reminders from `licenseStatus` and `expiresAt`; the SDK does not return a separate notice list.

After a license usage processing error, do not infer accounting solely from the error. Preserve the complete error and contact technical support.
