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

# Node.js SDK

> Use X1-BaaS from Node.js with a ready-to-use fetch-based client.

# Node.js

X1-BaaS is a plain REST API. This guide provides a zero-dependency client using the
built-in `fetch` (Node 18+), with optional `axios` examples.

## Requirements

* Node.js **18+** (native `fetch`)

## Client wrapper

```javascript Filename baas.js theme={null}
const BASE_URL = "https://api.tazpal.com";

class BaasClient {
  constructor({ apiKey, paymentPermit, timeout = 120000 } = {}) {
    this.apiKey = apiKey;
    this.paymentPermit = paymentPermit; // base64-encoded x402 permit
    this.timeout = timeout;
  }

  headers() {
    const h = { "Content-Type": "application/json" };
    if (this.apiKey) h.Authorization = `Bearer ${this.apiKey}`;
    else if (this.paymentPermit) h["X-PAYMENT"] = this.paymentPermit;
    return h;
  }

  async request(method, path, body) {
    const resp = await fetch(`${BASE_URL}${path}`, {
      method,
      headers: this.headers(),
      body: body ? JSON.stringify(body) : undefined,
      signal: AbortSignal.timeout(this.timeout),
    });
    const json = await resp.json().catch(() => ({}));
    if (!resp.ok) throw new BaasError(resp.status, json);
    return json;
  }

  scrape(url, output = "markdown", options = {}) {
    const body = { url, output };
    if (Object.keys(options).length) body.options = options;
    return this.request("POST", "/v1/scrape", body);
  }

  extract(url, { schema, selectors, xpaths } = {}) {
    const body = { url };
    if (schema) body.schema = schema;
    if (selectors) body.selectors = selectors;
    if (xpaths) body.xpaths = xpaths;
    return this.request("POST", "/v1/extract", body);
  }

  crawl({ startUrl, mode, urls, ...rest } = {}) {
    const body = { mode, ...rest };
    if (startUrl) body.start_url = startUrl;
    if (urls) body.urls = urls;
    return this.request("POST", "/v1/crawl", body);
  }

  crawlStatus(jobId) {
    return this.request("GET", `/v1/crawl/${jobId}`);
  }

  crawlResults(jobId, limit = 100) {
    return this.request("GET", `/v1/crawl/${jobId}/results?limit=${limit}`);
  }

  async cancelCrawl(jobId) {
    await fetch(`${BASE_URL}/v1/crawl/${jobId}`, {
      method: "DELETE",
      headers: this.headers(),
    });
  }

  health() {
    return this.request("GET", "/health");
  }

  pricing() {
    return this.request("GET", "/v1/pricing");
  }
}

class BaasError extends Error {
  constructor(statusCode, body) {
    super(`HTTP ${statusCode}: ${JSON.stringify(body)}`);
    this.statusCode = statusCode;
    this.body = body;
  }
}

module.exports = { BaasClient, BaasError };
```

## Usage

### Scrape to Markdown

```javascript theme={null}
const { BaasClient, BaasError } = require("./baas");

const client = new BaasClient({ apiKey: "baas_live_YOUR_KEY" });

try {
  const result = await client.scrape("https://news.ycombinator.com");
  console.log(result.data.title);
  console.log(result.data.markdown.slice(0, 500));
} catch (err) {
  if (err instanceof BaasError) console.error(err.statusCode, err.body);
}
```

### Screenshot to file

```javascript theme={null}
import { writeFileSync } from "fs";

const result = await client.scrape("https://example.com", "screenshot", {
  full_page: true,
});
writeFileSync("page.png", Buffer.from(result.data.data, "base64"));
```

### Structured extraction

```javascript theme={null}
const result = await client.extract("https://example.com/product/123", {
  schema: {
    type: "object",
    properties: {
      title: { type: "string" },
      price: { type: "number" },
    },
  },
});
console.log(result.data, result.metadata.confidence);
```

### Crawl a batch and poll

```javascript theme={null}
const job = await client.crawl({
  mode: "batch",
  urls: ["https://a.com/1", "https://a.com/2"],
  options: { parallel: 10 },
});

let status;
do {
  await new Promise((r) => setTimeout(r, 5000));
  status = await client.crawlStatus(job.job_id);
  console.log(status.progress);
} while (!["completed", "failed", "canceled"].includes(status.status));

const results = await client.crawlResults(job.job_id);
console.log(`Got ${results.count} pages`);
```

## Using axios instead

Prefer `axios`? Swap the `request` method:

```javascript theme={null}
import axios from "axios";

async request(method, path, body) {
  try {
    const { data } = await axios({
      method,
      url: `${BASE_URL}${path}`,
      headers: this.headers(),
      data: body,
      timeout: this.timeout,
    });
    return data;
  } catch (err) {
    if (err.response) throw new BaasError(err.response.status, err.response.data);
    throw err;
  }
}
```

## Error handling

Check `err.statusCode` and `err.body.detail.error` for the machine-readable code.

```javascript theme={null}
try {
  await client.scrape("https://example.com", "nope");
} catch (err) {
  if (err instanceof BaasError) {
    console.log(err.body.detail.error);   // "invalid_output"
    console.log(err.body.detail.message);
  }
}
```
