OpenPrinterOpenPrinter
REST API

TypeScript SDK

Use openprinter sdk to submit and inspect durable OpenPrinter Cloud jobs.

The openprinter package is a strongly typed client for the public Neplex OpenPrinter Cloud API. It validates structured print documents before sending them, validates successful responses at runtime, retries transient failures with bounded backoff, and preserves the server's idempotency model.

Install

pnpm add openprinter

Configure from the environment

export OPENPRINTER_API_KEY="opk_your_project_key"
export OPENPRINTER_PROJECT_ID="project_01"
import { createOpenPrinterClient } from 'openprinter';

const openprinter = createOpenPrinterClient();

const { job, duplicate } = await openprinter.jobs.create({
  printerId: 'printer_01',
  idempotencyKey: 'order_123_receipt',
  document: {
    width: 80,
    sections: [
      {
        type: 'text',
        value: 'Order 123',
        align: 'center',
        bold: true,
      },
      { type: 'divider' },
      { type: 'row', left: 'Coffee', right: '$4.00' },
      { type: 'feed', lines: 2 },
      { type: 'cut' },
    ],
  },
});

console.log(job.id, duplicate ? 'already queued' : 'queued now');

Explicit options override environment defaults:

const openprinter = createOpenPrinterClient({
  apiKey: () => keyStore.getCurrentKey(),
  projectId: 'project_01',
  timeoutMs: 10_000,
  retry: {
    maxRetries: 3,
    baseDelayMs: 200,
    maxDelayMs: 3_000,
  },
});

Supported environment defaults include OPENPRINTER_BASE_URL, OPENPRINTER_API_BASE_URL, OPENPRINTER_PUBLIC_BASE_URL, OPENPRINTER_API_KEY, OPENPRINTER_PROJECT_ID, OPENPRINTER_TIMEOUT_MS, and OPENPRINTER_MAX_RETRIES.

List jobs and use project views

const failed = await openprinter.jobs.list({ state: 'FAILED' });

const anotherProject = openprinter.project('project_02');
const recent = await anotherProject.jobs.list();

client.project() returns an immutable project-pinned view and does not alter the default project on the original client.

Handle errors

import {
  isOpenPrinterApiError,
  isOpenPrinterTimeoutError,
} from 'openprinter';

try {
  await openprinter.jobs.list();
} catch (error) {
  if (isOpenPrinterApiError(error)) {
    console.error(error.status, error.code, error.requestId);
  } else if (isOpenPrinterTimeoutError(error)) {
    console.error(
      'OpenPrinter Cloud did not respond before the deadline',
    );
  }
}

The SDK also exports configuration and response-contract errors. See the package README for the complete public API and generated JSDoc declarations.

API-key safety

API keys are sent only as Bearer opk_… headers on authenticated calls. The SDK does not persist or log them. Keep them in server-side environment configuration and grant only jobs:read or jobs:write as required.

For current managed plan limits, visit the Neplex OpenPrinter Cloud pricing.

For endpoint-by-endpoint behavior, see the public REST API reference.

API Documentation

This reference covers the complete public surface of openprinter package. The SDK is designed for server-side TypeScript applications, but accepts a custom fetch implementation for browsers, tests, and other Fetch-compatible runtimes.

Exports at a glance

ExportKindPurpose
createOpenPrinterClientFunctionCreate an immutable client for the public Cloud API.
OPENPRINTER_API_KEY_SCOPESConstantThe supported project-key scopes.
OPENPRINTER_PRINT_JOB_STATESConstantThe states accepted by jobs.list().
OpenPrinterApiErrorError classAn HTTP error returned by the API.
OpenPrinterConfigurationErrorError classA configuration error detected before a request.
OpenPrinterResponseErrorError classA successful response that violates its contract.
OpenPrinterTimeoutErrorError classA request that exceeded its deadline.
isOpenPrinterApiErrorType guardNarrow an unknown error to OpenPrinterApiError.
isOpenPrinterConfigurationErrorType guardNarrow an unknown error to OpenPrinterConfigurationError.
isOpenPrinterResponseErrorType guardNarrow an unknown error to OpenPrinterResponseError.
isOpenPrinterTimeoutErrorType guardNarrow an unknown error to OpenPrinterTimeoutError.
OpenPrinterClientOptionsTypeClient construction options.
OpenPrinterRequestOptionsTypePer-request cancellation options.
OpenPrinterRetryOptionsTypeExponential-backoff settings.
OpenPrinterClientTypeThe top-level client interface.
OpenPrinterProjectClientTypeA client view pinned to one project.
OpenPrinterJobsResourceTypeThe jobs.create() and jobs.list() methods.
CreatePrintJobInputTypeThe input accepted by jobs.create().
CreatePrintJobResponseTypeThe result returned by jobs.create().
ListPrintJobsOptionsTypeThe filters accepted by jobs.list().
ListPrintJobsResponseTypeThe result returned by jobs.list().
OpenPrinterJobTypeA public print-job projection.
OpenPrinterHealthTypeThe result of client.health().
OpenPrinterApiKeyTypeA key string or a synchronous/asynchronous key provider.
OpenPrinterApiKeyScopeTypeOne supported API-key scope.
OpenPrinterPrintJobStateTypeOne public print-job lifecycle state.

createOpenPrinterClient()

createOpenPrinterClient(options?: OpenPrinterClientOptions): OpenPrinterClient

Create a client once and reuse it. The returned client and its resource views are immutable, so a project view cannot change the default project on the original client.

import { createOpenPrinterClient } from 'openprinter';

const openprinter = createOpenPrinterClient({
  apiKey: () => keyStore.getCurrentKey(),
  projectId: 'project_01',
  timeoutMs: 10_000,
  retry: {
    maxRetries: 3,
    baseDelayMs: 200,
    maxDelayMs: 3_000,
  },
  headers: {
    'x-client-name': 'checkout-service',
  },
});

OpenPrinterClientOptions

OptionTypeDefault and behavior
baseUrlstringOptional explicit service origin or mounted base path. If omitted, the environment variables below are checked.
apiKeystring | (() => string | Promise<string>)Required only for authenticated methods. A provider is useful for rotation or secret refresh.
projectIdstringThe default project used by client.jobs. It can be omitted when every job call uses client.project().
fetchtypeof globalThis.fetchThe runtime’s global Fetch implementation. Supply a custom implementation for tests or adapters.
timeoutMsnumber30_000. Must be an integer from 1 through 86_400_000.
retryfalse | OpenPrinterRetryOptionsBounded retries are enabled by default. Use false to disable them.
headersHeadersInitAdditional headers for every request. The SDK manages accept, content-type, and API-key authorization headers.

Environment defaults

Explicit options take precedence over environment variables. The base URL is resolved in this order:

  1. options.baseUrl
  2. OPENPRINTER_BASE_URL
  3. OPENPRINTER_API_BASE_URL
  4. OPENPRINTER_PUBLIC_BASE_URL
Environment variableUsed forDefault
OPENPRINTER_BASE_URLPreferred service base URL
OPENPRINTER_API_BASE_URLAlternate API base URL
OPENPRINTER_PUBLIC_BASE_URLPublic API base URL fallback
OPENPRINTER_API_KEYAuthenticated job calls
OPENPRINTER_PROJECT_IDThe default client.jobs project
OPENPRINTER_TIMEOUT_MSRequest deadline30_000
OPENPRINTER_MAX_RETRIESRetry count after the initial request2

OpenPrinterClient

MemberTypeDescription
baseUrlstringThe normalized base URL used for requests.
projectIdstring | undefinedThe configured default project, if one exists.
jobsOpenPrinterJobsResourceJob methods using the default project.
project(projectId)(projectId: string) => OpenPrinterProjectClientCreate an immutable project-scoped view.
health(options?)Promise<OpenPrinterHealth>Check service liveness without credentials.
discovery(options?)Promise<OpenPrinterDiscoveryDocument>Read and validate the standard discovery document.
pair(input, options?)Promise<OpenPrinterPairingResponse>Submit an OPPA pairing request.

Client methods and HTTP operations

SDK methodHTTP requestAuthenticationRequest body or queryResult
health()GET /healthNoneOpenPrinterHealth
discovery()GET /.well-known/openprinterNoneOpenPrinterDiscoveryDocument
pair(input)POST /openprinter/pairPairing requestOpenPrinterPairingRequest JSON bodyOpenPrinterPairingResponse
jobs.create(input)POST /v1/projects/:projectId/jobsjobs:write API keyCreatePrintJobInput JSON bodyCreatePrintJobResponse
jobs.list(options)GET /v1/projects/:projectId/jobsjobs:read API keyOptional state query parameterListPrintJobsResponse

OpenPrinterDiscoveryDocument, OpenPrinterPairingRequest, OpenPrinterPairingResponse, and PrintDocument are protocol types consumed by the SDK. Import them from @openprinter/protocol when you need to annotate a value explicitly; the SDK validates them at runtime.

client.health()

const health = await openprinter.health();

if (!health.ok) {
  throw new Error(
    `OpenPrinter service ${health.service} is not healthy`,
  );
}

health() calls GET /health without an API key.

Sample result:

{
  "ok": true,
  "service": "openprinter"
}
Result fieldTypeDescription
okbooleanWhether the service reports itself as live.
servicestringThe service identifier.

client.discovery()

const discovery = await openprinter.discovery();

console.log(discovery.server.name);
console.log(discovery.endpoints.pairing);

discovery() calls GET /.well-known/openprinter without an API key and validates the returned protocol document.

Sample result:

{
  "protocolVersion": "1",
  "server": {
    "id": "neplex-openprinter-cloud",
    "name": "Neplex OpenPrinter Cloud",
    "version": "0.1.0"
  },
  "endpoints": {
    "pairing": "/openprinter/pair",
    "gateway": "/.well-known/openprinter/gateway"
  },
  "authentication": {
    "method": "pairing-code-ed25519",
    "challengeTtlSeconds": 30
  }
}
Result fieldTypeDescription
protocolVersion'1'The supported OpenPrinter protocol version.
server.idstringThe server identifier.
server.namestringThe server brand name.
server.versionstringThe server version.
endpoints.pairingstringPairing route, usually relative to the service.
endpoints.gatewaystringPrivate agent gateway route.
authentication.method'pairing-code-ed25519'The agent authentication method.
authentication.challengeTtlSecondsnumberGateway challenge lifetime.

client.pair(input, options?)

Pairing is normally performed by OPPA after a user creates a one-time code in the Neplex dashboard. The SDK sends the public key and agent metadata; private key generation and signing remain agent responsibilities.

const paired = await openprinter.pair({
  protocolVersion: '1',
  code: 'ABCD-EFGH',
  agent: {
    name: 'OPPA',
    version: '0.1.0',
    platform: 'macos-arm64',
    installationId: 'install_01',
  },
  credential: {
    algorithm: 'Ed25519',
    publicKey: {
      kty: 'OKP',
      crv: 'Ed25519',
      x: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
    },
  },
});

console.log(paired.agentId, paired.keyId);

Sample result:

{
  "agentId": "agt_01",
  "keyId": "key_01",
  "serverId": "neplex-openprinter-cloud",
  "pairedAt": "2026-08-21T00:00:00.000Z"
}
Input fieldTypeDescription
protocolVersion'1'Protocol version declared by the agent.
codestringOne-time dashboard pairing code.
agent.namestringHuman-readable agent name.
agent.versionstringAgent version.
agent.platformstringAgent platform identifier.
agent.installationIdstringStable identifier for this installation.
credential.algorithm'Ed25519'Public-key algorithm.
credential.publicKey.kty'OKP'JWK key type.
credential.publicKey.crv'Ed25519'JWK curve.
credential.publicKey.xstring43-character unpadded base64url public key.
Result fieldTypeDescription
agentIdstringThe paired agent identifier.
keyIdstringThe paired public-key identifier.
serverIdstringThe server that accepted pairing.
pairedAtstringISO 8601 pairing timestamp.

Pairing requests are not automatically retried because a pairing code is single-use and a successful response could be lost in transit.

client.project(projectId)

const project = openprinter.project('project_02');
const jobs = await project.jobs.list({ state: 'FAILED' });
MemberTypeDescription
projectIdstringThe immutable project identifier for this view.
jobsOpenPrinterJobsResourceJob methods addressed to this project.

Request options

Both health(), discovery(), and pair() accept OpenPrinterRequestOptions. Job methods accept the same options as their final argument, after the method-specific input/options.

OptionTypeDescription
signalAbortSignalCancel the request and any pending retry backoff.
const controller = new AbortController();

const request = openprinter.jobs.list({
  state: 'PENDING',
  signal: controller.signal,
});

controller.abort();
await request;

client.jobs.create(input, options?)

client.jobs.create(
  input: CreatePrintJobInput,
  options?: OpenPrinterRequestOptions,
): Promise<CreatePrintJobResponse>

The API key must include jobs:write. The SDK validates the structured document before sending POST /v1/projects/:projectId/jobs.

CreatePrintJobInput

FieldTypeRequiredDescription
printerIdstringYesProject printer record ID or project-local remote ID. Maximum 256 characters.
idempotencyKeystringYesStable key for one logical submission. Maximum 256 characters.
documentPrintDocumentYesStructured receipt document from @openprinter/protocol.
metadataReadonly<Record<string, string>>NoString metadata echoed in the job projection. The SDK allows up to 32 entries, 64-character keys, and 1,000-character values.
expiresInMsnumberNoInteger from 10 seconds through 30 days. The service defaults to 24 hours and may cap retention by plan.
maxAttemptsnumberNoInteger from 1 through 10; the service defaults to 5.
const result = await openprinter.jobs.create({
  printerId: 'printer_01',
  idempotencyKey: 'order_123_receipt_v1',
  document: {
    width: 80,
    sections: [
      {
        type: 'text',
        value: 'Order 123',
        align: 'center',
        bold: true,
      },
      { type: 'divider' },
      { type: 'row', left: 'Coffee', right: '$4.00' },
      { type: 'qr', value: 'https://example.com/orders/123' },
      { type: 'feed', lines: 2 },
      { type: 'cut' },
    ],
  },
  metadata: {
    orderId: 'order_123',
    source: 'checkout',
  },
  maxAttempts: 5,
});

console.log(result.job.id, result.duplicate);

Sample result:

{
  "job": {
    "id": "job_01",
    "projectId": "project_01",
    "printerId": "printer_01",
    "idempotencyKey": "order_123_receipt_v1",
    "state": "PENDING",
    "metadata": {
      "orderId": "order_123"
    },
    "expiresAt": "2026-08-22T00:00:00.000Z",
    "attemptCount": 0,
    "maxAttempts": 5,
    "lastErrorCode": null,
    "lastErrorMessage": null,
    "receivedAt": null,
    "submittedAt": null,
    "createdAt": "2026-08-21T00:00:00.000Z",
    "updatedAt": "2026-08-21T00:00:00.000Z"
  },
  "duplicate": false
}

CreatePrintJobResponse

FieldTypeDescription
jobOpenPrinterJobThe public job projection.
duplicatebooleanfalse for a new job; true when the idempotency key returned an existing job.

A new job returns HTTP 202. Repeating the same project and idempotency key returns HTTP 200 with the existing job and duplicate: true, making a retry after a network timeout safe.

client.jobs.list(options?)

client.jobs.list(
  options?: ListPrintJobsOptions,
): Promise<ListPrintJobsResponse>

The API key must include jobs:read. This calls GET /v1/projects/:projectId/jobs and returns the newest 100 public job projections.

ListPrintJobsOptions

FieldTypeDescription
stateOpenPrinterPrintJobStateOptional exact lifecycle-state filter.
signalAbortSignalOptional cancellation signal.
const failedJobs = await openprinter.jobs.list({ state: 'FAILED' });

for (const job of failedJobs.jobs) {
  console.log(job.id, job.lastErrorCode, job.lastErrorMessage);
}

Sample result:

{
  "jobs": [
    {
      "id": "job_01",
      "projectId": "project_01",
      "printerId": "printer_01",
      "idempotencyKey": "order_123_receipt_v1",
      "state": "FAILED",
      "metadata": {
        "orderId": "order_123"
      },
      "expiresAt": "2026-08-22T00:00:00.000Z",
      "attemptCount": 5,
      "maxAttempts": 5,
      "lastErrorCode": "printer_offline",
      "lastErrorMessage": "The local printer is offline.",
      "receivedAt": "2026-08-21T00:00:01.000Z",
      "submittedAt": null,
      "createdAt": "2026-08-21T00:00:00.000Z",
      "updatedAt": "2026-08-21T00:03:00.000Z"
    }
  ],
  "keyPrefix": "opk_proj"
}

There is currently no public pagination or job-detail REST method. The list response intentionally excludes the print document.

ListPrintJobsResponse

FieldTypeDescription
jobsreadonly OpenPrinterJob[]Newest public job projections first.
keyPrefixstringSafe display prefix for the authenticated API key. It is not a credential.

OpenPrinterJob

FieldTypeDescription
idstringDurable Cloud job identifier.
projectIdstringOwning project identifier.
printerIdstringProject-local printer identifier.
idempotencyKeystringApplication submission key.
stateOpenPrinterPrintJobStateCurrent delivery lifecycle state.
metadataReadonly<Record<string, string>> | nullApplication metadata, if supplied.
expiresAtstringISO 8601 expiry timestamp.
attemptCountnumberDelivery attempts started so far.
maxAttemptsnumberMaximum delivery attempts for this job.
lastErrorCodestring | nullLast bounded failure code, if any.
lastErrorMessagestring | nullLast bounded failure message, if any.
receivedAtstring | nullISO timestamp when OPPA durably received the job.
submittedAtstring | nullISO timestamp when the local printer backend accepted submission.
createdAtstringISO 8601 creation timestamp.
updatedAtstringISO 8601 update timestamp.

Constants and literal types

API-key scopes

import {
  OPENPRINTER_API_KEY_SCOPES,
  type OpenPrinterApiKeyScope,
} from 'openprinter';

console.log(OPENPRINTER_API_KEY_SCOPES);
// ['jobs:read', 'jobs:write']
ExportTypeValues
OPENPRINTER_API_KEY_SCOPESreadonly ['jobs:read', 'jobs:write']jobs:read, jobs:write
OpenPrinterApiKeyScopeUnion'jobs:read' | 'jobs:write'
ExportTypeValues
OPENPRINTER_PRINT_JOB_STATESReadonly tuplePENDING, DISPATCHING, DELIVERED, PRINTING, COMPLETED, FAILED, EXPIRED, CANCELLED
OpenPrinterPrintJobStateUnionOne of the states above.

DELIVERED means the paired agent received the job. submittedAt records a separate local printer-backend submission timestamp; neither field universally proves that paper was physically produced.

API-key providers

type OpenPrinterApiKey = string | (() => string | Promise<string>);

Use a provider when the application rotates secrets without rebuilding the client:

const openprinter = createOpenPrinterClient({
  apiKey: async () => secretManager.read('openprinter-api-key'),
});

Retry behavior

OptionTypeDefaultValid range
maxRetriesnumber2010 retries after the initial request.
baseDelayMsnumber250060_000 milliseconds.
maxDelayMsnumber5_000At least baseDelayMs, up to 120_000 milliseconds.

The SDK retries transient GET requests and idempotent job POST requests for HTTP 408, 425, 429, 500, 502, 503, and 504 responses. Job creation is safe to retry because its idempotency key is part of the request. Pairing requests are never automatically retried.

Set retry: false to disable all retries:

const openprinter = createOpenPrinterClient({
  retry: false,
});

Errors and type guards

ErrorImportant fieldsWhen it is raised
OpenPrinterApiErrorstatus, method, url, code, requestId, detailsThe server returns a non-2xx response.
OpenPrinterConfigurationErrormessageRequired configuration is missing or invalid before a request.
OpenPrinterResponseErrormethod, url, causeA successful response does not match the documented contract.
OpenPrinterTimeoutErrortimeoutMs, method, urlA request exceeds its configured deadline.
import {
  isOpenPrinterApiError,
  isOpenPrinterConfigurationError,
  isOpenPrinterResponseError,
  isOpenPrinterTimeoutError,
} from 'openprinter';

try {
  await openprinter.jobs.list();
} catch (error) {
  if (isOpenPrinterApiError(error)) {
    console.error(error.status, error.code, error.requestId);
  } else if (isOpenPrinterTimeoutError(error)) {
    console.error(`Timed out after ${error.timeoutMs}ms`);
  } else if (isOpenPrinterConfigurationError(error)) {
    console.error('Check the OpenPrinter environment configuration.');
  } else if (isOpenPrinterResponseError(error)) {
    console.error(
      `Unexpected response from ${error.method} ${error.url}`,
    );
  }
}

OpenPrinterApiError.code is the server’s machine-readable error code, such as unauthorized, forbidden, not_found, or entitlement_limit. Use the HTTP status and code for branching; do not branch on human-readable messages.

On this page