{"_id":"@cloudcreators-gmbh/lexware-ts-sdk","name":"@cloudcreators-gmbh/lexware-ts-sdk","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.3":{"name":"@cloudcreators-gmbh/lexware-ts-sdk","version":"0.1.3","description":"TypeScript SDK for the Lexware Office Public API","license":"MIT","main":"dist/index.js","types":"dist/index.d.ts","sideEffects":false,"engines":{"node":">=18"},"scripts":{"build":"tsc -p tsconfig.json","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build","example-download-invoice-pdf":"tsx examples/download-invoice-pdf.ts","example-list-contacts":"tsx examples/list-contacts.ts","example-contacts":"tsx examples/list-contacts.ts","example-invoices":"tsx examples/list-invoices.ts","example-voucherlist":"tsx examples/list-voucherlist.ts"},"exports":{".":{"require":"./dist/index.js","types":"./dist/index.d.ts"}},"devDependencies":{"@types/node":"^24.8.1","dotenv":"^16.4.5","tsx":"^4.7.0","typescript":"^5.4.0"},"packageManager":"pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c","_id":"@cloudcreators-gmbh/lexware-ts-sdk@0.1.3","gitHead":"7264338c955b3ca654b56024379fd3cd9491704e","_nodeVersion":"22.14.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-Nlf8Mjz6FixdhFs1eOOfbOXBUhQfzfSbTH5rgNHEl8b+1Z/nykcxUEYrpMkvMW0EcnjU02UZrTSJIr/gcI8Spw==","shasum":"bbe04a0d33cea33f7a0b181fd86108a52537ed45","tarball":"https://registry.npmjs.org/@cloudcreators-gmbh/lexware-ts-sdk/-/lexware-ts-sdk-0.1.3.tgz","fileCount":12,"unpackedSize":36340,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDNZ/bKjEyEmqNu8PomPhiIq5ckZWE+AwOJ3GBLUmFxowIgHOSP9Gb7VhpyipjLf/PfgfuaZUaKtTDPdD2zlt9hYaQ="}]},"_npmUser":{"name":"cloudcreators-gmbh","email":"accounts@cloud-creators.de"},"directories":{},"maintainers":[{"name":"cloudcreators-gmbh","email":"accounts@cloud-creators.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/lexware-ts-sdk_0.1.3_1761731854404_0.8841978996205702"},"_hasShrinkwrap":false}},"time":{"created":"2025-10-29T09:57:34.339Z","0.1.3":"2025-10-29T09:57:34.592Z","modified":"2025-10-29T09:57:34.818Z"},"maintainers":[{"name":"cloudcreators-gmbh","email":"accounts@cloud-creators.de"}],"description":"TypeScript SDK for the Lexware Office Public API","license":"MIT","readme":"\n# Lexware TypeScript SDK\n\nNote: this is not an official SDK by Haufe/Lexware, it was developed by [Cloud Creators GmbH](https://cloud-creators.de) from Freiburg.\n\nA 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.\n\n> **Base URL**: `https://api.lexware.io` (May 26, 2025 rebrand; former gateway continues only until Dec 2025)  \n> **Auth**: API Key (Bearer) created in your Lexware account under **Add-ons → Public API**.\n\nNote: parts of this code including the Readme is AI-generated, so make sure to test your results and open bug reports or PRs.\n\n---\n\n## ✨ Features\n\n- First-class support for **voucherlist**, **invoices**, **contacts**, **vouchers**, **files**, **event-subscriptions (webhooks)**.\n- **Typed filters** and query builders (date ranges, status, voucher types, name search, etc.).\n- **Pagination helpers** with sensible defaults and auto iteration.\n- **File APIs**: download invoice PDFs / e-invoice XML via `/v1/invoices/{id}/file`; upload voucher files via `/v1/files`.\n- **Rate limiting** guard (2 req/s) with exponential backoff on `429` (configurable).\n- **Optimistic locking** helpers for PUTs with `version`.\n- **Node & TypeScript first**. Fully typed responses.\n\n---\n\n## 📦 Installation\n\n```bash\nnpm install @cloudcreators-gmbh/lexware-ts-sdk\n# or\nyarn add @cloudcreators-gmbh/lexware-ts-sdk\npnpm add @cloudcreators-gmbh/lexware-ts-sdk\n```\n\n---\n\n## ⚙️ Setup\n\n```ts\nimport { LexwareClient } from \"@cloudcreators-gmbh/lexware-ts-sdk\";\nconst client = new LexwareClient({ apiKey: process.env.LEXWARE_API_KEY! });\n```\n\n> **Tip:** Store the API key in your secrets manager. The SDK only sends it via the `Authorization: Bearer <token>` header to `api.lexware.io`.\n\n---\n\n## 🧭 Quick starts & Recipes\n\n### ▶️ Running the examples\n\nSet your API key and optional filters via environment variables (all examples use LEXWARE_-prefixed vars):\n\n```bash\n# Common\nexport LEXWARE_API_KEY=your_token\nexport LEXWARE_PAGE=0\nexport LEXWARE_SIZE=25\n\n# Contacts filters\nexport LEXWARE_NAME=\"Muster & Partner\"   # optional\nexport LEXWARE_EMAIL=\"info@example.org\"  # optional\nexport LEXWARE_CUSTOMER=true              # optional (true/false)\nexport LEXWARE_VENDOR=false               # optional (true/false)\n\n# Invoices filters\nexport LEXWARE_STATUS=\"any\"              # e.g. \"open,overdue\" or \"any\"\nexport LEXWARE_CONTACT_ID=\"...\"          # optional\nexport LEXWARE_NUMBER=\"RE-2025-0001\"     # optional\nexport LEXWARE_SORT=\"updatedDate,DESC\"   # optional\n\n# Voucherlist filters\nexport LEXWARE_VOUCHER_TYPE=\"invoice,purchaseinvoice\"  # optional\nexport LEXWARE_VOUCHER_STATUS=\"any\"                    # optional\nexport LEXWARE_FROM=\"2025-01-01\"                       # optional (yyyy-MM-dd)\nexport LEXWARE_TO=\"2025-01-31\"                         # optional (yyyy-MM-dd)\nexport LEXWARE_SORT=\"updatedDate,DESC\"                 # optional\n\n# Run\nnpm run example-contacts\nnpm run example-invoices\nnpm run example-voucherlist\n\n# Download a PDF for a specific invoice\nexport LEXWARE_INVOICE_ID=\"<invoice-id>\"\nnpm run example-download-invoice-pdf\n```\n\nNotes:\n- All examples require `LEXWARE_API_KEY` to be set.\n- Booleans accept: `true/false`, `1/0`, `yes/no`, `y/n`, `on/off`.\n- Status lists accept comma-separated values or `any`.\n\n### 1) List **invoices** and download the PDF (or XRechnung XML)\n\n```ts\n// Page through latest invoices (draft/open), newest first:\nconst page1 = await client.invoices.list({ page: 0 }); // default size 25\nfor (const inv of page1.content) {\n  console.log(inv.id, inv.voucherStatus, inv.voucherNumber);\n}\n\n// Download file (\"pdf\" or \"xml\" for XRechnung when available):\nconst bytes = await client.invoices.downloadFile(invId, 'pdf');\nawait fs.promises.writeFile(`invoice-${invId}.pdf`, Buffer.from(bytes));\n```\n\nNotes:\n- Use `client.invoices.downloadFile(id, {accept})` which hits `/v1/invoices/{id}/file` and returns raw bytes.\n- Invoices in **draft** have **no file**; server returns `409` in that case—SDK raises a typed error.\n\n---\n\n### 2) Find **contacts** (with paging and name/email filters)\n\n```ts\n// Fetch first page\nconst contacts = await client.contacts.list({ page: 0, size: 50 });\n\n// Filter by name and/or email\nconst result = await client.contacts.list({\n  name: \"johnson & partner\",\n  email: \"info@example.org\",\n  page: 0,\n  size: 25,\n});\nconsole.log(result.totalElements, result.content[0]?.person ?? result.content[0]?.company);\n```\n\n> 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.\n\n---\n\n### 3) Use the **voucherlist** for fast cross-entity searches\n\n```ts\n// List all purchase invoices and sales invoices that are open in March 2025:\nconst voucherPage = await client.voucherlist.list({\n  voucherType: [\"purchaseinvoice\", \"invoice\"],\n  voucherStatus: [\"open\"],\n  voucherDateFrom: \"2025-03-01\",\n  voucherDateTo: \"2025-03-31\",\n  size: 100, // up to 250\n  page: 0,\n});\n\n// Pick one and follow relations\nfor (const v of voucherPage.content) {\n  const contact = v.contactId ? await client.contacts.get(v.contactId) : null;\n  console.log(v.id, v.voucherType, v.voucherStatus, contact?.person?.firstName ?? contact?.company?.name);\n}\n```\n\n> 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.\n\n---\n\n### 4) Get a **voucher** by id (bookkeeping, e.g., purchaseinvoice) and follow to related contact\n\n```ts\nconst voucher = await client.vouchers.get(voucherId); // /v1/vouchers/{id}\nconst contact = voucher.contactId ? await client.contacts.get(voucher.contactId) : null;\n```\n\n---\n\n### 5) Upload a **voucher file** (PDF/JPG/PNG/XML) to create bookkeeping vouchers\n\n```ts\n// Creates/returns a bookkeeping voucher and file id (async OCR → status 'unchecked' later)\nconst data = await fs.promises.readFile(\"/path/to/receipt.pdf\");\nconst { id: fileId, voucherId } = await client.files.upload(data, \"receipt.pdf\", \"voucher\");\n```\n\n- Uses `POST /v1/files` with `multipart/form-data` and `type=voucher`.\n- Max file size **5 MB** for vouchers. If file already exists (checksum), server returns existing ids.\n- After upload, voucher is available via `/v1/vouchers/{id}`. Initial status may be `blank` until OCR completes, then `unchecked`.\n\n---\n\n---\n\n## 🔎 Pagination\n\nAll paged endpoints return a Spring-like page envelope:\n\n```ts\ntype Page<T> = {\n  content: T[];\n  first: boolean;\n  last: boolean;\n  totalPages: number;\n  totalElements: number;\n  numberOfElements: number;\n  size: number;  // requested size\n  number: number; // page index (0-based)\n  sort?: Array<{ direction: \"ASC\"|\"DESC\"; property: string }>;\n};\n```\n\n- Defaults: `size=25`, `page=0`. Maximum `size=250` for contacts, voucherlist, vouchers, etc.\n- 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`.\n\n---\n\n## ⏱️ Rate limits\n\n- The public API allows **2 requests/second** (token-bucket).  \n- SDK retries on `429` with exponential backoff and jitter. You can configure `rateLimit` or plug your own limiter.\n\n---\n\n## 🔐 Optimistic locking\n\n- `PUT` updates require a matching `version` (a revision number).  \n- 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).\n\n---\n\n## 🧱 Errors\n\nThe SDK throws typed errors with `status`, `code`, `i18nKey` (if present) and raw `response` snapshot. Handle known cases:\n\n- `401/403`: missing/invalid token.\n- `404`: resource not found.\n- `406`: unacceptable media type or validation issue (e.g., trying to render a PDF for a draft invoice).\n- `409`: version conflict or draft invoice file download attempt.\n- `429`: rate-limited (SDK retries).\n\n```ts\ntry {\n  await client.invoices.downloadFile(id);\n} catch (e) {\n  if (e.name === \"HttpError\" && e.status === 409) {\n    // invoice is draft → finalize first or poll until open\n  }\n}\n```\n\n---\n\n## 🧪 End-to-end examples\n\n### List invoices updated this week and download their files\n\n```ts\nimport { endOfToday, subDays, formatISO } from \"date-fns\";\n\nconst updatedFrom = formatISO(subDays(endOfToday(), 7), { representation: \"date\" });\n\nfor await (const v of client.voucherlist.iterateAll({\n  voucherType: [\"invoice\"],\n  updatedDateFrom: updatedFrom,\n  size: 250,\n})) {\n  try {\n    const bin = await client.invoices.downloadFile(v.id, 'pdf');\n    await fs.promises.writeFile(`out/${v.voucherNumber ?? v.id}.pdf`, Buffer.from(bin));\n  } catch (e) {\n    console.warn(`Skip ${v.id}: ${e}`);\n  }\n}\n```\n\n### Search contacts by name and map to invoices\n\n```ts\nconst c = await client.contacts.search({ name: \"Muster & Partner\", size: 50 });\nfor (const contact of c.content) {\n  const related = await client.voucherlist.list({\n    contactId: contact.id,\n    voucherType: [\"invoice\"],\n    size: 50,\n  });\n  console.log(contact.id, related.numberOfElements);\n}\n```\n\n---\n\n## 🔁 Filtering cheat-sheet\n\n- **voucherlist**: `voucherType[]`, `voucherStatus[]`, `voucherDateFrom/To`, `createdDateFrom/To`, `updatedDateFrom/To`, `dueDateFrom/To`, `voucherNumber`, `contactId`, `archived`, `page`, `size`.\n- **contacts**: `name`, `email`, role filters (customer/vendor), paging.\n- **vouchers** (bookkeeping): `GET /v1/vouchers/{id}`; filtering by `voucherNumber` is **deprecated**—use `voucherlist` instead.\n- **invoices**: `GET /v1/invoices/{id}`, file via `/v1/invoices/{id}/file` (PDF or XML for XRechnung).\n\n> The SDK auto-encodes search strings for endpoints that require **HTML+URL encoding** for `&`, `<`, `>`.\n\n---\n\n## 🗂 File handling\n\n- **Download invoice file**: `GET /v1/invoices/{id}/file` (use `accept` to pick `application/pdf` or `application/xml` for XRechnung).  \n- **Download bookkeeping voucher file**: `GET /v1/files/{id}`.  \n- **Upload voucher file**: `POST /v1/files` (`multipart/form-data` with `type=voucher`; max 5 MB).\n\n---\n\n## 🧰 SDK surface (high-level)\n\n```ts\nclass LexwareClient {\n  contacts: {\n    list(q?: { page?: number; size?: number; name?: string; email?: string; customer?: boolean; vendor?: boolean }): Promise<Page<Contact>>;\n    iterateAll(q?: Omit<{ page?: number; size?: number; name?: string; email?: string; customer?: boolean; vendor?: boolean }, 'page'>): AsyncGenerator<Contact>;\n    get(id: string): Promise<Contact>;\n    create(patch: Partial<Contact>): Promise<{ id: string; resourceUri: string; createdDate: string; updatedDate: string; version: number }>;\n    update(id: string, patch: Partial<Contact>): Promise<void>;\n  };\n\n  voucherlist: {\n    list(q: VoucherlistListParams): Promise<Page<VoucherListItem>>;\n    iterateAll(q: Omit<VoucherlistListParams, 'page'|'size'> & { size?: number }): AsyncGenerator<VoucherListItem>;\n  };\n\n  invoices: {\n    get(id: string): Promise<Invoice>;\n    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>>;\n    downloadFile(id: string, format?: 'pdf'|'xml'|'auto'): Promise<Uint8Array>;\n  };\n\n  vouchers: {\n    create(body: Omit<Voucher, 'id'|'organizationId'|'version'> & Partial<Pick<Voucher,'version'>>): Promise<{ id: string; resourceUri: string; createdDate: string; updatedDate: string; version: number }>;\n    get(id: string): Promise<Voucher>;\n    update(id: string, body: Partial<Voucher>): Promise<void>;\n    uploadFile(id: string, file: Blob | Uint8Array | ArrayBuffer | Buffer, filename: string): Promise<{ id: string }>;\n  };\n\n  files: {\n    upload(file: Blob | Uint8Array | ArrayBuffer | Buffer, filename: string, type?: 'voucher'): Promise<{ id: string; voucherId?: string }>;\n    download(id: string): Promise<Uint8Array>;\n  };\n\n  getRelatedContact(input: VoucherListItem | Voucher | Invoice | string, typeHint?: 'voucher'|'invoice'|'voucherlist'): Promise<Contact | null>;\n}\n```\n\n---\n\n## ✅ Best practices\n\n- Prefer **voucherlist** for cross-entity searches & filtering.\n- Handle **rate limits** and **429** with retries (SDK does this by default).\n- Respect **optimistic locking** (`version` on PUT/POST).\n- Use the new **`/v1/invoices/{id}/file`** instead of legacy render+files flow.\n- Narrow filters if you hit the **10,000-entry** window limit.\n- When searching in contacts/voucherlist, let the SDK do the required **HTML+URL encoding** of search strings.\n\n---\n\n## 📚 Types\n\nAll endpoints return fully typed models derived from the official API reference. You can import them directly:\n\n```ts\nimport type { Invoice, VoucherlistItem, Contact, Voucher, Page } from \"@cloudcreators-gmbh/lexware-ts-sdk/types\";\n```\n\n---\n\n## 🔗 Links\n\n- **Dashboard/Vouchers**: `https://app.lexware.de/vouchers`\n- **Create API Key**: `https://app.lexware.de/addons/public-api`\n- **Lexware API docs**: `https://developers.lexware.io/docs/#lexware-api-documentation`\n\n---\n\n","readmeFilename":"README.md","_rev":"1-67dbe455c9f5d77e40d8542c9ac3a3fc"}