Node.js / TypeScript SDK

The Node.js SDK wraps the global fetch function to passively capture outgoing API calls. Zero changes to your application logic.

Installation

npm install quotawatch

Quick example

app.tstypescript
import { QuotaWatch } from 'quotawatch';

// Initialize once at startup
QuotaWatch.init({
  apiKey: 'qw_live_...',
  environment: process.env.NODE_ENV ?? 'production',
  apis: [
    {
      name: 'OpenAI',
      baseUrl: 'https://api.openai.com',
      limits: { requestsPerMinute: 60, requestsPerDay: 10_000 },
    },
  ],
});

// Your existing code — unchanged
const res = await fetch('https://api.openai.com/v1/chat/completions', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
  body: JSON.stringify({ model: 'gpt-4o', messages: [] }),
});

How it works

On init(), the SDK monkey-patches globalThis.fetch. Every outgoing fetch() call is intercepted. If the URL matches a configured baseUrl, an event is recorded asynchronously and the real request proceeds immediately.

💡
The SDK is completely fire-and-forget. If the QuotaWatch ingest API is unreachable, events are silently dropped — your requests always go through unaffected.

What gets captured

{
  "api": "OpenAI",
  "endpoint": "/v1/chat/completions",
  "method": "POST",
  "status": 200,
  "latencyMs": 847,
  "timestamp": "2026-05-07T12:00:00Z",
  "environment": "production",
  "hit429": false,
  "rateLimitHeaders": {
    "x-ratelimit-remaining-requests": "43",
    "x-ratelimit-reset-requests": "2026-05-07T12:01:00Z"
  }
}
⚠️
No request or response bodies are ever captured. Only metadata: URL path, method, status code, latency, and rate limit response headers. This is non-negotiable.

Supported HTTP clients

ClientSupportedNotes
global fetchAuto-patched on init
axiosUse patchAxios() — see below
node-fetch⚠️Only if assigned to globalThis.fetch
undici⚠️Only if assigned to globalThis.fetch

Axios

Axios doesn't use globalThis.fetch, so it needs an explicit interceptor. Call patchAxios() once after QuotaWatch.init():

import axios from 'axios';
import { QuotaWatch, patchAxios } from 'quotawatch';

const qw = QuotaWatch.init({ ... });

// Patch the default axios instance
patchAxios(axios, qw);

// Or patch a custom instance
const client = axios.create({ baseURL: 'https://api.openai.com' });
patchAxios(client, qw);

// Now all calls through axios are monitored automatically
const res = await axios.post('https://api.openai.com/v1/chat/completions', body);
💡
Patch each Axios instance you use. If you create multiple axios.create() clients, call patchAxios() on each one.

Manual recording

Use instance.record()only when using clients that aren't auto-patched (e.g. a vendor SDK with a bundled HTTP transport, or a custom XMLHttpRequest wrapper). For fetch and Axios, the interceptors handle everything.

const qw = QuotaWatch.getInstance();
if (qw) {
  qw.record({
    api: 'MyAPI',              // must match your ApiConfig name
    endpoint: '/v1/resource',
    method: 'POST',
    status: 200,
    latencyMs: 142,
    timestamp: new Date().toISOString(),
    environment: 'production',
    hit429: false,
    rateLimitHeaders: {},      // e.g. { 'x-ratelimit-remaining': '42' }
  });
}

API reference

QuotaWatch.init(config)

Initializes the SDK. Call once at application startup. Returns the QuotaWatch instance. Calling init() more than once is a no-op (logs a warning).

OptionTypeDefaultDescription
apiKeystringRequired. Your project API key from Settings.
apisApiConfig[]Required. APIs to monitor (see below).
environmentstring'production'Tag events by environment (production/staging/dev).
ingestUrlstring'https://ingest.quotawatch.app'Where to send events. Set to your ingest service URL (e.g. http://localhost:3001 for local dev).
bufferSizenumber500Max events to buffer locally before dropping oldest.
flushIntervalMsnumber5000How often to flush buffered events (ms).

ApiConfig

FieldTypeDescription
namestringDisplay name — must match alert config names exactly.
baseUrlstringURL prefix to match. e.g. https://api.openai.com
limits.requestsPerMinutenumber?Used for threshold alert calculations.
limits.requestsPerHournumber?
limits.requestsPerDaynumber?
limits.requestsPerMonthnumber?

QuotaWatch.getInstance()

Returns the initialized instance, or null if not yet initialized.

instance.flush()

Manually flush buffered events. Useful before graceful shutdown:

process.on('SIGTERM', async () => {
  await QuotaWatch.getInstance()?.flush();
  process.exit(0);
});