Tutorial1 min readUpdated Mar 22, 2026

Use PriceFetch API with Node.js and TypeScript

TL;DR

TypeScript types for PriceFetch API responses, a reusable client class with error handling and retries, and examples using both fetch and axios.

TypeScript Type Definitions

Start by defining types that match the PriceFetch API response format. These types give you autocomplete, catch errors at compile time, and serve as documentation for the API's response structure.

The API always returns a consistent envelope: `success` boolean, `data` on success, `error` on failure, plus metadata like `credits_remaining` and `request_id`.

typescript
// types/pricefetch.ts

interface PriceData {
  url: string;
  price: number;
  currency: string;
  in_stock: boolean;
  retailer: string;
  timestamp: string;
}

interface PriceFetchError {
  code: string;
  message: string;
}

interface PriceFetchSuccess {
  success: true;
  data: PriceData;
  credits_remaining: number;
  request_id: string;
}

interface PriceFetchFailure {
  success: false;
  error: PriceFetchError;
  request_id: string;
}

type PriceFetchResponse = PriceFetchSuccess | PriceFetchFailure;

// Error codes you'll encounter
type PriceFetchErrorCode =
  | "UNSUPPORTED_RETAILER"
  | "PRICE_NOT_FOUND"
  | "PAGE_LOAD_FAILED"
  | "SCRAPE_FAILED"
  | "INVALID_API_KEY"
  | "RATE_LIMITED"
  | "INSUFFICIENT_CREDITS";

Try it yourself — 500 free API credits, no credit card required.

Start Free

Basic Client with Fetch

Node.js 18+ has built-in `fetch`, so you don't need any HTTP library. Here's a minimal client function that handles the API call and type-narrows the response.

The key pattern: check `response.success` to narrow the type. TypeScript knows that inside the `if (response.success)` block, `response.data` exists. Outside it, `response.error` exists.

typescript
// lib/pricefetch.ts

const API_KEY = process.env.PRICEFETCH_API_KEY!;
const BASE_URL = "https://api.pricefetch.dev/v1";

export async function getPrice(productUrl: string): Promise<PriceData> {
  const url = new URL(`${BASE_URL}/price`);
  url.searchParams.set("url", productUrl);

  const res = await fetch(url.toString(), {
    headers: { "X-API-Key": API_KEY },
    signal: AbortSignal.timeout(15_000),
  });

  const json: PriceFetchResponse = await res.json();

  if (!json.success) {
    throw new PriceFetchApiError(json.error.code, json.error.message, json.request_id);
  }

  return json.data;
}

// Custom error class for PriceFetch errors
export class PriceFetchApiError extends Error {
  constructor(
    public readonly code: string,
    message: string,
    public readonly requestId: string,
  ) {
    super(message);
    this.name = "PriceFetchApiError";
  }

  get isRetryable(): boolean {
    return ["SCRAPE_FAILED", "PAGE_LOAD_FAILED"].includes(this.code);
  }
}

Production Client Class

For production use, wrap the API in a class that handles retries, timeout configuration, and logging. This client retries transient errors with exponential backoff and provides a clean interface for the rest of your application.

typescript
// lib/PriceFetchClient.ts

interface ClientOptions {
  apiKey: string;
  baseUrl?: string;
  timeout?: number;
  maxRetries?: number;
}

export class PriceFetchClient {
  private apiKey: string;
  private baseUrl: string;
  private timeout: number;
  private maxRetries: number;

  constructor(options: ClientOptions) {
    this.apiKey = options.apiKey;
    this.baseUrl = options.baseUrl ?? "https://api.pricefetch.dev/v1";
    this.timeout = options.timeout ?? 15_000;
    this.maxRetries = options.maxRetries ?? 2;
  }

  async getPrice(productUrl: string): Promise<PriceData> {
    let lastError: Error | null = null;

    for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
      try {
        return await this.fetchPrice(productUrl);
      } catch (err) {
        lastError = err as Error;
        if (err instanceof PriceFetchApiError && !err.isRetryable) {
          throw err; // Don't retry client errors
        }
        if (attempt < this.maxRetries) {
          await this.sleep(Math.pow(2, attempt) * 1000);
        }
      }
    }

    throw lastError;
  }

  async getPrices(urls: string[], concurrency = 5): Promise<Map<string, PriceData | Error>> {
    const results = new Map<string, PriceData | Error>();
    const queue = [...urls];

    const worker = async () => {
      while (queue.length > 0) {
        const url = queue.shift()!;
        try {
          results.set(url, await this.getPrice(url));
        } catch (err) {
          results.set(url, err as Error);
        }
      }
    };

    await Promise.all(Array.from({ length: concurrency }, () => worker()));
    return results;
  }

  private async fetchPrice(productUrl: string): Promise<PriceData> {
    const url = new URL(`${this.baseUrl}/price`);
    url.searchParams.set("url", productUrl);

    const res = await fetch(url.toString(), {
      headers: { "X-API-Key": this.apiKey },
      signal: AbortSignal.timeout(this.timeout),
    });

    const json: PriceFetchResponse = await res.json();
    if (!json.success) {
      throw new PriceFetchApiError(json.error.code, json.error.message, json.request_id);
    }
    return json.data;
  }

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

Usage Examples

Here's how to use the client in different scenarios: single price fetch, bulk checking, and error handling. The client class handles retries internally, so your application code stays clean.

typescript
// Single price check
const client = new PriceFetchClient({
  apiKey: process.env.PRICEFETCH_API_KEY!,
});

const price = await client.getPrice("https://www.amazon.com/dp/B0CHX3QBCH");
console.log(`${price.currency} ${price.price}`); // USD 298.00

// Bulk price check
const urls = [
  "https://www.amazon.com/dp/B0CHX3QBCH",
  "https://www.amazon.com/dp/B0BSHF7WHW",
  "https://www.walmart.com/ip/12345",
];
const results = await client.getPrices(urls, 3);

for (const [url, result] of results) {
  if (result instanceof Error) {
    console.error(`Failed: ${url} - ${result.message}`);
  } else {
    console.log(`${result.retailer}: ${result.currency} ${result.price}`);
  }
}

// Error handling
try {
  const data = await client.getPrice("https://unsupported-site.com/product");
} catch (err) {
  if (err instanceof PriceFetchApiError) {
    switch (err.code) {
      case "UNSUPPORTED_RETAILER":
        console.log("This retailer isn't supported yet");
        break;
      case "RATE_LIMITED":
        console.log("Slow down — too many requests");
        break;
      default:
        console.log(`API error: ${err.message}`);
    }
  }
}

Axios Alternative

If you're already using axios in your project, here's the equivalent setup. The only meaningful difference is how timeouts and headers are configured — the PriceFetch API works the same either way.

typescript
import axios, { AxiosInstance } from "axios";

const api: AxiosInstance = axios.create({
  baseURL: "https://api.pricefetch.dev/v1",
  timeout: 15_000,
  headers: { "X-API-Key": process.env.PRICEFETCH_API_KEY! },
});

async function getPrice(productUrl: string): Promise<PriceData> {
  const { data: json } = await api.get<PriceFetchResponse>("/price", {
    params: { url: productUrl },
  });

  if (!json.success) {
    throw new PriceFetchApiError(json.error.code, json.error.message, json.request_id);
  }

  return json.data;
}

Frequently asked questions

Related Retailers

Start fetching prices — 500 free credits

Sign up in 30 seconds. No credit card required. One credit per successful API call.