npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cloudcreators-gmbh/lexware-ts-sdk

v0.1.3

Published

TypeScript SDK for the Lexware Office Public API

Readme

Lexware TypeScript SDK

Note: this is not an official SDK by Haufe/Lexware, it was developed by Cloud Creators GmbH from Freiburg.

A strongly-typed, ergonomic SDK for the Lexware Office Public API. It wraps common REST patterns (paging, filtering, file download/upload, webhooks) and exposes composable helpers for querying invoices, contacts, voucherlist, vouchers, and more.

Base URL: https://api.lexware.io (May 26, 2025 rebrand; former gateway continues only until Dec 2025)
Auth: API Key (Bearer) created in your Lexware account under Add-ons → Public API.

Note: parts of this code including the Readme is AI-generated, so make sure to test your results and open bug reports or PRs.


✨ Features

  • First-class support for voucherlist, invoices, contacts, vouchers, files, event-subscriptions (webhooks).
  • Typed filters and query builders (date ranges, status, voucher types, name search, etc.).
  • Pagination helpers with sensible defaults and auto iteration.
  • File APIs: download invoice PDFs / e-invoice XML via /v1/invoices/{id}/file; upload voucher files via /v1/files.
  • Rate limiting guard (2 req/s) with exponential backoff on 429 (configurable).
  • Optimistic locking helpers for PUTs with version.
  • Node & TypeScript first. Fully typed responses.

📦 Installation

npm install @cloudcreators-gmbh/lexware-ts-sdk
# or
yarn add @cloudcreators-gmbh/lexware-ts-sdk
pnpm add @cloudcreators-gmbh/lexware-ts-sdk

⚙️ Setup

import { LexwareClient } from "@cloudcreators-gmbh/lexware-ts-sdk";
const client = new LexwareClient({ apiKey: process.env.LEXWARE_API_KEY! });

Tip: Store the API key in your secrets manager. The SDK only sends it via the Authorization: Bearer <token> header to api.lexware.io.


🧭 Quick starts & Recipes

▶️ Running the examples

Set your API key and optional filters via environment variables (all examples use LEXWARE_-prefixed vars):

# Common
export LEXWARE_API_KEY=your_token
export LEXWARE_PAGE=0
export LEXWARE_SIZE=25

# Contacts filters
export LEXWARE_NAME="Muster & Partner"   # optional
export LEXWARE_EMAIL="[email protected]"  # optional
export LEXWARE_CUSTOMER=true              # optional (true/false)
export LEXWARE_VENDOR=false               # optional (true/false)

# Invoices filters
export LEXWARE_STATUS="any"              # e.g. "open,overdue" or "any"
export LEXWARE_CONTACT_ID="..."          # optional
export LEXWARE_NUMBER="RE-2025-0001"     # optional
export LEXWARE_SORT="updatedDate,DESC"   # optional

# Voucherlist filters
export LEXWARE_VOUCHER_TYPE="invoice,purchaseinvoice"  # optional
export LEXWARE_VOUCHER_STATUS="any"                    # optional
export LEXWARE_FROM="2025-01-01"                       # optional (yyyy-MM-dd)
export LEXWARE_TO="2025-01-31"                         # optional (yyyy-MM-dd)
export LEXWARE_SORT="updatedDate,DESC"                 # optional

# Run
npm run example-contacts
npm run example-invoices
npm run example-voucherlist

# Download a PDF for a specific invoice
export LEXWARE_INVOICE_ID="<invoice-id>"
npm run example-download-invoice-pdf

Notes:

  • All examples require LEXWARE_API_KEY to be set.
  • Booleans accept: true/false, 1/0, yes/no, y/n, on/off.
  • Status lists accept comma-separated values or any.

1) List invoices and download the PDF (or XRechnung XML)

// Page through latest invoices (draft/open), newest first:
const page1 = await client.invoices.list({ page: 0 }); // default size 25
for (const inv of page1.content) {
  console.log(inv.id, inv.voucherStatus, inv.voucherNumber);
}

// Download file ("pdf" or "xml" for XRechnung when available):
const bytes = await client.invoices.downloadFile(invId, 'pdf');
await fs.promises.writeFile(`invoice-${invId}.pdf`, Buffer.from(bytes));

Notes:

  • Use client.invoices.downloadFile(id, {accept}) which hits /v1/invoices/{id}/file and returns raw bytes.
  • Invoices in draft have no file; server returns 409 in that case—SDK raises a typed error.

2) Find contacts (with paging and name/email filters)

// Fetch first page
const contacts = await client.contacts.list({ page: 0, size: 50 });

// Filter by name and/or email
const result = await client.contacts.list({
  name: "johnson & partner",
  email: "[email protected]",
  page: 0,
  size: 25,
});
console.log(result.totalElements, result.content[0]?.person ?? result.content[0]?.company);

Some endpoints (incl. contacts, voucherlist, vouchers) require the search string to be HTML-encoded and URL-encoded for reserved characters like &, <, >. The SDK does this for you automatically.


3) Use the voucherlist for fast cross-entity searches

// List all purchase invoices and sales invoices that are open in March 2025:
const voucherPage = await client.voucherlist.list({
  voucherType: ["purchaseinvoice", "invoice"],
  voucherStatus: ["open"],
  voucherDateFrom: "2025-03-01",
  voucherDateTo: "2025-03-31",
  size: 100, // up to 250
  page: 0,
});

// Pick one and follow relations
for (const v of voucherPage.content) {
  const contact = v.contactId ? await client.contacts.get(v.contactId) : null;
  console.log(v.id, v.voucherType, v.voucherStatus, contact?.person?.firstName ?? contact?.company?.name);
}

Prefer voucherlist for filtering across sales vouchers (invoices, credit notes, quotations, order confirmations, delivery notes) and bookkeeping vouchers. The older GET /v1/vouchers?voucherNumber=… filter is deprecated.


4) Get a voucher by id (bookkeeping, e.g., purchaseinvoice) and follow to related contact

const voucher = await client.vouchers.get(voucherId); // /v1/vouchers/{id}
const contact = voucher.contactId ? await client.contacts.get(voucher.contactId) : null;

5) Upload a voucher file (PDF/JPG/PNG/XML) to create bookkeeping vouchers

// Creates/returns a bookkeeping voucher and file id (async OCR → status 'unchecked' later)
const data = await fs.promises.readFile("/path/to/receipt.pdf");
const { id: fileId, voucherId } = await client.files.upload(data, "receipt.pdf", "voucher");
  • Uses POST /v1/files with multipart/form-data and type=voucher.
  • Max file size 5 MB for vouchers. If file already exists (checksum), server returns existing ids.
  • After upload, voucher is available via /v1/vouchers/{id}. Initial status may be blank until OCR completes, then unchecked.


🔎 Pagination

All paged endpoints return a Spring-like page envelope:

type Page<T> = {
  content: T[];
  first: boolean;
  last: boolean;
  totalPages: number;
  totalElements: number;
  numberOfElements: number;
  size: number;  // requested size
  number: number; // page index (0-based)
  sort?: Array<{ direction: "ASC"|"DESC"; property: string }>;
};
  • Defaults: size=25, page=0. Maximum size=250 for contacts, voucherlist, vouchers, etc.
  • The SDK exposes forEachPage(...) helpers to iterate safely and respects the 10,000-entry search window. Narrow your date ranges if you hit Maximum search window size exceeded.

⏱️ Rate limits

  • The public API allows 2 requests/second (token-bucket).
  • SDK retries on 429 with exponential backoff and jitter. You can configure rateLimit or plug your own limiter.

🔐 Optimistic locking

  • PUT updates require a matching version (a revision number).
  • The SDK flow: get → apply changes → put and throws a specific VersionConflictError (409) when versions mismatch. On first POST, set version: 0 (handled by the SDK).

🧱 Errors

The SDK throws typed errors with status, code, i18nKey (if present) and raw response snapshot. Handle known cases:

  • 401/403: missing/invalid token.
  • 404: resource not found.
  • 406: unacceptable media type or validation issue (e.g., trying to render a PDF for a draft invoice).
  • 409: version conflict or draft invoice file download attempt.
  • 429: rate-limited (SDK retries).
try {
  await client.invoices.downloadFile(id);
} catch (e) {
  if (e.name === "HttpError" && e.status === 409) {
    // invoice is draft → finalize first or poll until open
  }
}

🧪 End-to-end examples

List invoices updated this week and download their files

import { endOfToday, subDays, formatISO } from "date-fns";

const updatedFrom = formatISO(subDays(endOfToday(), 7), { representation: "date" });

for await (const v of client.voucherlist.iterateAll({
  voucherType: ["invoice"],
  updatedDateFrom: updatedFrom,
  size: 250,
})) {
  try {
    const bin = await client.invoices.downloadFile(v.id, 'pdf');
    await fs.promises.writeFile(`out/${v.voucherNumber ?? v.id}.pdf`, Buffer.from(bin));
  } catch (e) {
    console.warn(`Skip ${v.id}: ${e}`);
  }
}

Search contacts by name and map to invoices

const c = await client.contacts.search({ name: "Muster & Partner", size: 50 });
for (const contact of c.content) {
  const related = await client.voucherlist.list({
    contactId: contact.id,
    voucherType: ["invoice"],
    size: 50,
  });
  console.log(contact.id, related.numberOfElements);
}

🔁 Filtering cheat-sheet

  • voucherlist: voucherType[], voucherStatus[], voucherDateFrom/To, createdDateFrom/To, updatedDateFrom/To, dueDateFrom/To, voucherNumber, contactId, archived, page, size.
  • contacts: name, email, role filters (customer/vendor), paging.
  • vouchers (bookkeeping): GET /v1/vouchers/{id}; filtering by voucherNumber is deprecated—use voucherlist instead.
  • invoices: GET /v1/invoices/{id}, file via /v1/invoices/{id}/file (PDF or XML for XRechnung).

The SDK auto-encodes search strings for endpoints that require HTML+URL encoding for &, <, >.


🗂 File handling

  • Download invoice file: GET /v1/invoices/{id}/file (use accept to pick application/pdf or application/xml for XRechnung).
  • Download bookkeeping voucher file: GET /v1/files/{id}.
  • Upload voucher file: POST /v1/files (multipart/form-data with type=voucher; max 5 MB).

🧰 SDK surface (high-level)

class LexwareClient {
  contacts: {
    list(q?: { page?: number; size?: number; name?: string; email?: string; customer?: boolean; vendor?: boolean }): Promise<Page<Contact>>;
    iterateAll(q?: Omit<{ page?: number; size?: number; name?: string; email?: string; customer?: boolean; vendor?: boolean }, 'page'>): AsyncGenerator<Contact>;
    get(id: string): Promise<Contact>;
    create(patch: Partial<Contact>): Promise<{ id: string; resourceUri: string; createdDate: string; updatedDate: string; version: number }>;
    update(id: string, patch: Partial<Contact>): Promise<void>;
  };

  voucherlist: {
    list(q: VoucherlistListParams): Promise<Page<VoucherListItem>>;
    iterateAll(q: Omit<VoucherlistListParams, 'page'|'size'> & { size?: number }): AsyncGenerator<VoucherListItem>;
  };

  invoices: {
    get(id: string): Promise<Invoice>;
    list(q: { status: VoucherStatus|VoucherStatus[]|'any'; from?: string; to?: string; page?: number; size?: number; sort?: string; hydrate?: boolean; contactId?: string; number?: string }): Promise<Page<Invoice | VoucherListItem>>;
    downloadFile(id: string, format?: 'pdf'|'xml'|'auto'): Promise<Uint8Array>;
  };

  vouchers: {
    create(body: Omit<Voucher, 'id'|'organizationId'|'version'> & Partial<Pick<Voucher,'version'>>): Promise<{ id: string; resourceUri: string; createdDate: string; updatedDate: string; version: number }>;
    get(id: string): Promise<Voucher>;
    update(id: string, body: Partial<Voucher>): Promise<void>;
    uploadFile(id: string, file: Blob | Uint8Array | ArrayBuffer | Buffer, filename: string): Promise<{ id: string }>;
  };

  files: {
    upload(file: Blob | Uint8Array | ArrayBuffer | Buffer, filename: string, type?: 'voucher'): Promise<{ id: string; voucherId?: string }>;
    download(id: string): Promise<Uint8Array>;
  };

  getRelatedContact(input: VoucherListItem | Voucher | Invoice | string, typeHint?: 'voucher'|'invoice'|'voucherlist'): Promise<Contact | null>;
}

✅ Best practices

  • Prefer voucherlist for cross-entity searches & filtering.
  • Handle rate limits and 429 with retries (SDK does this by default).
  • Respect optimistic locking (version on PUT/POST).
  • Use the new /v1/invoices/{id}/file instead of legacy render+files flow.
  • Narrow filters if you hit the 10,000-entry window limit.
  • When searching in contacts/voucherlist, let the SDK do the required HTML+URL encoding of search strings.

📚 Types

All endpoints return fully typed models derived from the official API reference. You can import them directly:

import type { Invoice, VoucherlistItem, Contact, Voucher, Page } from "@cloudcreators-gmbh/lexware-ts-sdk/types";

🔗 Links

  • Dashboard/Vouchers: https://app.lexware.de/vouchers
  • Create API Key: https://app.lexware.de/addons/public-api
  • Lexware API docs: https://developers.lexware.io/docs/#lexware-api-documentation