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

@jtl-software/cloud-apps-rest-sdk

v0.0.1

Published

TypeScript REST client for the JTL Wawi ERP Cloud API, generated with Microsoft Kiota.

Readme

@jtl-software/cloud-apps-rest-sdk

TypeScript REST client for the JTL ERP API. Generated with Microsoft Kiota from the official OpenAPI specification.

npm

Installation

npm install @jtl-software/cloud-apps-rest-sdk

That's it. The SDK bundles the Kiota HTTP transport (@microsoft/kiota-http-fetchlibrary), JSON serializer (@microsoft/kiota-serialization-json) and abstractions as direct dependencies. You don't need to install or wire them yourself.

Requires Node.js 20 or later. ESM only ("type": "module").

Quick start

import {
  createJtlClient,
  createTenantContext,
} from "@jtl-software/cloud-apps-rest-sdk";

const { getCurrentTenant, withTenant } = createTenantContext();

const client = createJtlClient({
  auth: {
    clientId: process.env.OAUTH_CLIENT_ID!,
    clientSecret: process.env.OAUTH_CLIENT_SECRET!,
  },
  tenantId: getCurrentTenant,
});

// In your request handler, pass the tenant as the first argument of the withTenant scope for the SDK to pick up.
await withTenant(tenantIdFromRequest, async () => {
  const info = await client.v2.info.get();
  console.log(info);
});

That's the whole setup. The SDK handles the OAuth 2.0 client-credentials flow internally: it fetches a token on first use, caches it, refreshes it ~60 seconds before expires_in, and de-duplicates concurrent fetches. Every outgoing request gets Authorization: Bearer <token> and X-Tenant-ID (resolved per request, see Tenant ID).

Configuration

createJtlClient(options) accepts:

| Option | Type | Description | | --- | --- | --- | | auth | ClientCredentialsOptions \| string \| (() => string \| Promise<string>) | Required. See Authentication below. | | tenantId | () => string \| undefined | Resolver called on every request, injects the result as X-Tenant-ID. See Tenant ID. | | baseUrl | string | Optional. Overrides the API base URL. Defaults to https://api.jtl-cloud.com/erp. |

Authentication

auth accepts three shapes:

1. Client-credentials (recommended)

Pass { clientId, clientSecret } and the SDK runs the OAuth 2.0 client-credentials flow for you:

createJtlClient({
  auth: {
    clientId: "your-client-id",
    clientSecret: "your-client-secret",
    // Optional:
    // tokenUrl: "https://auth.jtl-cloud.com/oauth2/token",  // default shown
    // scope: "...",
    // renewBeforeExpiry: 60_000,  // ms before expires_in to fetch a new token
  },
});

2. Static bearer token

If you already have a token (e.g. fetched by your auth gateway):

createJtlClient({ auth: process.env.ACCESS_TOKEN! });

The SDK won't refresh this. Useful for short-lived scripts and testing.

3. Custom async resolver

For any other auth flow, pass a function. Return a token, the SDK calls it on every request, you decide what to cache:

createJtlClient({
  auth: async () => {
    return await myAuthService.getToken();
  },
});

Tenant ID

A backend using this SDK proxies requests from many users on different tenants to the JTL ERP API. The same cloud-app OAuth credentials authenticate against all of them, only the X-Tenant-ID header on each outbound request changes. Share one JtlClient (and its token cache) across all tenants, and resolve the tenant per request via createTenantContext(), which wraps Node's AsyncLocalStorage.

createTenantContext() returns { withTenant, getCurrentTenant }:

  • withTenant(tenantId, fn): executes fn with tenantId as the active value for any async work it triggers.
  • getCurrentTenant(): returns the active tenant ID, or undefined if called outside a withTenant scope.

Pass getCurrentTenant as the tenantId option on createJtlClient, then call withTenant from your request middleware. If getCurrentTenant() returns undefined at request time the SDK omits the header. The ERP API rejects most v2 endpoints without X-Tenant-ID, so make sure your middleware sets it before any handler that calls the SDK.

This mirrors the C# SDK's AddHttpContextTenantContext.

Express example

How your backend obtains a tenant ID is entirely your app's concern, the SDK doesn't care. It could come from:

  • a JTL session token your frontend forwards to you (the example below),
  • your own app's JWT (cookie or Authorization header) that you signed yourself,
  • a database lookup keyed by your own user ID,
  • any other mechanism that yields a string at request time.

The example below shows the JTL-session-token shape because it's the common case for cloud apps. It uses jose for JWT verification, but if you already have your own auth/session middleware that knows the tenant, just pass that value into withTenant instead, the rest of the wiring is the same.

import express, { type NextFunction, type Request, type Response } from "express";
import { createRemoteJWKSet, jwtVerify } from "jose";
import {
  createJtlClient,
  createTenantContext,
} from "@jtl-software/cloud-apps-rest-sdk";

const { getCurrentTenant, withTenant } = createTenantContext();

const client = createJtlClient({
  auth: {
    clientId: process.env.OAUTH_CLIENT_ID!,
    clientSecret: process.env.OAUTH_CLIENT_SECRET!,
  },
  tenantId: getCurrentTenant,    // resolved per request
});

// JTL signs session tokens with EdDSA. Cache the JWKS once at startup;
// jose handles refresh internally.
const JWKS = createRemoteJWKSet(
  new URL("https://api.jtl-cloud.com/account/.well-known/jwks.json"),
);

async function tenantFromSessionToken(token: string): Promise<string> {
  const { payload } = await jwtVerify(token, JWKS);
  return (payload as { tenantId: string }).tenantId;
}

const app = express();

// Verify the inbound session token from your frontend, pull the tenant
// ID out of its payload, and scope it for the downstream handlers.
// The header name (`X-Session-Token` here) is your app's choice;
// don't confuse it with the ERP API's `X-Tenant-ID`, which the SDK
// sets for you.
app.use(async (req: Request, res: Response, next: NextFunction) => {
  const sessionToken = req.header("x-session-token");
  if (!sessionToken) {
    res.status(401).json({ error: "Missing X-Session-Token header" });
    return;
  }
  try {
    const tenantId = await tenantFromSessionToken(sessionToken);
    withTenant(tenantId, next);
  } catch {
    res.status(401).json({ error: "Invalid session token" });
  }
});

app.get("/companies", async (_req, res) => {
  // X-Tenant-ID injected automatically by the SDK from the current scope.
  res.json(await client.v2.companies.get());
});

app.listen(3000);

Only the middleware changes if you obtain the tenant ID differently, the createJtlClient setup and the handlers stay identical. The same pattern works with Fastify (fastify.addHook("onRequest", ...)), Hono (app.use(async (c, next) => withTenant(id, next))), or any other framework with per-request middleware.

API shape

The client exposes the v2 surface of the JTL ERP API as a fluent builder. Examples:

await client.v2.companies.get();
await client.v2.salesOrders.get({ queryParameters: { top: 50 } });
await client.v2.salesOrders.byId("ORDER-1").get();
await client.v2.salesOrders.post(newOrder);

Refer to the generated .d.ts files in your editor for the full surface.

Advanced

The high-level factory above suits the common case. If you need more control, the SDK also exports the building blocks:

  • createClientCredentialsTokenProvider(options): returns the cached () => Promise<string> used internally. Useful if you want to share one token cache across multiple clients or pass the function as a custom auth resolver.
  • createDefaultAdapter(options): returns the underlying RequestAdapter so you can construct the generated client yourself or chain additional middleware.
  • createApiClient(adapter): the bare-bones constructor from the generated code; takes any RequestAdapter and gives you the typed client.
  • createTenantContext(): AsyncLocalStorage helper for per-request tenant scoping. See Tenant ID.

Documentation

Support

Issues and feature requests: github.com/jtl-software/jtl-platform-erp-sdk/issues

License

Copyright (c) JTL-Software GmbH. See the repository for license terms.