npm package: @gtm-router/sdk
Description: Official TypeScript SDK for the GTM Router API — GTM data enrichment
Author: GTM Router
Latest version: 1.1.0

# @gtm-router/sdk

Official TypeScript SDK for the [GTM Router API](https://gtmrouter.io) — one API for all your Go-to-Market data enrichment needs.

- **Zero runtime dependencies** — uses Node.js built-in `fetch`
- **Full TypeScript support** — every input and output is typed
- **ESM + CJS** — works everywhere
- **Automatic retries** — exponential backoff on 5xx errors
- **Auto-polling** — async operations (bond, waterfall, bulk) poll until completion

## Installation

```bash
npm install @gtm-router/sdk
# or
pnpm add @gtm-router/sdk
# or
yarn add @gtm-router/sdk
```

Requires Node.js 18+.

## Quick Start

```typescript
import { GTMRouter } from "@gtm-router/sdk";

const gtmRouter = new GTMRouter({ apiKey: "sk_live_xxxxx" });

// Find an email
const result = await gtm-router.email.find({
  firstName: "John",
  lastName: "Doe",
  domain: "acme.com",
});

console.log(result.data?.email); // "john.doe@acme.com"
```

## Configuration

```typescript
const gtmRouter = new GTMRouter({
  apiKey: "sk_live_xxxxx",       // Required
  baseUrl: "https://gtmrouter.io/api/v1", // Default
  timeout: 30000,                // Request timeout in ms (default: 30s)
  maxRetries: 2,                 // Retry count for 5xx errors (default: 2)
  retryDelay: 1000,              // Initial retry delay in ms (default: 1s)
  pollInterval: 3000,            // Polling interval for async ops (default: 3s)
  pollTimeout: 300000,           // Polling timeout for async ops (default: 5min)
});
```

## API Reference

### Email

#### `gtm-router.email.find(params)`

Find an email address from a person's name and company domain.

```typescript
const result = await gtm-router.email.find({
  firstName: "John",
  lastName: "Doe",
  domain: "acme.com",
  mode: "fastest",               // "fastest" | "cheapest" | "waterfall"
  providers: ["leadmagic"],      // Optional: specific providers
  verify: true,                  // Optional: verify the found email
});

// result.status: "found" | "not_found" | "error"
// result.data?.email: "john.doe@acme.com"
// result.data?.confidence: 0.96
// result.cost: 0.01
```

#### `gtm-router.email.verify(params)`

Verify the deliverability of an email address.

```typescript
const result = await gtm-router.email.verify({
  email: "john@acme.com",
  mode: "cheapest",
});

// result.data?.result: "deliverable" | "undeliverable" | "risky" | "unknown"
// result.data?.isCatchAll: false
// result.data?.isDisposable: false
// result.data?.confidence: 0.99
```

### Phone

#### `gtm-router.phone.find(params)`

Find a phone number from an email, LinkedIn URL, or name + domain.

```typescript
// By email
const result = await gtm-router.phone.find({ email: "john@acme.com" });

// By LinkedIn
const result = await gtm-router.phone.find({ linkedinUrl: "linkedin.com/in/johndoe" });

// By name + domain
const result = await gtm-router.phone.find({
  firstName: "John",
  lastName: "Doe",
  domain: "acme.com",
});

// result.data?.phone: "+1-555-0123"
// result.data?.type: "mobile" | "landline" | "voip" | "unknown"
```

### Bond (AI Research)

#### `gtm-router.bond.create(params, options?)`

Create an AI research task. Automatically polls until completion.

```typescript
const result = await gtm-router.bond.create(
  {
    query: "Find top 5 engineering leaders at Intercom",
    model: "anthropic/claude-sonnet-4",      // Optional
    webhookUrl: "https://example.com/hook",   // Optional
  },
  {
    onProgress: (status) => {
      console.log(status.progress); // "Enriching profiles (3/5)..."
    },
  },
);

// result.data?.results: [{ fullName: "Jane Smith", title: "VP Engineering" }]
// result.data?.stepsExecuted: ["Searched PDL", "Found 5 profiles", ...]
```

#### `gtm-router.bond.get(id)`

Get the status of a bond task by ID.

```typescript
const task = await gtm-router.bond.get("bond_pqr678");
```

### Waterfall

#### `gtm-router.waterfall.create(params, options?)`

Run a multi-step enrichment waterfall. Automatically polls until completion.

```typescript
const result = await gtm-router.waterfall.create({
  input: { firstName: "John", lastName: "Doe", domain: "acme.com" },
  steps: [
    { action: "email.find", options: { mode: "waterfall", providers: ["leadmagic", "prospeo"], verify: true } },
    { action: "phone.find", options: { mode: "cheapest" } },
  ],
  maxCost: 0.50,
});

// result.steps: [{ step: 1, action: "email.find", status: "found", ... }]
// result.mergedProfile: { email: "john@acme.com", phone: "+1-555-0123" }
```

### Bulk Operations

#### `gtm-router.bulk.emailFind(params, options?)`

Bulk email finding. Automatically polls until completion.

```typescript
const result = await gtm-router.bulk.emailFind({
  records: [
    { firstName: "John", lastName: "Doe", domain: "acme.com" },
    { firstName: "Jane", lastName: "Smith", domain: "example.com" },
  ],
  mode: "cheapest",
});
```

#### `gtm-router.bulk.emailVerify(params, options?)`

```typescript
const result = await gtm-router.bulk.emailVerify({
  records: [{ email: "john@acme.com" }, { email: "jane@example.com" }],
});
```

#### `gtm-router.bulk.phoneFind(params, options?)`

```typescript
const result = await gtm-router.bulk.phoneFind({
  records: [{ email: "john@acme.com" }, { linkedinUrl: "linkedin.com/in/jane" }],
});
```

#### `gtm-router.bulk.get(id)`

Get the status of a bulk job by ID.

```typescript
const job = await gtm-router.bulk.get("bulk_vwx234");
// job.status: "queued" | "processing" | "completed" | "failed"
// job.totalRecords: 100
// job.completedRecords: 75
```

### Account

#### `gtm-router.account.get()`

```typescript
const account = await gtm-router.account.get();
// account.balance: 94.32
// account.rateLimits.requestsPerMinute: 60
// account.usageThisMonth.totalCost: 5.68
```

### API Keys

#### `gtm-router.keys.create(params)`

```typescript
const key = await gtm-router.keys.create({ name: "Production", expiresAt: "2027-01-01" });
// key.key: "sk_live_xxxxx" (only returned on creation)
```

#### `gtm-router.keys.list()`

```typescript
const keys = await gtm-router.keys.list();
```

#### `gtm-router.keys.revoke(id)`

```typescript
await gtm-router.keys.revoke("key_abc123");
```

### Providers

#### `gtm-router.providers.list()`

```typescript
const providers = await gtm-router.providers.list();
// [{ id: "leadmagic", displayName: "LeadMagic", healthStatus: "healthy", ... }]
```

### Models

#### `gtm-router.models.list()`

```typescript
const models = await gtm-router.models.list();
// [{ id: "anthropic/claude-sonnet-4", name: "Claude Sonnet 4", ... }]
```

## Error Handling

The SDK throws typed errors for different failure modes:

```typescript
import {
  GTMRouterError,
  InvalidApiKeyError,
  InsufficientBalanceError,
  RateLimitError,
  ValidationError,
  ProviderError,
  TimeoutError,
} from "@gtm-router/sdk";

try {
  await gtm-router.email.find({ firstName: "John", lastName: "Doe", domain: "acme.com" });
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter}s`);
  } else if (error instanceof InsufficientBalanceError) {
    console.log("Please top up your balance");
  } else if (error instanceof InvalidApiKeyError) {
    console.log("Check your API key");
  } else if (error instanceof ValidationError) {
    console.log(`Invalid request: ${error.message}`);
  } else if (error instanceof ProviderError) {
    console.log("Data provider issue, try again later");
  } else if (error instanceof TimeoutError) {
    console.log("Request timed out");
  } else if (error instanceof GTMRouterError) {
    console.log(`${error.code}: ${error.message} (request: ${error.requestId})`);
  }
}
```

All errors extend `GTMRouterError` which extends `Error`, so you can also catch broadly:

```typescript
try {
  await gtm-router.email.find(params);
} catch (error) {
  if (error instanceof GTMRouterError) {
    console.log(error.code, error.statusCode, error.message);
  }
}
```

## Retry Behavior

- **5xx errors**: Automatically retried with exponential backoff (up to `maxRetries`)
- **4xx errors**: Never retried (thrown immediately)
- **Network errors**: Automatically retried
- **Timeouts**: Thrown as `TimeoutError`

## License

MIT