Skip to content

Configuration

KSeFClient accepts configuration options that control environment selection, HTTP behavior, retry logic, rate limiting, and security policies.


Client Options

ts
import { KSeFClient } from 'ksef-client-ts';

const client = new KSeFClient({
  environment: 'PROD',         // 'TEST' | 'DEMO' | 'PROD' (default: 'TEST')
  timeout: 60_000,             // Request timeout in ms (default: 30000)
  customHeaders: { 'X-Request-Id': 'abc' },
  transport: customFetchFn,    // Replace native fetch
  retry: { maxRetries: 5 },    // Partial retry policy overrides
  rateLimit: { globalRps: 20 },// Partial rate limit config (or null to disable)
  circuitBreaker: { failureThreshold: 5 }, // Opt-in circuit breaker (undefined/null disables)
  presignedUrlHosts: ['*.my-storage.com'], // Additional allowed hosts
  authManager: customAuthMgr,  // Custom AuthManager implementation
  errorFormat: 'problem-details', // 'problem-details' (default) | 'legacy'
});

KSeFClientOptions

OptionTypeDefaultDescription
environment'TEST' | 'DEMO' | 'PROD''TEST'KSeF environment (sets base URLs)
baseUrlstringPer environmentOverride API base URL
baseQrUrlstringPer environmentOverride QR verification base URL
lighthouseUrlstringPer environmentOverride lighthouse status URL
apiVersionstring'v2'API version prefix
timeoutnumber30000Request timeout in milliseconds
customHeadersRecord<string, string>{}Additional headers on every request
transportTransportFnNative fetchCustom HTTP transport function
retryPartial<RetryPolicy>See belowRetry policy overrides
rateLimitPartial<RateLimitConfig> | null{ globalRps: 10 }Rate limit config, or null to disable
circuitBreakerPartial<CircuitBreakerConfig> | nullOpt-in HTTP circuit breaker. undefined / omitted / null disables the policy entirely; pass a partial config (even {}) to enable with defaults merged in
presignedUrlHostsstring[]Additional allowed hosts for presigned URLs
authManagerAuthManagerDefaultAuthManagerCustom auth/token manager
errorFormat'problem-details' | 'legacy''problem-details'Request format for server error bodies. 'legacy' suppresses the X-Error-Format header for older servers/proxies (KSeF API v2.4.0+)

Retry Policy

Failed requests are automatically retried with exponential backoff. The retry policy is merged with defaults — you only need to specify the fields you want to override.

What gets retried

HTTP status codes (default): 429, 500, 502, 503, 504

Network errors (when retryNetworkErrors: true): ECONNRESET, ECONNREFUSED, ETIMEDOUT, UND_ERR_CONNECT_TIMEOUT, and AbortError (fetch timeout).

All HTTP methods are retried, including POST. This is safe because KSeF API operations are idempotent by design — for example, submitting the same invoice returns the same KSeF number.

Backoff formula

delay = min(baseDelayMs × 2^attempt + random(0, baseDelayMs), maxDelayMs)

For 429 responses with a Retry-After header, the server-specified delay is used instead of the calculated backoff. Retry-After is parsed as seconds (integer) or HTTP-date.

Configuration

FieldTypeDefaultDescription
maxRetriesnumber3Maximum retry attempts
baseDelayMsnumber500Base delay in milliseconds
maxDelayMsnumber30000Maximum delay cap in milliseconds
retryableStatusCodesnumber[][429, 500, 502, 503, 504]HTTP status codes to retry
retryNetworkErrorsbooleantrueRetry on network errors

Examples

ts
// More aggressive retries
const client = new KSeFClient({
  retry: {
    maxRetries: 5,
    baseDelayMs: 1000,
    maxDelayMs: 60_000,
  },
});

// Add 409 Conflict to retryable codes
const client = new KSeFClient({
  retry: {
    retryableStatusCodes: [409, 429, 500, 502, 503, 504],
  },
});

// Disable retries entirely
const client = new KSeFClient({
  retry: { maxRetries: 0 },
});

Rate Limiting

A token bucket rate limiter prevents overwhelming the KSeF API. Requests exceeding the limit are delayed (not rejected).

How it works

  • Global bucket: All requests share a global tokens-per-second limit (default: 10 RPS)
  • Per-endpoint buckets: Optional per-endpoint limits (created lazily on first use)
  • A request must pass both the global bucket and its endpoint-specific bucket (if configured)
  • Rate limit is acquired once before the retry loop; on 429 retries, a token is re-acquired
  • Concurrency-safe via sequential promise chain

Configuration

FieldTypeDefaultDescription
globalRpsnumber10Global requests per second
endpointLimitsRecord<string, number>{}Per-endpoint RPS limits

Examples

ts
// Higher global limit
const client = new KSeFClient({
  rateLimit: { globalRps: 20 },
});

// Per-endpoint limits
const client = new KSeFClient({
  rateLimit: {
    globalRps: 15,
    endpointLimits: {
      '/v2/online/Invoice/Send': 5,
    },
  },
});

// Disable rate limiting entirely
const client = new KSeFClient({
  rateLimit: null,
});

KSeF server-side limits (v2.4.0)

The KSeF API enforces its own per-endpoint caps. If you opt into client-side back-pressure via endpointLimits, use the values below to match the current server limits. The library does not apply these defaults automatically — the server enforces them regardless.

Endpointreq/sreq/min
POST /invoices/exports816
POST /invoices/query/metadata816

Prior to KSeF API v2.4.0, POST /invoices/exports was capped at 4 req/s and 8 req/min; v2.4.0 aligned it with query/metadata. The client-side token bucket only supports a per-second window — minute-level ceilings are enforced server-side only, so keep globalRps at or below the per-second cap to stay within the minute budget under sustained load.


Circuit Breaker

Opt-in HTTP circuit breaker that fails fast when the upstream is known to be unavailable. Sits above the retry loop: once open, every matching request raises KSeFCircuitOpenError without hitting the network, so a batch of requests during an outage consumes one retry budget instead of one-per-request. When the cooldown elapses, the breaker lets a single probe through; on success it clears, on failure it re-opens. See HTTP Resilience — Circuit Breaker for the state machine and retry-loop interaction.

The feature is off by default. Pass a (possibly empty) partial config to enable it; null and undefined both disable.

What counts as a failure

OutcomeCounted?
Network error (ECONNRESET, ETIMEDOUT, etc.)Yes
5xx response after retries exhaustedYes
429 Too Many RequestsNo (throttling, not an outage)
401 UnauthorizedNo (auth problem, not availability)
2xx / 4xx (other than 401/429)No (records success)

Configuration

FieldTypeDefaultDescription
failureThresholdnumber5Consecutive failures within openMs before opening. Must be > 0.
openMsnumber30000Cooldown window in ms. Requests fail fast while open; one probe is allowed after it elapses. Must be > 0.
scope'global' | 'endpoint''global''global' uses one breaker for the whole client. 'endpoint' keeps per-endpoint state so an outage on one route leaves the others alone.

Examples

ts
// Enable with defaults (threshold 5, cooldown 30s, global scope)
const client = new KSeFClient({
  circuitBreaker: {},
});

// Aggressive: open sooner, stay open longer, per-endpoint scope
const client = new KSeFClient({
  circuitBreaker: {
    failureThreshold: 3,
    openMs: 60_000,
    scope: 'endpoint',
  },
});

// Explicitly disable (same as omitting the option)
const client = new KSeFClient({
  circuitBreaker: null,
});

Handle the new error class where it makes sense:

ts
import { KSeFCircuitOpenError } from 'ksef-client-ts';

try {
  await client.invoices.sendInvoice(xml);
} catch (err) {
  if (err instanceof KSeFCircuitOpenError) {
    // Park the work for later; don't retry-storm a known-down upstream
    await parkForLater(xml, err.retryAfterMs);
    return;
  }
  throw err;
}

Presigned URL Policy

When downloading files from presigned URLs returned by the KSeF API (e.g., batch exports, UPO downloads), the library validates URLs against a security policy to prevent SSRF attacks.

Security checks (in order)

  1. HTTPS enforcement — HTTP URLs are rejected (default: enabled)
  2. Host whitelist — Only URLs matching allowed host patterns are permitted. Wildcard patterns (*.domain.com) match any subdomain. Default: ['*.ksef.mf.gov.pl']
  3. Redirect parameter blocking — URLs containing redirect, callback, return_url, or next query parameters are rejected (case-insensitive)
  4. Private IP rejection — URLs targeting private/reserved IPs are rejected:
    • IPv4: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16
    • IPv6: ::1, fc00::/7, fe80::/10

The first failing check throws KSeFValidationError.

Adding allowed hosts

Use presignedUrlHosts in client options to add hosts beyond the default *.ksef.mf.gov.pl:

ts
const client = new KSeFClient({
  presignedUrlHosts: ['*.my-corporate-storage.com', 'cdn.example.com'],
});

The additional hosts are merged with the defaults — you don't need to repeat *.ksef.mf.gov.pl.


Custom Transport

Replace the default fetch with a custom transport function for testing, logging, or proxying.

Type signature

ts
type TransportFn = (url: string, init: RequestInit) => Promise<Response>;

The transport receives the fully-constructed URL and RequestInit (including method, headers, body, and AbortSignal for timeouts).

Examples

ts
// Logging transport
const client = new KSeFClient({
  transport: async (url, init) => {
    console.log(`${init.method} ${url}`);
    const start = Date.now();
    const res = await fetch(url, init);
    console.log(`${res.status} in ${Date.now() - start}ms`);
    return res;
  },
});

// Mock transport for tests
const client = new KSeFClient({
  transport: async (url, init) => {
    return new Response(JSON.stringify({ ok: true }), {
      status: 200,
      headers: { 'Content-Type': 'application/json' },
    });
  },
});

// Proxy transport
import { ProxyAgent } from 'undici';

const agent = new ProxyAgent('http://proxy.corp.com:8080');
const client = new KSeFClient({
  transport: (url, init) => fetch(url, { ...init, dispatcher: agent }),
});

Released under the MIT License.