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

xingen-sdk

v0.2.0

Published

TypeScript client SDK for the Xingen e-invoice validation API

Readme

xingen-sdk

TypeScript client SDK for the Xingen e-invoice validation API — submit UBL, CII, ZUGFeRD, and SAP IDoc/OData invoices for validation against EN16931, XRechnung, and Peppol.

Requires Node 22+. Built on native fetch/FormData — the only runtime dependency is lossless-json, used internally to preserve exact monetary/quantity precision (see Design notes).

Status: v1, covering invoice submission/validation and API key management. Contacts and dashboard/user endpoints are not exposed (they're Firebase-auth-only on the backend).

Install

npm install xingen-sdk

Ships both ESM and CommonJS builds, so both import and require() work.

Authentication

Every request needs an API key (xgn_live_... for production, xgn_test_... for sandbox — sandbox requests never count toward quota). Create one from the Xingen dashboard or via client.apiKeys.

import { XingenClient } from "xingen-sdk";

const client = new XingenClient({ apiKey: process.env.XINGEN_API_KEY! });

baseUrl overrides the default https://app.xingen.de/api, useful for self-hosted or local (./gradlew bootRun, port 10001) testing. timeoutMs (default 30000) covers the whole request.

Validate a file

Every validate/submit endpoint is asynchronous — the backend queues the invoice and returns immediately. Use a *AndWait helper to submit and poll for the result in one call:

import { InvoiceStatus, ValidationProfile } from "xingen-sdk";

const result = await client.invoices.validateFileAndWait("invoice.xml", ValidationProfile.XRECHNUNG);

if (result.status === InvoiceStatus.VALIDATED && result.validationResult?.valid) {
  console.log("Valid!");
} else {
  for (const error of result.validationResult?.errors ?? []) {
    console.log(`${error.severity}: ${error.message} (${error.field})`);
  }
}

PollOptions controls the backoff (initialIntervalMs, maxIntervalMs, backoffMultiplier), the overall timeoutMs, and an optional cancellationCheck. A failed validation is not an error — it's a completed API call that found the invoice invalid, so *AndWait resolves normally with validationResult.valid === false. Only a transport failure, cancellation, or timeout rejects.

const result = await client.invoices.validateFileAndWait(
  "invoice.xml",
  ValidationProfile.XRECHNUNG,
  { initialIntervalMs: 300, maxIntervalMs: 3000, timeoutMs: 30000 },
);

If you'd rather manage polling yourself, use the low-level pair:

const submitted = await client.invoices.validateFile("invoice.xml", ValidationProfile.EN16931);
// ... later ...
const record = await client.invoices.get(submitted.id);

validateIdoc/validateIdocAndWait work the same way for SAP IDoc XML files. Both, and validateFile/validateFileAndWait, also accept { filename, content } (a Uint8Array) if you already hold the file bytes in memory instead of a path — the bare-path form only works in Node (read via node:fs/promises), so browser/edge callers should always use the in-memory form.

Submit a structured invoice (JSON)

Monetary and quantity fields (price, quantity, taxRate, etc.) are typed as string, not number — see Design notes for why.

const result = await client.invoices.submitAndWait({
  invoiceNumber: "INV-2024-0042",
  issueDate: "2024-03-15",
  currency: "EUR",
  buyerReference: "991-12345-06",
  validationProfile: ValidationProfile.XRECHNUNG,
  supplier: {
    name: "Acme GmbH",
    vatId: "DE123456789",
    address: { city: "Berlin", countryCode: "DE" },
  },
  buyer: {
    name: "Buyer Co",
    leitwegId: "991-12345-06",
    address: { countryCode: "DE" },
  },
  lines: [
    { description: "Software License Q1", quantity: "5", unit: "C62", price: "199.00", taxRate: "19" },
  ],
  paymentMeans: [{ typeCode: "58", creditTransferAccountId: "DE89370400440532013000" }],
});

InvoiceSubmission has full parity with the backend's domain model — every invoice type it can validate, it can also submit, including non-standard VAT categories (exempt/reverse-charge/export via LineInput.taxCategoryCode + exemptionReason/exemptionReasonCode), payee/ taxRepresentative, delivery, invoicePeriod, precedingInvoiceReferences (for credit notes), and the full BT-11..BT-19 document reference fields. See the types exported alongside InvoiceSubmission for the complete set.

SAP S/4HANA OData supplier-invoice payloads are supported as a thin passthrough — pass raw JSON or a plain object rather than a fully typed model:

await client.invoices.submitOData(rawODataJson, ValidationProfile.EN16931);

Extract an invoice from a PDF (AI)

Upload a plain invoice PDF — including scanned/image-based PDFs — and let the backend extract structured fields with Claude. Works exactly like the other submit endpoints: async, so use extractInvoiceAndWait or the low-level extractInvoice/get pair.

import { ExtractionModelTier, ValidationProfile } from "xingen-sdk";

const result = await client.invoices.extractInvoiceAndWait(
  "scanned-invoice.pdf",
  ValidationProfile.EN16931,
  ExtractionModelTier.FAST, // or ACCURATE -- higher accuracy, Pro subscription required
);

If the extraction missed a field or validation flagged something, correct it with a JSON merge-patch (RFC 7386) and re-validate synchronously — only invoices that finished processing (VALIDATED or FAILED_VALIDATION) can be corrected. Array fields (lines, paymentMeans, allowanceCharges, taxBreakdowns) are replaced wholesale when present in the patch:

const corrected = await client.invoices.patchInvoice(result.id, {
  currency: "EUR",
  buyerReference: "991-12345-06",
});

To find out which fields the backend fills in automatically per profile (so you know what not to prompt the user for):

const autoFilled = await client.invoices.getAutoFilledFields();

List and retrieve invoices

const page = await client.invoices.list(0, 20, "createdAt,desc");

// or, to walk every invoice without managing page indices yourself:
for await (const record of client.invoices.listAll(50)) {
  console.log(record.id, "->", record.status);
}

const one = await client.invoices.get("inv_01HXYZ");

Download results

const pdf = await client.invoices.downloadPdf(id); // ZUGFeRD PDF with embedded XML, Uint8Array
const idocXml = await client.invoices.downloadIdocXml(id); // SAP IDoc XML, Uint8Array

API keys

const created = await client.apiKeys.create({ name: "Production CI", sandbox: false });
console.log("Store this now, it's shown only once:", created.rawKey);

const keys = await client.apiKeys.list();
await client.apiKeys.revoke(created.id);

Error handling

All SDK errors extend XingenError. HTTP errors map to typed subclasses of ApiError:

| Error | Status | Notes | |---|---|---| | AuthenticationError | 401 | Missing or invalid API key | | PermissionError | 403 | Resource exists but isn't owned by the caller | | NotFoundError | 404 | | | ValidationRequestError | 400 | .fieldErrors has details for request-body validation failures | | QuotaExceededError | 429 | Monthly request quota exhausted | | ApiError | other 4xx/5xx | Fallback; .statusCode / .rawBody always available |

import { QuotaExceededError, ValidationRequestError, XingenError } from "xingen-sdk";

try {
  await client.invoices.submit(submission);
} catch (error) {
  if (error instanceof ValidationRequestError) {
    for (const [field, message] of Object.entries(error.fieldErrors)) {
      console.log(`${field}: ${message}`);
    }
  } else if (error instanceof QuotaExceededError) {
    console.log("Quota exceeded — upgrade or wait for the next billing period");
  } else if (error instanceof XingenError) {
    console.log(`Request failed: ${error.message}`);
  }
}

Design notes

  • No automatic retries. Retrying a submit() after a client-side timeout is unsafe without idempotency keys, which the API doesn't support yet — a retried submit could create a duplicate invoice. Handle retries at the call site if you need them.
  • Monetary and quantity fields are string, never number. JavaScript numbers are IEEE-754 doubles with no arbitrary-precision decimal type. Worse, native JSON.parse's reviver runs after numeric literals are already converted to number — it cannot recover lost precision. This SDK parses every response through lossless-json instead, which intercepts numeric tokens before conversion, so every number becomes its exact source-text string; each model then narrows only the handful of known-safe small integer counters (page numbers, quota counts, etc.) back to number. Monetary/quantity fields are left untouched — parse them with your own decimal library if you need arithmetic.
  • Dates are string (ISO 8601), not Date. Date is mutable, has no timezone-safe date-only representation, and JSON.parse never reconstructs Date objects automatically anyway — keeping dates as strings avoids a whole class of timezone-shift bugs for date-only fields like issueDate.

Contributing

npm install
npm run lint
npm run typecheck
npm test
npm run build

Tests run against a real (loopback) node:http server, not a mocking library — no network calls leave the machine, and no external test-server dependency is required.

License

MIT — see LICENSE.