npm package: @mayday.sh/sdk
Description: Third-party API dependency monitoring SDK
Latest version: 0.2.1

[![npm version](https://img.shields.io/npm/v/@mayday.sh/sdk.svg)](https://www.npmjs.com/package/@mayday.sh/sdk)
# @mayday.sh/sdk

**Know when your third-party APIs break before your users do.**

Mayday monitors every outbound HTTP call your Node.js application makes — Stripe, OpenAI, Twilio, AWS, and 100+ services — and alerts you the moment something degrades.

One SDK. Three lines of code. Real-time dependency health.

```ts
import { init } from '@mayday.sh/sdk'

init({ apiKey: 'md_live_...' })
// That's it. Every outbound HTTP call is now monitored.
```

## Why Mayday?

When a third-party API your app depends on starts failing, you need to know immediately — and you need to know if it's **them** or **you**.

- **Status pages are slow.** Vendor status pages lag 15-30+ minutes behind real incidents and miss partial degradations.
- **Full APM is overkill.** Datadog/New Relic cost $100K+/year and require multi-day instrumentation projects.
- **Mayday is focused.** One SDK, one job: monitor your outbound API calls and answer "is it us or them?" in seconds.

## How It Works

1. The SDK intercepts all outbound HTTP requests at the transport layer
2. Metrics are aggregated in-process into 1-minute buckets (no per-request export)
3. Compressed metric batches are sent to the Mayday backend every 30 seconds
4. The backend runs anomaly detection and alerts you via Slack, Discord, or email

**No request/response bodies are ever captured.** URLs are sanitized in-process before any data leaves your application. See [Privacy](#privacy).

## Installation

```bash
npm install @mayday.sh/sdk
```

Requires **Node.js 18+**.

## Quick Start

### 1. Get an API key

Sign up at [app.mayday.sh](https://app.mayday.sh) — one click with GitHub, no credit card.

### 2. Add to your app

**Express / Fastify / any Node.js server:**

```ts
// At the top of your entry file (server.ts, index.ts, app.ts)
import { init } from '@mayday.sh/sdk'

init({ apiKey: process.env.DEPWATCH_API_KEY })

// ... rest of your app
import express from 'express'
const app = express()
```

**Next.js (instrumentation hook):**

```ts
// instrumentation.ts (project root)
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { init } = await import('@mayday.sh/sdk')
    init({ apiKey: process.env.DEPWATCH_API_KEY })
  }
}
```

**Important:** Call `init()` as early as possible, before any HTTP calls are made.

### 3. Deploy and check the dashboard

Make some requests in your app and open your Mayday dashboard. You'll see every third-party service auto-discovered and categorized within 60 seconds.

## Configuration

```ts
init({
  // Required: your API key
  apiKey: 'md_live_...',

  // Disable monitoring (e.g., in test environments)
  // Default: true
  enabled: process.env.NODE_ENV !== 'test',

  // How often to export metrics (milliseconds)
  // Default: 30000 (30 seconds). Minimum: 5000.
  flushIntervalMs: 30000,

  // Domains to exclude from monitoring
  // The backend domain (api.mayday.sh) is auto-excluded.
  ignoreDomains: ['localhost', 'your-internal-api.local'],

  // Log every intercepted call to console (for verifying setup)
  // Default: false
  debug: true,

  // Custom backend URL (for self-hosted or local dev)
  // Default: 'https://api.mayday.sh'
  endpoint: 'http://localhost:3001',
})
```

## What's Captured

For each outbound HTTP request, the SDK records:

| Field | Example | Purpose |
|-------|---------|---------|
| Domain | `api.stripe.com` | Service identification |
| Endpoint | `/v1/charges/{id}` | Endpoint-level metrics |
| Method | `POST` | Endpoint grouping |
| Status code | `200` | Error rate calculation |
| Total duration | `145ms` | Latency tracking |
| DNS time | `2ms` | "Is it us or them?" |
| TCP connect time | `15ms` | Network diagnosis |
| TLS time | `20ms` | TLS issue detection |
| Time to first byte | `108ms` | Server processing time |
| Error flag | `false` | Error detection |

These fields are aggregated into 1-minute statistical buckets (count, min, max, P50, P95, P99, status code distribution) before export. Raw per-request data never leaves your application.

## What's NOT Captured

- Request bodies
- Response bodies
- Authorization headers
- Cookie values
- Query parameter values (keys are kept for endpoint grouping, values are stripped)
- Any PII

## Privacy

URL sanitization runs **in-process** before any data leaves your application:

```
Input:  https://api.stripe.com/v1/charges/ch_3MqAyZ2eZvKYlo/capture?expand[]=balance_transaction
Output: domain: api.stripe.com
        endpoint: /v1/charges/{id}/capture?expand=
```

- UUIDs → `{id}`
- Numeric IDs → `{id}`
- Base62/Base64 tokens → `{id}`
- Query values → stripped (keys preserved)

## Performance

The SDK is designed to be invisible in production:

| Metric | Target | How |
|--------|--------|-----|
| Latency overhead | <0.1ms per call | Event listeners, no sync work in request path |
| CPU | <0.5% of one core | Aggregation only, no serialization per request |
| Memory | <10MB RSS | Bounded ring buffer, DDSketch percentiles |
| Network | <50KB/min exported | 1-min aggregation reduces volume 100-500x |
| Startup | <50ms | Channel subscription + timer setup |

The export timer uses `setInterval().unref()` — it won't prevent your process from exiting.

## Interception

The SDK uses two complementary interception strategies:

1. **`diagnostics_channel`** (primary): Subscribes to undici's diagnostic events. Covers native `fetch()` in Node 18+ and direct undici usage. Zero monkey-patching.

2. **`http.request` patch** (fallback): Wraps Node's `http.request()` and `https.request()`. Covers axios, got, node-fetch v2, and any library using the built-in http module.

These are **disjoint code paths** — undici does not use Node's http module. Both are always enabled. Zero double-counting.

## Graceful Shutdown

```ts
import { shutdown } from '@mayday.sh/sdk'

process.on('SIGTERM', async () => {
  await shutdown() // Final metric flush + restore patched functions
  process.exit(0)
})
```

## Troubleshooting

**"I don't see any services on the dashboard"**

1. Verify `debug: true` is set — you should see log lines like:
   ```
   [mayday] POST api.stripe.com/v1/charges → 200 (145.2ms)
   ```
2. Check that `enabled` isn't set to `false`
3. Ensure the API key starts with `md_live_` (not `md_test_`)
4. Check that the backend is reachable (default: `https://api.mayday.sh`)

**"Some HTTP calls aren't being captured"**

- The SDK captures calls made **after** `init()` is called. Make sure `init()` runs before your app makes any requests.
- Calls to `localhost` or your own backend aren't captured by default — but you can check `ignoreDomains` to be sure.

**"I'm worried about performance impact"**

- Set `debug: true` temporarily and watch the console. The overhead is sub-millisecond.
- The SDK only records metadata (timing + status code). No request/response bodies are ever processed.
- Memory is bounded: max 10 slots × 1000 endpoints regardless of traffic volume.

## API

### `init(config: MaydayConfig): void`

Initialize the SDK. Call once at application startup.

### `shutdown(): Promise<void>`

Gracefully shut down: performs a final metric flush and restores all patched functions.

### `MaydayConfig`

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `apiKey` | `string` | (required) | API key (`md_live_*` or `md_test_*`) |
| `enabled` | `boolean` | `true` | Kill switch |
| `flushIntervalMs` | `number` | `30000` | Export interval (min 5000) |
| `ignoreDomains` | `string[]` | `[]` | Domains to exclude |
| `debug` | `boolean` | `false` | Log intercepted calls |
| `endpoint` | `string` | `https://api.mayday.sh` | Backend URL |
| `appId` | `string` | `null` | App identifier (multi-app) |

## License

MIT