> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oathnet.org/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript SDK

> Official JavaScript/TypeScript client library for OathNet API

## Installation

```bash theme={null}
npm install oathnet

# Or with yarn
yarn add oathnet

# Or with pnpm
pnpm add oathnet
```

**Requirements:** Node.js 16+. Download helpers use Node `Buffer`, `fs`, and `path`.

**Source:** [oathnet/oathnet-js](https://github.com/oathnet/oathnet-js)

## Quick Start

```typescript theme={null}
import { OathNetClient } from 'oathnet';

// Initialize client with API key (required)
const client = new OathNetClient('your-api-key');

// Search breaches
const result = await client.search.breach("user@example.com");
console.log(`Found ${result.data?.results_found} results`);

for (const record of result.data?.results || []) {
  console.log(record.email, "password_present=", Boolean(record.password));
}
```

## TypeScript Support

Full TypeScript support with comprehensive type definitions:

```typescript theme={null}
import { OathNetClient } from 'oathnet';
import type { BreachRecord, StealerItem, VictimProfile } from 'oathnet';

const client = new OathNetClient('your-api-key');

// Fully typed responses
const result = await client.search.breach("user@example.com");
result.data?.results.forEach((record: BreachRecord) => {
  console.log(record.email);
  console.log(Boolean(record.password));
  console.log(record.dbname);
});
```

## Authentication

```typescript theme={null}
import { OathNetClient } from 'oathnet';

// Explicit API key (required)
const client = new OathNetClient('your-api-key');

// With options
const client = new OathNetClient('your-api-key', {
  baseUrl: 'https://oathnet.org/api',
  timeout: 30000
});
```

## Search Service

### Breach Search

```typescript theme={null}
// Basic search
const result = await client.search.breach("user@example.com");

// With filters
const result = await client.search.breach("user@example.com", {
  dbnames: "linkedin_2012,adobe_2013"
});

// Access results
console.log(`Total: ${result.data?.results_found}`);
console.log(`Shown: ${result.data?.results_shown}`);

for (const record of result.data?.results || []) {
  console.log(`Email: ${record.email}`);
  console.log(`Password present: ${Boolean(record.password)}`);
  console.log(`Database: ${record.dbname}`);
}

// Pagination
if (result.data?.cursor) {
  const nextPage = await client.search.breach("user@example.com", {
    cursor: result.data.cursor
  });
}
```

### Initialize Session

Search sessions group related lookups for the same query. Pass `searchType`
when you already know what the query is; otherwise OathNet will detect it.
Reuse the returned `session.id` as `searchId` on follow-up searches.

```typescript theme={null}
// Create a search session for quota optimization.
const result = await client.search.initSession("user@example.com", {
  searchType: "email"
});

const sessionId = result.data?.session.id;
console.log(`Session ID: ${sessionId}`);
console.log(`Status: ${result.data?.session.status}`);
console.log(`Search Type: ${result.data?.session.search_type}`);
console.log(`Expires: ${result.data?.session.expires_at}`);

console.log(`Available services: ${result.data?.summary?.available_services}`);

// Reuse the session ID on current V2 search calls.
const breachResults = await client.breach.search("user@example.com", {
  searchId: sessionId,
  pageSize: 25
});
console.log(`Breach items: ${breachResults.data?.items.length ?? 0}`);
```

## AI Filters

Translate natural-language prompts into reusable V2 structured filters.

```typescript theme={null}
const aiFilter = await client.breach.createAIFilter({
  index: "breach",
  query: "US gmail users with LinkedIn records after 2020"
});

console.log(aiFilter.filter_id);
console.log(aiFilter.filter);

if (aiFilter.filter_id) {
  const context = await client.breach.getAIFilterContext(aiFilter.filter_id);
  console.log(context.index_type);
  console.log(context.query);
}
```

Use `aiFilter.filter_id` with V2 search, export, bulk-search, or scanner
`query_config` flows when you want OathNet to reuse the generated filter
context. Use `createAIFilter({ filter_id, query, index })` to refine an
existing context.

## V2 Breach Search

Use V2 breach search when you need fielded filters, structured filters, or
autocomplete-driven workflows. GET is best for simple filters that fit in query
parameters. POST is best when the filter tree comes from AI filters, saved UI
state, or a complex manual builder.

```typescript theme={null}
// Simple pivots stay readable and shareable
const result = await client.breach.search("user@example.com", {
  dbnames: ["linkedin_2012"],
  fields: ["email", "password", "dbname"],
  pageSize: 25
});

// The same method accepts structured filters
const filtered = await client.breach.search({
  filter: {
    and: [
      { field: "email_domain", operator: "eq", value: "example.com" },
      { field: "country", operator: "eq", value: "US" }
    ]
  },
  fields: ["email", "username", "dbname"]
});

// Use autocomplete before building a narrow query or scanner query_config
const values = await client.breach.autocompleteValues({
  field: "email_domain",
  q: "example",
  limit: 10,
  includeInfo: true
});
const dbnames = await client.breach.autocompleteDBNames({
  q: "link",
  limit: 10
});
const fieldCoverage = await client.breach.autocompleteFields(
  "discord_id",
  { limit: 10 }
);
```

## V2 Stealer Search

```typescript theme={null}
// Basic search
const result = await client.stealer.search("user@example.com");

// Advanced filtering
const result = await client.stealer.search("user@example.com", {
  domains: ["google.com", "facebook.com"],
  hasLogId: true,
  pageSize: 50
});

// Access items
for (const item of result.data?.items || []) {
  console.log(`URL: ${item.url}`);
  console.log(`Username: ${item.username}`);
  console.log(`Password present: ${Boolean(item.password)}`);
  console.log(`Log ID: ${item.log_id}`);
}

// Pagination
if (result.data?.next_cursor) {
  const nextPage = await client.stealer.search("user@example.com", {
    cursor: result.data.next_cursor
  });
}

// Subdomain extraction with optional live checks and session reuse
const subdomains = await client.stealer.subdomain("example.com", {
  query: "mail",
  alive: true,
  searchId: "sess_0123456789abcdef"
});
console.log(`Found ${subdomains.data?.count} subdomains`);
for (const sub of subdomains.data?.subdomains || []) {
  console.log(`  ${typeof sub === "string" ? sub : sub.subdomain}`);
}
```

## Investigation And Phonebook

Use investigation when one query should fan out across credentials, victims,
files, properties, and related credentials. Use phonebook when you want domain
host and email intelligence before deciding which pivots to run next.

```typescript theme={null}
// Structured filters use the same search method
const filtered = await client.stealer.search({
  filter: { field: "domain", operator: "eq", value: "example.com" },
  fields: ["log_id", "url_str", "username"],
  view: "enriched"
});

const investigation = await client.stealer.investigate("example.com", {
  scope: "all",
  include: ["credentials", "victims", "files"],
  pageSize: 25,
  view: "enriched"
});

const investigationFromBody = await client.stealer.investigate({
  q: "example.com",
  scope: "all",
  include: ["credentials"],
  filters: { credentials: { domain: "example.com" } }
});

for (const item of investigation.data?.sections?.credentials?.items || []) {
  console.log(item.log_id, item.username);
}

const phonebook = await client.stealer.phonebook("example.com", {
  alive: true
});
console.log(phonebook.data?.subdomains?.length, phonebook.data?.email_count);
```

## Victims

Victim search returns stealer-log profiles. Use the `log_id` from a selected
victim to inspect its file tree, fetch one raw file, or download the whole
archive. Passing the `searchId` from `initSession` keeps these follow-up
requests tied to the same search workflow.

```typescript theme={null}
// Search victims
const result = await client.victims.search("user@example.com");

for (const victim of result.data?.items || []) {
  console.log(`Log ID: ${victim.log_id}`);
  console.log(`Users: ${victim.device_users?.join(", ")}`);
  console.log(`Documents: ${victim.total_docs}`);
}

// Use the same method when the pivot comes from an AI/manual structured filter
const victims = await client.victims.search({
  filter: { field: "service", operator: "eq", value: "discord" },
  view: "enriched"
});

// Search victim properties globally, then inspect one selected log
const properties = await client.victims.searchProperties({
  q: "example.com",
  service: "discord",
  confidence: "high",
  exclude_cookie_evidence: true
});
const propertiesFromBody = await client.victims.searchProperties({
  q: "example.com",
  service: "discord",
  confidence: ["high", "medium"]
});
console.log(properties.data?.items?.length, propertiesFromBody.data?.items?.length);

const details = await client.victims.getProperties("log_id_here", {
  service: "discord"
});
console.log(details.data?.items?.length);

const summary = await client.victims.getSummary("log_id_here");
const cookies = await client.victims.getCookies("log_id_here", {
  domain: "example.com",
  status: "active"
});

// Raw cookie-domain inspection returns copyable text
const cookieText = await client.victims.inspectCookieDomain(
  "log_id_here",
  "example.com"
);
console.log(`Cookie export characters: ${cookieText.length}`);

// Get the manifest (file tree) for the selected log
const searchId = "sess_0123456789abcdef";
const logId = "log_id_here";
const manifest = await client.victims.getManifest(logId, { searchId });
console.log(`Root: ${manifest.victim_tree.name}`);

for (const child of manifest.victim_tree.children?.slice(0, 10) || []) {
  console.log(`  ${child.name} (${child.type})`);
}

// Get one raw file from the manifest as a Buffer
const file = await client.victims.getFile(
  logId,
  "Browser Passwords/file 1.txt",
  { searchId }
);
console.log(`Downloaded file bytes: ${file.byteLength}`);

// Download the full victim archive as a ZIP
const archivePath = await client.victims.downloadArchive(
  logId,
  "./archive.zip",
  { searchId }
);
```

## File Search

File search runs an asynchronous scan across selected stealer-log files. Use
metadata search first when you only need file names and IDs. Create a file-search
job when you need to scan file contents with a literal, regex, or wildcard
expression.

```typescript theme={null}
// Search victim file metadata before fetching any file bytes
const files = await client.fileSearch.searchMetadata({
  q: "cookies",
  kind: "cookies",
  pageSize: 25
});
for (const file of files.data?.items || []) {
  console.log(file.log_id, file.file_id, file.path);
}

// Create a file search job
const result = await client.fileSearch.create("password", {
  logIds: ["log_id_1", "log_id_2"],
  searchMode: "literal"  // literal, regex, or wildcard
});
const jobId = result.data?.job_id;
console.log(`Job ID: ${jobId}`);

// Check job status
const status = await client.fileSearch.getStatus(jobId);
console.log(`Status: ${status.data?.status}`);
if (status.data?.summary) {
  console.log(`Files scanned: ${status.data.summary.files_scanned}`);
  console.log(`Matches: ${status.data.summary.matches}`);
}

// Wait for completion and get results
const searchResult = await client.fileSearch.search("password", {
  logIds: ["log_id_1", "log_id_2"],
  searchMode: "literal"
}, 300000);  // timeout in ms

for (const match of searchResult.data?.matches || []) {
  console.log(`File: ${match.file_name}`);
  console.log(`Log ID: ${match.log_id}`);
  console.log(`Match: ${match.match_text}`);
}
```

## Exports

Exports are asynchronous jobs for large result sets. Create a job with the
service and structured `query_config` you want, poll the job status, then
download the completed file. Use `docs` for credential-style stealer exports,
`victims` for victim/profile exports, and `breach` for breach-record exports.

```typescript theme={null}
// List recent export jobs before creating a duplicate
const exportJobs = await client.exports.listExportsV2({ page: 1, pageSize: 20 });
for (const job of exportJobs.results || []) {
  console.log(job.job_id, job.status);
}

// Create an export job from a structured breach query
const result = await client.exports.createExportV2("breach", {
  service: "breach",
  limit: 100,
  format: "jsonl", // json, jsonl, csv, txt, or html
  fields: ["email", "password", "dbname"],
  query_config: {
    filter: {
      field: "email_domain",
      operator: "eq",
      value: "example.com"
    }
  }
});
const jobId = result.data?.job_id;
console.log(`Export Job ID: ${jobId}`);

// Check export status
const status = await client.exports.getExportV2(jobId);
console.log(`Status: ${status.data?.status}`);
if (status.data?.progress) {
  console.log(`Progress: ${status.data.progress.percent}%`);
}

// Wait for completion
const completed = await client.exports.waitForCompletion(jobId, 600000);
if (completed.data?.status === "completed") {
  // Download the export file
  await client.exports.downloadExportV2(jobId, "./export.jsonl");
}
```

## Bulk Search

Bulk search is for many input terms or a saved structured filter that should run
as one background job. Create the job, poll it, and download the result file
when it completes.

```typescript theme={null}
const job = await client.bulkSearch.createBulkSearchV2({
  service: "breach",
  terms: ["alice@example.com", "bob@example.com"],
  format: "jsonl",
  query_config: {
    filter: {
      field: "email_domain",
      operator: "eq",
      value: "example.com"
    }
  },
  fields: ["email", "password", "dbname"]
});

const jobId = job.data?.job_id;
if (jobId) {
  const status = await client.bulkSearch.getBulkSearchV2(jobId);

  const jobs = await client.bulkSearch.listBulkSearchV2({
    page: 1,
    pageSize: 20
  });
  for (const item of jobs.results || []) {
    console.log(item.job_id, item.status);
  }

  if (status.status === "completed") {
    await client.bulkSearch.downloadBulkSearchV2(
      jobId,
      "./bulk-results.jsonl"
    );
  }
}
```

## Scanners

Scanners monitor newly indexed breach or stealer data and send notifications.
Use regular search endpoints for historical investigation; scanners only watch
new data after their baseline.

```typescript theme={null}
// Check scanner quota before creating a new monitor
const quota = await client.scanners.getQuota();
console.log(`Remaining scanners: ${quota.remaining}`);

const queryConfig = {
  filter: {
    and: [
      { field: "email_domain", operator: "eq", value: "example.com" },
      { field: "dbname", operator: "in", value: ["twitter.com", "linkedin.com"] }
    ]
  }
};

// Validate webhook or Discord delivery before saving the scanner.
// A 200 response can still have success=false when the remote endpoint rejects
// the test delivery, so inspect the returned object.
const test = await client.scanners.testDelivery({
  scanner_type: "breach",
  query_config: queryConfig,
  notification_type: "webhook",
  webhook_url: "https://alerts.example.com/oathnet",
  webhook_security_mode: "signed_json"
});
if (!test.success) {
  console.log(`Delivery test failed: ${test.message}`);
}

const scanner = await client.scanners.create({
  name: "Example breach monitor",
  scanner_type: "breach",
  query_config: queryConfig,
  notification_type: "webhook",
  webhook_url: "https://alerts.example.com/oathnet",
  webhook_security_mode: "signed_json",
  notify_on_zero_results: false
});

const security = await client.scanners.getWebhookSecurity(scanner.uid);
console.log(security.data?.verification_method);

await client.scanners.update(scanner.uid, {
  name: "Example breach monitor v2"
});
await client.scanners.trigger(scanner.uid);

const runs = await client.scanners.listRuns(scanner.uid, { limit: 10 });
for (const run of runs) {
  console.log(`${run.uid}: ${run.status} (${run.results_count} results)`);
}

await client.scanners.pause(scanner.uid);
await client.scanners.resume(scanner.uid);
```

Use `client.scanners.delete(scannerUid)` when you want to permanently remove a
scanner and its run history.

Webhook receivers should verify `signed_json` or `signed_encrypted` deliveries
against the exact raw request body before parsing JSON. See [Scanners](/guides/scanners#verifying-webhook-signatures)
for the HMAC and encryption contract.

## OSINT Lookups

```typescript theme={null}
// IP Info
const ipInfo = await client.osint.ipInfo("8.8.8.8");
console.log(`Location: ${ipInfo.data?.city}, ${ipInfo.data?.country}`);
console.log(`ISP: ${ipInfo.data?.isp}`);
console.log(`Proxy: ${ipInfo.data?.proxy}`);

// Steam
const steam = await client.osint.steam("76561198012345678");
console.log(`Username: ${steam.data?.username}`);
console.log(`Avatar: ${steam.data?.avatar}`);

// Xbox
const xbox = await client.osint.xbox("GamerTag123");
console.log(`Username: ${xbox.data?.username}`);
console.log(`Avatar: ${xbox.data?.avatar}`);

// Discord User Info
const discord = await client.osint.discordUserinfo("123456789012345678");
console.log(`Username: ${discord.data?.username}`);
console.log(`Global Name: ${discord.data?.global_name}`);
console.log(`Created: ${discord.data?.creation_date}`);

// Discord Username History
const history = await client.osint.discordUsernameHistory("123456789012345678");
for (const entry of history.data?.history || []) {
  if (entry.name?.[0] && entry.time?.[0]) {
    console.log(`${entry.name[0]} at ${entry.time[0]}`);
  }
}

// Roblox User Info
const roblox = await client.osint.robloxUserinfo({ userId: "123456789" });
// Or by username:
const roblox2 = await client.osint.robloxUserinfo({ username: "PlayerName" });
console.log(`Username: ${roblox.data?.username}`);
console.log(`Display Name: ${roblox.data?.['Display Name']}`);

// Holehe - Email account detection
const holehe = await client.osint.holehe("user@example.com");
console.log(`Found on ${holehe.data?.domains?.length || 0} services:`);
for (const domain of holehe.data?.domains || []) {
  console.log(`  ${domain}`);
}

// GHunt - Google account lookup
const ghunt = await client.osint.ghunt("user@gmail.com");
console.log(`Status: ${ghunt.data?.status}`);
console.log(`Name: ${ghunt.data?.data?.profile?.Name}`);

// Subdomain extraction
const subdomains = await client.osint.extractSubdomain("example.com", true);
for (const sub of subdomains.data?.subdomains || []) {
  console.log(typeof sub === "string" ? sub : sub.subdomain);
}

// Minecraft username history
const mc = await client.osint.minecraftHistory("PlayerName");
for (const entry of mc.data?.history || []) {
  console.log(`${entry.username} - ${entry.changed_at}`);
}
```

## Utility Service

```typescript theme={null}
// Database name autocomplete
const result = await client.utility.dbnameAutocomplete("link");
for (const name of result || []) {
  console.log(name);  // linkedin_2012, linkedin_2021, etc.
}
```

## Error Handling

```typescript theme={null}
import {
  OathNetClient,
  OathNetError,
  AuthenticationError,
  ValidationError,
  NotFoundError,
  RateLimitError,
  QuotaExceededError,
} from 'oathnet';

try {
  const result = await client.search.breach("user@example.com");
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.log("Invalid API key");
  } else if (error instanceof QuotaExceededError) {
    console.log("Daily quota exceeded");
  } else if (error instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter}s`);
  } else if (error instanceof NotFoundError) {
    console.log("Resource not found");
  } else if (error instanceof ValidationError) {
    console.log(`Invalid input: ${error.message}`);
  } else if (error instanceof OathNetError) {
    console.log(`API error: ${error.message}`);
  } else {
    throw error;
  }
}
```

## Configuration

```typescript theme={null}
import { OathNetClient } from 'oathnet';

const client = new OathNetClient('your-api-key', {
  baseUrl: 'https://oathnet.org/api',
  timeout: 30000       // Request timeout in ms
});
```

<CardGroup cols={2}>
  <Card title="Go SDK" icon="golang" href="/sdks/go">
    Next: Go SDK
  </Card>

  <Card title="Python SDK" icon="python" href="/sdks/python">
    Previous: Python SDK
  </Card>
</CardGroup>
