@undetecta/client
v0.2.0
Published
JavaScript/TypeScript client for the Undetecta API - Anti-detection web scraping made simple
Maintainers
Readme
@undetecta/client
JavaScript/TypeScript client for the Undetecta API - Anti-detection web scraping made simple.
Features
- Full TypeScript Support - Comprehensive types for all API responses and requests
- Web Scraping - Scrape URLs with advanced options (screenshots, branding extraction, actions)
- Web Search - Search the web with result scraping capabilities
- Automatic Retries - Built-in retry logic with exponential backoff for transient failures
- Error Handling - Custom error classes for different error types
- ESM & CJS - Support for both ES modules and CommonJS
- Tree Shakable - Minimize bundle size for browser usage
Installation
npm install @undetecta/client
# or
pnpm add @undetecta/client
# or
yarn add @undetecta/clientQuick Start
import { UndetectaClient } from '@undetecta/client';
const client = new UndetectaClient({
apiKey: process.env.UNDETECTA_API_KEY,
});
// Scrape a URL
const result = await client.scrape({
url: 'https://example.com',
formats: ['markdown'],
});
console.log(result.markdown);Configuration
const client = new UndetectaClient({
apiKey: 'your-api-key', // Required
baseUrl: 'https://api.undetecta.com', // Optional, defaults to production
timeout: 60000, // Optional, default 60 seconds (ms)
maxRetries: 3, // Optional, default 3 retries
userAgent: 'my-app/1.0', // Optional, custom user agent
});API Reference
Scraping
scrape(options)
Scrape a URL and return the results.
const result = await client.scrape({
url: 'https://example.com', // Required
formats: ['markdown', 'screenshot'], // Output formats
waitForSelector: '.main-content', // Wait for CSS selector
screenshotOptions: {
fullPage: true,
format: 'png',
},
});Scrape Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| url | string | Required | URL to scrape |
| formats | ScrapeFormat[] | ['markdown'] | Output formats: html, rawHtml, markdown, links, screenshot, branding |
| headless | boolean | true | Run browser in headless mode |
| proxy | string | - | Proxy URL to use |
| waitForSelector | string | - | CSS selector to wait for |
| waitFor | number | - | Time to wait in ms |
| waitUntil | 'load' \| 'domcontentloaded' \| 'networkidle' | - | Wait until page load event |
| timeout | number | 30000 | Request timeout in ms |
| mobile | boolean | - | Use mobile viewport |
| actions | Action[] | - | Browser actions to perform |
Browser Actions:
const result = await client.scrape({
url: 'https://example.com',
actions: [
{ type: 'click', selector: '.cookie-accept' },
{ type: 'fill', selector: 'input[name="email"]', value: '[email protected]' },
{ type: 'wait', options: { duration: 1000 } },
{ type: 'scroll', options: { direction: 'down', amount: 500 } },
],
});Response:
interface ScrapeJobResponse {
id: string;
status: 'pending' | 'running' | 'completed' | 'failed' | 'stopped';
createdAt: string;
completedAt?: string;
metadata: ScrapeMetadata | null;
html?: string;
rawHtml?: string;
markdown?: string;
links?: string[];
screenshot?: string; // base64 encoded image
branding?: BrandingProfile;
error?: ScrapeJobError;
}Search
search(options)
Perform a web search and return the results.
const result = await client.search({
query: 'web scraping tools', // Required
limit: 10, // Number of results
scrapeOptions: {
formats: ['markdown'], // Scrape each result
},
});
console.log(result.web?.[0].title);
console.log(result.web?.[0].markdown); // Scraped contentSearch Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| query | string | Required | Search query |
| limit | number | 10 | Number of results |
| categories | SearchCategory[] | - | github, research, pdf |
| lang | string | - | Language code |
| country | string | - | Country code |
| scrapeOptions | SearchScrapeOptions | - | Options for scraping results |
| timeout | number | - | Timeout in ms |
Health Check
health()
Check the API health status.
const health = await client.health();
console.log(health.status); // 'ok'Error Handling
The client provides custom error classes for different error types:
import {
UndetectaError,
ApiKeyError,
RateLimitError,
TimeoutError,
NetworkError,
ValidationError,
NotFoundError,
} from '@undetecta/client';
try {
const result = await client.scrape({ url: 'https://example.com' });
} catch (error) {
if (error instanceof ApiKeyError) {
console.error('Invalid API key:', error.message);
// Handle invalid API key (status code: 401)
} else if (error instanceof RateLimitError) {
console.error('Rate limit exceeded:', error.message);
// Implement backoff/retry logic (status code: 429)
} else if (error instanceof TimeoutError) {
console.error('Request timed out:', error.message);
// Retry with longer timeout
} else if (error instanceof NetworkError) {
console.error('Network error:', error.message);
// Check connection, retry
} else if (error instanceof ValidationError) {
console.error('Validation error:', error.message);
// Fix request parameters (status code: 400)
} else if (error instanceof NotFoundError) {
console.error('Resource not found:', error.message);
// Handle missing resource (status code: 404)
} else if (error instanceof UndetectaError) {
console.error(`API error: ${error.code} - ${error.message}`);
console.error(`Status: ${error.statusCode}`);
}
}Error Classes
| Error Class | Status Code | Description |
|-------------|-------------|-------------|
| ApiKeyError | 401 | Invalid or missing API key |
| ValidationError | 400 | Request validation failed |
| NotFoundError | 404 | Resource not found |
| RateLimitError | 429 | Rate limit exceeded |
| UndetectaError | Variable | Base error class (includes status code for server errors) |
| TimeoutError | - | Request timed out |
| NetworkError | - | Network connection failed |
TypeScript Usage
All types are exported for convenience:
import type {
// Client options
ClientConfig,
// Scrape types
ScrapeOptions,
ScrapeJobResponse,
ScrapeMetadata,
ScrapeFormat,
ScreenshotOptions,
Action,
// Search types
SearchOptions,
SearchJobResponse,
} from '@undetecta/client';Advanced Examples
Extract Branding
const result = await client.scrape({
url: 'https://example.com',
formats: ['branding'],
});
console.log(result.branding?.colors?.primary);
console.log(result.branding?.typography?.fontFamilies);
console.log(result.branding?.components?.buttonPrimary);Screenshot with Custom Options
const result = await client.scrape({
url: 'https://example.com',
formats: ['screenshot'],
screenshotOptions: {
fullPage: false,
format: 'png',
quality: 90,
clip: { x: 0, y: 0, width: 1920, height: 1080 },
selector: '.main-content',
viewport: { width: 1920, height: 1080 },
},
});
// Save screenshot
const buffer = Buffer.from(result.screenshot!, 'base64');
fs.writeFileSync('screenshot.png', buffer);Search with Result Scraping
const result = await client.search({
query: 'typescript web scraping',
limit: 5,
scrapeOptions: {
formats: ['markdown', 'links'],
onlyMainContent: true,
},
});
for (const item of result.web || []) {
console.log(item.title);
console.log(item.url);
if (item.markdown) {
console.log(item.markdown.substring(0, 200) + '...');
}
}Custom Error Handling with Retry
async function scrapeWithRetry(url: string, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await client.scrape({ url });
} catch (error) {
if (error instanceof RateLimitError && i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000; // Exponential backoff
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}Development
Install development dependencies:
pnpm installRun linting and formatting:
pnpm lint # Check for issues
pnpm lint:fix # Fix linting issues
pnpm format # Format codeRun type checking:
pnpm typecheckRun tests:
pnpm test # Run all tests
pnpm test:watch # Watch modeRequirements
- Node.js: >= 18.0.0
- TypeScript: >= 5.0.0 (for TypeScript users)
License
MIT © Undetecta
