TypeScript SDK
A fully typed client for the CrawlKit API. Build data pipelines, deploy spiders, enrich datasets, and ship code that Google actually indexes — all with complete type safety.
Coming soon:
npm install @crawlkit/sdk. The examples below show the planned API surface using a typed wrapper around fetch. You can use these patterns today with zero dependencies.
Installation
npm install @crawlkit/sdk
Quick Start
import { CrawlKit } from "@crawlkit/sdk";
const ck = new CrawlKit({ apiKey: "ck_live_YOUR_API_KEY" });
// Create a dataset
const dataset = await ck.datasets.create({
name: "competitor_data",
columns: [
{ name: "domain", data_type: "text" },
{ name: "traffic", data_type: "integer" },
{ name: "tech_stack", data_type: "jsonb" },
],
});
console.log(`Dataset created: ${dataset.id}`);
Type Definitions
Core types used throughout the SDK. These give you full IntelliSense and compile-time validation.
interface CrawlKitConfig {
apiKey: string;
baseUrl?: string; // defaults to "https://api.crawlkit.app/api/v1"
timeout?: number; // defaults to 30000ms
retries?: number; // defaults to 3
}
interface Dataset {
id: string;
name: string;
description?: string;
columns: Column[];
row_count: number;
created_at: string;
}
interface Column {
name: string;
data_type: "text" | "integer" | "float" | "boolean" | "jsonb" | "timestamp";
}
interface DatasetRow {
id: string;
data: Record<string, unknown>;
created_at: string;
}
interface SpiderConfig {
name: string;
scope: { start_urls: string[] };
definition: {
handlers: Array<{
url_pattern: string;
fields: Array<{
name: string;
selector: string;
type: "text" | "html" | "attribute";
attribute?: string;
}>;
}>;
navigation?: { follow_links?: string; max_depth?: number };
};
dataset_id?: string;
}
interface Spider {
id: string;
name: string;
status: "ready" | "running" | "paused" | "completed" | "failed";
scope: { start_urls: string[] };
dataset_id?: string;
created_at: string;
}
interface EnrichmentJob {
job_id: string;
status: "pending" | "running" | "completed" | "failed";
source: string;
dataset_id: string;
total_rows: number;
processed_rows: number;
created_at: string;
}
interface Pipeline {
id: string;
name: string;
steps: PipelineStep[];
status: "draft" | "active" | "paused";
created_at: string;
}
interface PipelineStep {
type: "crawl" | "extract" | "enrich" | "filter" | "export";
config: Record<string, unknown>;
}
interface SeoCheckResult {
url: string;
score: number;
issues: SeoIssue[];
metadata: { title?: string; word_count: number; load_time_ms: number };
remediations?: Remediation[];
}
interface SeoIssue {
category: string;
severity: "critical" | "high" | "medium" | "low" | "info";
message: string;
affected_urls: string[];
}
interface CrawlKitError {
error: string;
message: string;
status: number;
retryable: boolean;
retry_after?: number;
}
Client Implementation
A typed wrapper around fetch you can use today. Drop this into your project and import it anywhere.
class CrawlKitClient {
private baseUrl: string;
private apiKey: string;
private timeout: number;
constructor(config: CrawlKitConfig) {
this.apiKey = config.apiKey;
this.baseUrl = config.baseUrl ?? "https://api.crawlkit.app/api/v1";
this.timeout = config.timeout ?? 30000;
}
private async request<T>(
method: string,
path: string,
body?: unknown,
params?: Record<string, string>
): Promise<T> {
const url = new URL(`${this.baseUrl}${path}`);
if (params) {
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
}
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), this.timeout);
try {
const res = await fetch(url.toString(), {
method,
headers: {
"Authorization": `Bearer ${this.apiKey}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
if (!res.ok) {
const err: CrawlKitError = await res.json();
throw new CrawlKitApiError(err);
}
return (await res.json()) as T;
} finally {
clearTimeout(timer);
}
}
// ── Data Platform ──
async createDataset(config: {
name: string;
description?: string;
columns: Column[];
}): Promise<Dataset> {
return this.request("POST", "/datasets", config);
}
async listDatasets(): Promise<Dataset[]> {
return this.request("GET", "/datasets");
}
async addRows(datasetId: string, rows: Record<string, unknown>[]): Promise<{
inserted: number;
}> {
return this.request("POST", `/datasets/${datasetId}/rows`, {
format: "json",
data: rows,
});
}
async createSpider(config: SpiderConfig): Promise<Spider> {
return this.request("POST", "/spiders", config);
}
async enrich(config: {
dataset_id: string;
source_name: string;
input_mapping: Record<string, string>;
output_mapping: Record<string, string>;
}): Promise<EnrichmentJob> {
return this.request("POST", "/enrichment/enrich", config);
}
async createPipeline(config: {
name: string;
steps: PipelineStep[];
}): Promise<Pipeline> {
return this.request("POST", "/pipelines", config);
}
async runPipeline(pipelineId: string): Promise<{
run_id: string;
status: string;
}> {
return this.request("POST", `/pipelines/${pipelineId}/run`);
}
// ── SEO Analysis ──
async checkUrl(url: string, options?: {
js_rendering?: boolean;
}): Promise<SeoCheckResult> {
return this.request("POST", "/check-url", { url, ...options });
}
async auditSite(siteUrl: string, options?: {
max_pages?: number;
js_rendering?: boolean;
}): Promise<{ audit_id: string; score: number; issues: SeoIssue[] }> {
return this.request("POST", "/audit/run", { site_url: siteUrl, ...options });
}
// ── Crawl Jobs ──
async startCrawl(config: {
site_url: string;
spider_id: string;
budget?: { max_pages?: number };
}): Promise<{ job_id: string; status: string }> {
return this.request("POST", "/crawl/jobs", config);
}
async getCrawlStatus(jobId: string): Promise<{
job_id: string;
status: string;
pages_crawled: number;
}> {
return this.request("GET", `/crawl/jobs/${jobId}`);
}
}
class CrawlKitApiError extends Error {
status: number;
retryable: boolean;
retry_after?: number;
constructor(err: CrawlKitError) {
super(err.message);
this.name = "CrawlKitApiError";
this.status = err.status;
this.retryable = err.retryable;
this.retry_after = err.retry_after;
}
}
Examples
Create a Dataset and Add Rows
const client = new CrawlKitClient({ apiKey: "ck_live_YOUR_API_KEY" });
// Create a dataset for lead data
const dataset = await client.createDataset({
name: "saas_companies",
description: "SaaS companies with tech stacks and traffic",
columns: [
{ name: "domain", data_type: "text" },
{ name: "company_name", data_type: "text" },
{ name: "monthly_traffic", data_type: "integer" },
{ name: "tech_stack", data_type: "jsonb" },
{ name: "employee_count", data_type: "integer" },
{ name: "last_updated", data_type: "timestamp" },
],
});
// Add rows to the dataset
const result = await client.addRows(dataset.id, [
{
domain: "stripe.com",
company_name: "Stripe",
monthly_traffic: 45000000,
tech_stack: { frameworks: ["React", "Ruby on Rails"], cdn: "Cloudflare" },
employee_count: 8000,
},
{
domain: "linear.app",
company_name: "Linear",
monthly_traffic: 2500000,
tech_stack: { frameworks: ["React", "Next.js"], cdn: "Vercel" },
employee_count: 120,
},
]);
console.log(`Inserted ${result.inserted} rows`);
Deploy a Spider
// Create a spider to extract job listings
const spider = await client.createSpider({
name: "job_board_scraper",
scope: {
start_urls: ["https://www.ycombinator.com/jobs"],
},
definition: {
handlers: [
{
url_pattern: "/listings/*",
fields: [
{ name: "title", selector: "h1.job-title", type: "text" },
{ name: "company", selector: ".company-name", type: "text" },
{ name: "location", selector: ".job-location", type: "text" },
{ name: "salary", selector: ".salary-range", type: "text" },
{ name: "description", selector: ".job-description", type: "html" },
{ name: "apply_url", selector: "a.apply-btn", type: "attribute", attribute: "href" },
],
},
],
navigation: {
follow_links: ".pagination a.next",
max_depth: 5,
},
},
dataset_id: dataset.id,
});
console.log(`Spider ${spider.id} is ${spider.status}`);
Run Enrichment
// Enrich dataset rows with tech stack detection
const job = await client.enrich({
dataset_id: dataset.id,
source_name: "tech_stack_detector",
input_mapping: { url: "domain" },
output_mapping: { technologies: "tech_stack" },
});
console.log(`Enrichment job ${job.job_id}: ${job.status}`);
console.log(`Processing ${job.total_rows} rows`);
Build and Run a Pipeline
// Create a multi-step pipeline: crawl → extract → enrich → export
const pipeline = await client.createPipeline({
name: "competitor_intelligence",
steps: [
{
type: "crawl",
config: {
spider_id: spider.id,
max_pages: 500,
},
},
{
type: "enrich",
config: {
source_name: "company_data",
input_mapping: { url: "domain" },
output_mapping: { revenue: "estimated_revenue", employees: "employee_count" },
},
},
{
type: "filter",
config: {
conditions: [{ field: "employee_count", operator: "gte", value: 50 }],
},
},
{
type: "export",
config: {
format: "csv",
destination: "webhook",
webhook_url: "https://your-app.com/api/pipeline-results",
},
},
],
});
// Run the pipeline
const run = await client.runPipeline(pipeline.id);
console.log(`Pipeline run ${run.run_id}: ${run.status}`);
Check a URL for SEO Issues
// Ship code that Google actually indexes
const result = await client.checkUrl("https://crawlkit.app", {
js_rendering: false,
});
console.log(`SEO Score: ${result.score}/100`);
console.log(`Issues: ${result.issues.length}`);
// Filter high-severity issues
const critical = result.issues.filter(
(i) => i.severity === "critical" || i.severity === "high"
);
for (const issue of critical) {
console.log(`[${issue.severity.toUpperCase()}] ${issue.message}`);
}
Error Handling
try {
const result = await client.checkUrl("https://crawlkit.app");
console.log(`Score: ${result.score}`);
} catch (err) {
if (err instanceof CrawlKitApiError) {
console.error(`API Error ${err.status}: ${err.message}`);
if (err.retryable && err.retry_after) {
console.log(`Retrying in ${err.retry_after}s...`);
await new Promise((r) => setTimeout(r, err.retry_after! * 1000));
// retry the request
}
if (err.status === 401) {
console.error("Invalid API key. Check your key at https://crawlkit.app");
}
if (err.status === 429) {
console.error("Rate limited. Upgrade at https://crawlkit.app/pricing");
}
} else {
throw err; // network error or timeout
}
}
Retry with Exponential Backoff
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
if (
err instanceof CrawlKitApiError &&
err.retryable &&
attempt < maxRetries
) {
const delay = err.retry_after
? err.retry_after * 1000
: baseDelay * Math.pow(2, attempt);
await new Promise((r) => setTimeout(r, delay));
} else {
throw err;
}
}
}
throw new Error("Unreachable");
}
// Usage
const result = await withRetry(() => client.checkUrl("https://crawlkit.app"));