Official Node.js / TypeScript SDK for the Chocodata web scraping API.
Typed methods for the endpoints most people start with, a generic client for the rest of the catalog, retry with backoff, and a structured error type.
npm install chocodataRequires Node 18+ (uses the global fetch). No runtime dependencies.
import { Chocodata } from 'chocodata';
const chocodata = new Chocodata('asa_live_YOUR_KEY');
const product = await chocodata.amazon.product({ query: '0143127748' });
console.log(product.title, product.price);Get a key at chocodata.com. The free tier is 1,000 requests, one time, no card.
The key travels as a query parameter, which the SDK handles for you. There is no header auth: sending Authorization: Bearer or X-API-Key returns 401.
Every method below was smoke-tested against production before release.
// Ecommerce
await chocodata.amazon.product({ query: '0143127748' }); // ASIN or ISBN
await chocodata.amazon.product({ query: '0143127748', domain: 'de' });
await chocodata.amazon.search({ query: 'laptop' });
await chocodata.walmart.product({ url: 'https://www.walmart.com/ip/19075520026' });
await chocodata.walmart.search({ query: 'laptop' });
await chocodata.ebay.product({ url: 'https://www.ebay.com/itm/298520538688' });
await chocodata.ebay.search({ query: 'laptop' });
// Search engines (note: Bing takes `q`, not `query`)
await chocodata.bing.search({ q: 'coffee', count: 10 });
await chocodata.bing.images({ q: 'red panda', count: 20, safe_search: 'strict' });
// Social / video
await chocodata.tiktok.profile({ username: 'rotana' });
await chocodata.youtube.video({ video_id: 'dQw4w9WgXcQ' });
await chocodata.instagram.profile({ username: 'nasa' });Parameters are not uniform across targets, and the SDK does not pretend otherwise. amazon.product takes query; walmart.product takes url or id and rejects query; bing.search takes q. The types encode the real contract per endpoint.
The catalog has 499 endpoints. Anything without a typed method is reachable through the same code path:
await chocodata.scrape('bing', 'videos', { q: 'coffee' });
await chocodata.scrape('reddit', 'post', { post_id: '627akk', subreddit: 'askscience' });Failures throw a ChocodataError carrying the status, the API's error code, and whether the server considers it retryable.
import { Chocodata, ChocodataError } from 'chocodata';
try {
await chocodata.tiktok.profile({ username: 'a-handle-that-is-gone' });
} catch (e) {
if (e instanceof ChocodataError) {
console.log(e.status); // 404
console.log(e.code); // 'item_not_found'
console.log(e.retryable); // false
console.log(e.requestId); // for support
}
}Common codes:
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_params |
A required parameter is missing or the wrong type. The body names it. |
| 401 | INVALID_API_KEY |
Key missing, unrecognised, or revoked. |
| 402 | INSUFFICIENT_CREDITS |
Balance exhausted. |
| 404 | item_not_found |
The item genuinely does not exist. Not retryable. |
| 429 | RATE_LIMITED |
Over 120 requests / 60s, or over your plan's concurrency. |
| 502 | extraction_failed / target_unreachable |
Retryable; the SDK already retried. |
You are only charged on a 2xx. Errors cost no credits.
Retryable failures (429, 5xx, network) are retried with exponential backoff plus jitter. The SDK trusts the server's retryable flag, so a 404 is never retried.
const chocodata = new Chocodata('asa_live_YOUR_KEY', {
maxRetries: 3, // default 2
timeoutMs: 120_000, // default 90_000
debug: true, // log every call to stderr
});Requests are 120 per key per 60 seconds, plus a per-plan concurrency ceiling. Size your own fan-out:
const asins = ['0143127748', 'B09B8V1LZ3', 'B0BSHF7WHW'];
const results = await Promise.all(
asins.map((query) => chocodata.amazon.product({ query })),
);MIT