npm package: @isava/sentinel-sdk
Description: Official JavaScript/TypeScript SDK for the Sentinel Watch Security Platform
Author: Sentinel Watch
Latest version: 2.1.0

# sentinel-watch-sdk

Official **JavaScript/TypeScript SDK** for the [Sentinel Watch](https://sentinel-watch.io) Security Platform.

Built to Stripe SDK standards — fully typed, tree-shakeable, zero runtime dependencies, works in Node 18+, browsers, and edge runtimes.

---

## Install

```bash
npm install sentinel-watch-sdk
# or
yarn add sentinel-watch-sdk
# or
pnpm add sentinel-watch-sdk
```

**Optional:** Install `axios` if you prefer it over the native Fetch API:

```bash
npm install axios
```

---

## Quick Start

```typescript
import SentinelClient from 'sentinel-watch-sdk';

const sentinel = new SentinelClient({
  baseUrl: 'https://api.your-sentinel.io/api',
  timeout: 15_000,
  maxRetries: 3,
});

// 1. Login — token stored automatically, no manual header management
const { user } = await sentinel.auth.login('analyst@company.com', 'p@ssw0rd');
console.log(user.role);           // 'security_analyst'
console.log(user.dashboardRoute); // '/analyst'

// 2. Work with typed resources
const { data: alerts, total } = await sentinel.alerts.list({
  severity: 'critical',
  status:   'open',
  limit:    20,
});

// 3. Shorthand actions
await sentinel.alerts.investigate(alerts[0]._id);
await sentinel.alerts.resolve(alerts[0]._id);

// 4. Dashboard + analyst summary in one call
const [stats, summary] = await Promise.all([
  sentinel.dashboard.stats(),
  sentinel.analyst.summary(),
]);
console.log(stats.alerts.open);           // e.g. 14
console.log(summary.vulnerabilities.open); // e.g. 5
```

---

## Configuration

```typescript
const sentinel = new SentinelClient({
  // Required in production
  baseUrl: 'https://api.your-sentinel.io/api',

  // Pre-set token (useful in server-side apps with stored sessions)
  token: process.env.SENTINEL_TOKEN,

  // Request timeout in ms (default: 30_000)
  timeout: 15_000,

  // Max retry attempts for 5xx errors (default: 2, exponential backoff)
  maxRetries: 3,

  // 'auto' | 'fetch' | 'axios'  (default: 'auto')
  transport: 'axios',

  // Verbose request/response logging — dev only
  debug: process.env.NODE_ENV === 'development',
});
```

---

## Error Handling

Every error the SDK throws is an instance of `SentinelError` or one of its subclasses. This mirrors the Stripe SDK pattern — catch what you need, let everything else bubble.

```typescript
import {
  SentinelClient,
  SentinelError,
  AuthenticationError,
  PermissionError,
  NotFoundError,
  ValidationError,
  RateLimitError,
  BillingError,
  NetworkError,
  TimeoutError,
  isSentinelError,
} from 'sentinel-watch-sdk';

try {
  const alert = await sentinel.alerts.get('bad-id');
} catch (err) {
  if (err instanceof NotFoundError) {
    console.error('Alert not found');                         // 404
  } else if (err instanceof AuthenticationError) {
    // Token expired — redirect to login
    sentinel.clearToken();
    redirectToLogin();
  } else if (err instanceof ValidationError) {
    // Server-side field validation failed
    err.fieldErrors.forEach(e => console.error(e.field, e.message));
  } else if (err instanceof RateLimitError) {
    // Back off and retry
    await sleep(err.retryAfter ? err.retryAfter * 1000 : 2000);
  } else if (err instanceof BillingError) {
    console.error('Upgrade your plan to access this feature.');
  } else if (err instanceof NetworkError) {
    console.error('No internet connection.');
  } else if (err instanceof TimeoutError) {
    console.error('Request timed out — try again.');
  } else if (isSentinelError(err)) {
    // All other Sentinel errors
    console.error(err.code, err.statusCode, err.message);
  } else {
    throw err; // Unknown — rethrow
  }
}
```

### Error classes

| Class                 | HTTP status | Code                    | When                                   |
|-----------------------|-------------|-------------------------|----------------------------------------|
| `AuthenticationError` | 401         | `authentication_error`  | Token missing, invalid, or expired     |
| `PermissionError`     | 403         | `permission_denied`     | Valid token but wrong role             |
| `NotFoundError`       | 404         | `resource_not_found`    | Resource doesn't exist                 |
| `ConflictError`       | 409         | `resource_already_exists`| Duplicate email, name, etc.           |
| `ValidationError`     | 400         | `validation_error`      | Body failed server validation          |
| `RateLimitError`      | 429         | `rate_limit_error`      | Too many requests                      |
| `BillingError`        | 402         | `subscription_inactive` | Plan limit, trial expired, etc.        |
| `ApiError`            | 5xx         | `api_error`             | Server threw an unexpected error       |
| `NetworkError`        | —           | `network_error`         | Fetch failed before response received  |
| `TimeoutError`        | —           | `timeout_error`         | Request exceeded `timeout` ms          |

---

## Interceptors

```typescript
// Add a custom header to every request
sentinel.addRequestInterceptor(req => ({
  ...req,
  headers: { ...req.headers, 'X-Tenant-ID': tenantId },
}));

// Log every response with its request ID
sentinel.addResponseInterceptor(res => {
  console.log(`[${res.statusCode}] ${res.requestId}`);
  return res;
});

// Auto-logout when token expires
sentinel.addErrorInterceptor(err => {
  if (err instanceof AuthenticationError) {
    sentinel.clearToken();
    window.location.href = '/login';
  }
  throw err;
});
```

---

## All Resources

### Core

```typescript
// ── Auth ──────────────────────────────────────────────────────────────────
await sentinel.auth.login(email, password)
await sentinel.auth.register(name, email, password, orgName?)
await sentinel.auth.me()
await sentinel.auth.forgotPassword(email)
await sentinel.auth.resetPassword(token, newPassword)
await sentinel.auth.changePassword(current, new)
sentinel.auth.logout()                  // clears token locally

// ── Alerts ────────────────────────────────────────────────────────────────
await sentinel.alerts.list({ severity, status, page, limit })
await sentinel.alerts.get(id)
await sentinel.alerts.create({ title, severity, source, ... })
await sentinel.alerts.update(id, { status, ... })
await sentinel.alerts.investigate(id)   // shorthand
await sentinel.alerts.resolve(id)       // shorthand
await sentinel.alerts.stats()           // → { total, critical, high, open, ... }
await sentinel.alerts.delete(id)

// ── Incidents ─────────────────────────────────────────────────────────────
await sentinel.incidents.list({ status, severity })
await sentinel.incidents.get(id)
await sentinel.incidents.create({ title, severity })
await sentinel.incidents.update(id, { status })
await sentinel.incidents.contain(id)    // shorthand → status: 'contained'
await sentinel.incidents.resolve(id)    // shorthand → status: 'resolved'
await sentinel.incidents.close(id)      // shorthand → status: 'closed'
await sentinel.incidents.stats()

// ── Organizations ─────────────────────────────────────────────────────────
await sentinel.organizations.list()
await sentinel.organizations.pending()  // orgs awaiting approval
await sentinel.organizations.get(id)
await sentinel.organizations.approve(id)
await sentinel.organizations.reject(id, reason?)
await sentinel.organizations.suspend(id)
await sentinel.organizations.members(id)

// ── Users ─────────────────────────────────────────────────────────────────
await sentinel.users.list({ role, organizationId })
await sentinel.users.get(id)
await sentinel.users.create({ name, email, password, role })
await sentinel.users.update(id, { name, isActive })
await sentinel.users.assignSystems(id, [systemId1, systemId2])
await sentinel.users.delete(id)

// ── Systems, Threats, Detection Rules ────────────────────────────────────
await sentinel.systems.list()
await sentinel.threats.list({ severity, status })
await sentinel.detectionRules.list({ type, status })

// ── Playbooks ─────────────────────────────────────────────────────────────
await sentinel.playbooks.list()
await sentinel.playbooks.run(id)        // executes + increments executionCount

// ── Dashboard ─────────────────────────────────────────────────────────────
await sentinel.dashboard.stats()        // global counts
await sentinel.dashboard.reportSummary() // 30-day trends

// ── Billing ───────────────────────────────────────────────────────────────
await sentinel.billing.plans()
await sentinel.billing.subscription()
await sentinel.billing.createOrder('pro')   // → { orderId, approvalUrl }
await sentinel.billing.captureOrder(orderId)
await sentinel.billing.cancel()
await sentinel.billing.reactivate()
await sentinel.billing.invoices()
```

### Analyst

```typescript
// ── Summary ───────────────────────────────────────────────────────────────
await sentinel.analyst.summary()         // all secondary stats in one call

// ── Vulnerabilities ───────────────────────────────────────────────────────
await sentinel.vulnerabilities.listWithStats()  // data + aggregate stats
await sentinel.vulnerabilities.markPatching(id)
await sentinel.vulnerabilities.markPatched(id)

// ── Phishing ──────────────────────────────────────────────────────────────
await sentinel.phishing.list()
await sentinel.phishing.create({ reporter, subject })
await sentinel.phishing.quarantine(id)
await sentinel.phishing.block(id)
await sentinel.phishing.resolve(id)

// ── Malware ───────────────────────────────────────────────────────────────
await sentinel.malware.list()
await sentinel.malware.submit({ hash, fileName, type })
await sentinel.malware.startAnalysis(id)
await sentinel.malware.markComplete(id)

// ── Certificates ──────────────────────────────────────────────────────────
await sentinel.certificates.list()
await sentinel.certificates.create({ name, expiresAt })
await sentinel.certificates.delete(id)

// ── Firewall Changes ──────────────────────────────────────────────────────
await sentinel.firewallChanges.list({ status: 'pending' })
await sentinel.firewallChanges.create({ requester, rule })
await sentinel.firewallChanges.approve(id)
await sentinel.firewallChanges.reject(id)

// ── Threat Hunts ──────────────────────────────────────────────────────────
await sentinel.threatHunts.list()
await sentinel.threatHunts.create({ name, ttps, query })
await sentinel.threatHunts.start(id)
await sentinel.threatHunts.finish(id, findings)

// ── User Activity (UEBA) ──────────────────────────────────────────────────
await sentinel.userActivity.list({ risk: 'high' })
await sentinel.userActivity.review(id)

// ── Compliance ────────────────────────────────────────────────────────────
await sentinel.compliance.stats()  // ISO 27001, NIST, GDPR, SOC2, PCI-DSS
```

---

## Token Management

```typescript
// Option A: Login (token stored automatically)
await sentinel.auth.login(email, password);

// Option B: Pre-load from storage
const sentinel = new SentinelClient({ token: localStorage.getItem('token') });

// Option C: Set after construction
sentinel.setToken(storedToken);

// Chaining
const sentinel = new SentinelClient({ baseUrl })
  .setToken(process.env.SENTINEL_TOKEN!);

// Get current token (e.g. to persist after login)
const { token } = await sentinel.auth.login(email, password);
localStorage.setItem('token', token);

// Clear on logout
sentinel.clearToken();
```

---

## TypeScript

All types are exported from the root package:

```typescript
import type {
  Alert, AlertStatus, AlertStats,
  Incident, IncidentStatus,
  Organization, User, System,
  Vulnerability, ThreatHunt,
  DashboardStats, AnalystSummary,
  PagedResponse, ApiResponse,
  SentinelConfig,
} from 'sentinel-watch-sdk';
```

---

## Publishing

```bash
npm run build          # compile TypeScript → dist/
npm publish            # publish sentinel-watch-sdk@2.1.0 to npmjs.com
```