Skip to content

NovadaLabs/novada-nodejs

Repository files navigation

novada

CI

Node.js (TypeScript) SDK for the Novada API — proxy management, scraping, and wallet endpoints. Zero runtime dependencies (global fetch + Node built-ins), Node.js 18+, ships ESM + CJS with full type declarations.

Install

npm install novada

Quick start

import { NovadaClient, Product } from "novada";

const client = new NovadaClient("YOUR_API_KEY"); // or omit to read NOVADA_API_KEY

const list = await client.proxy.whitelist.list({ product: Product.Residential });
console.log(`whitelist total=${list.total}`);

The constructor accepts an options object as the second argument:

const client = new NovadaClient("API_KEY", {
  baseURL: "https://api-m.novada.com",
  webUnblockerURL: "https://webunlocker.novada.com",
  scraperURL: "https://scraper.novada.com",
  timeout: 30_000, // ms, per attempt
  maxRetries: 2,
  fetch: customFetch, // optional fetch override
  userAgent: "my-app/1.0",
});

If no key is passed and NOVADA_API_KEY is also empty, the constructor throws a NovadaError.

Every network method returns a Promise<T> and accepts an optional trailing { signal } argument for cancellation (the Node counterpart of Go's context.Context):

const ac = new AbortController();
setTimeout(() => ac.abort(), 1_000);
await client.wallet.balance({ signal: ac.signal });

The three base URLs

Novada serves requests from three hosts. All are configurable and default to production:

Purpose Default Used by
General https://api-m.novada.com Every /v1/* endpoint (proxy, wallet, and the scraper area/balance/unit queries)
Web Unblocker https://webunlocker.novada.com scraper.unblocker.scrape, or scraper.do with Target.WebUnblocker
Scraper API https://scraper.novada.com scraper.do / scraper.api.* with Target.ScraperAPI

Only the scrape POST /request calls go to the Web Unblocker / Scraper API hosts; everything else (/v1/*) uses the general host. The Bearer API key is injected on every request. Retries apply only to network errors and HTTP 429/5xx — never to a non-zero business code.

Proxy

import { NovadaClient, Product } from "novada";
const client = new NovadaClient();

// Sub-accounts
await client.proxy.account.create({
  product: Product.Residential, account: "account11", password: "pass11", status: 1,
});
await client.proxy.account.list({ product: Product.Residential });

// Whitelist
await client.proxy.whitelist.add({ product: Product.Residential, ip: "10.10.10.1", remark: "test" });
await client.proxy.whitelist.list({ product: Product.Residential });

// Residential areas & traffic
await client.proxy.residential.countries();
await client.proxy.residential.balance();
await client.proxy.residential.consumeLog({ start: "2025-01-01 00:00:00", end: "2025-01-31 23:59:59" });

// Static ISP / dedicated datacenter
await client.proxy.staticISP.open({ ipType: "normal", region: "hk:1|us-va:2", duration: "week", num: 3 });
await client.proxy.dedicatedDC.list({});

Sub-services: account, whitelist, residential, mobile, rotatingISP, rotatingDC, staticISP, dedicatedDC, unlimited, prohibitDomain. Required parameters are validated client-side and reported as a ValidationError (listing every missing field) before any request is sent. Optional fields that are undefined, "" or 0 are omitted from the request.

Scraper

import { NovadaClient, Target } from "novada";
const client = new NovadaClient();

// Strongly typed (auto-selects the Scraper API host)
const yt = await client.scraper.api.youtube.videoPost({
  url: "https://www.youtube.com/watch?v=HAwTwmzgNc4",
});
console.log(yt.raw); // raw scrape result (JSON/CSV/XLSX, depending on scraper)

// Google Search — typed params, structured result (data.json is the raw result, left as unknown)
const gs = await client.scraper.api.google.search({ query: "apple", country: "us" });
console.log(gs.code, gs.cost_time, gs.data.task_id);

// Generic driver — any scraper_id, choose the host explicitly
const res = await client.scraper.do({
  target: Target.ScraperAPI, // or Target.WebUnblocker
  scraperName: "youtube.com",
  scraperId: "youtube_video-post_explore",
  params: [{ url: "https://www.youtube.com/watch?v=HAwTwmzgNc4" }],
  returnErrors: true,
});
console.log(res.raw);

// Web Unblocker — typed scrape; returns a structured result, not raw text
const unb = await client.scraper.unblocker.scrape({
  targetUrl: "https://www.google.com", // required; responseFormat defaults to "html"
  country: "us",
});
console.log(unb.code, unb.html.length, unb.use_balance);

// Query endpoints on the general host
await client.scraper.universal.balance();   // /v1/capture/get_balance
await client.scraper.universal.unit();       // /v1/capture/unit
await client.scraper.unblocker.countries();  // /v1/proxy/unblocker_area
await client.scraper.browser.countries();    // /v1/proxy/browser_area

scraper.do JSON-stringifies params into the scraper_params form field, URL-encodes the body, and routes to the host selected by target. Scrape responses are returned raw because their format varies by scraper. scraper.api.google.search and scraper.unblocker.scrape are exceptions: they send flat form fields and decode the JSON envelope into a structured result.

Wallet

await client.wallet.balance();
await client.wallet.usageRecord({ page: 1, limit: 20 }); // defaults to 1 / 10

Error handling

Management endpoints return a uniform envelope { code, data, msg, timestamp }; only code === 0 is success. A non-zero code or a non-2xx HTTP status becomes an APIError (with AuthError / RateLimitError subclasses for the common HTTP failures).

import { isAuthError, isRateLimited, codeOf } from "novada";

try {
  await client.proxy.whitelist.list({ product: Product.Residential });
} catch (err) {
  if (isAuthError(err)) {            // HTTP 401/403
    console.error("invalid API key");
  } else if (isRateLimited(err)) {   // HTTP 429
    console.error("rate limited");
  } else {
    const code = codeOf(err);        // business code, or undefined
    console.error(code !== undefined ? `business error code=${code}` : err);
  }
}

Error classes: NovadaError (base) → APIError (httpStatus, code) → AuthError / RateLimitError; plus ValidationError (method, fields) for client-side required-field checks.

ESM and CommonJS

The package ships both module formats with type declarations for each:

// ESM
import { NovadaClient } from "novada";
// CommonJS
const { NovadaClient } = require("novada");

Examples

Runnable examples live in examples/: proxy.ts, scraper.ts, wallet.ts. Set NOVADA_API_KEY and run e.g. npx tsx examples/proxy.ts.

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors