DEV Community

Cover image for Build a TikTok New-Product Ranking Pipeline with Node.js
Nexscope Team for Nexscope

Posted on Originally published at nexscope.ai AI-assisted

Build a TikTok New-Product Ranking Pipeline with Node.js

TikTok Shop rankings are snapshots, not conclusions. A useful pipeline records the market, requested date, raw response, optional fields, and retrieval time before it calculates any score or trend.

This Node.js example uses the current Nexscope TikTok New Product Rank API contract. It preserves missing values, retries transient failures, and compares two dated snapshots without treating correlation as causation.

Prerequisites

  • Node.js 18 or later
  • A server-side NEXSCOPE_API_KEY
  • A supported region code and date in YYYY-MM-DD
export NEXSCOPE_API_KEY="nk_your_key_here"
Enter fullscreen mode Exit fullscreen mode

Keep the key on the server. Do not expose it in browser code or commit it to source control.

Request a Ranking Page

The documented endpoint is:

POST https://api.nexscope.ai/api/skill-api/v1/skills/tiktok-new-product-rank/run
Enter fullscreen mode Exit fullscreen mode
const ENDPOINT = "https://api.nexscope.ai/api/skill-api/v1/skills/tiktok-new-product-rank/run";

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

async function fetchRanking({ region, date, pageNum = 1, pageSize = 50 }, attempts = 3) {
  for (let attempt = 1; attempt <= attempts; attempt++) {
    const response = await fetch(ENDPOINT, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.NEXSCOPE_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ region, date, pageNum, pageSize }),
    });

    if (response.ok) return response.json();

    const body = await response.text();
    const retryable = response.status >= 500;
    if (!retryable || attempt === attempts) {
      throw new Error(`Ranking request failed (${response.status}): ${body.slice(0, 300)}`);
    }

    await wait(500 * 2 ** (attempt - 1));
  }
}
Enter fullscreen mode Exit fullscreen mode

The current documentation lists date as required. region, pageNum, and pageSize are optional. Region availability and limits should be read from the live endpoint reference rather than hard-coded from an old article.

Normalize Optional Fields

The response can include optional values. Preserve null instead of converting missing fields to zero.

const numberOrNull = value =>
  typeof value === "number" && Number.isFinite(value) ? value : null;

const stringOrNull = value =>
  typeof value === "string" && value.length ? value : null;

function normalizeProduct(product, context) {
  return {
    productId: stringOrNull(product.asin),
    title: stringOrNull(product.title),
    region: stringOrNull(product.region) ?? context.region,
    requestedDate: context.date,
    retrievedAt: context.retrievedAt,
    price: numberOrNull(product.price),
    currency: stringOrNull(product.currency),
    sales30d: numberOrNull(product.totalSale30dCnt),
    gmv30d: numberOrNull(product.totalSaleGmv30dAmt),
    videos: numberOrNull(product.totalVideoCnt),
    livestreams: numberOrNull(product.totalLiveCnt),
    creators: numberOrNull(product.totalIflCnt),
    commissionRate: numberOrNull(product.productCommissionRate),
    rating: numberOrNull(product.productRating),
    reviews: numberOrNull(product.reviewCount),
    availableDate: stringOrNull(product.availableDate),
    trendLabel: stringOrNull(product.salesTrendFlagText),
    raw: product,
  };
}
Enter fullscreen mode Exit fullscreen mode

Store the Snapshot

Save both normalized and raw data. The raw payload allows future schema changes to be audited.

import { mkdir, writeFile } from "node:fs/promises";

async function saveSnapshot({ region, date }) {
  const retrievedAt = new Date().toISOString();
  const payload = await fetchRanking({ region, date });
  const products = Array.isArray(payload.products) ? payload.products : [];

  const snapshot = {
    region,
    requestedDate: date,
    retrievedAt,
    total: numberOrNull(payload.total),
    products: products.map(p => normalizeProduct(p, { region, date, retrievedAt })),
    raw: payload,
  };

  await mkdir("snapshots", { recursive: true });
  const path = `snapshots/${region}-${date}.json`;
  await writeFile(path, JSON.stringify(snapshot, null, 2));
  return snapshot;
}
Enter fullscreen mode Exit fullscreen mode

Compare Dated Snapshots

Use stable product IDs where available. A missing product ID should be excluded from automated matching.

function compareSnapshots(previous, current) {
  const before = new Map(
    previous.products.filter(p => p.productId).map(p => [p.productId, p])
  );

  return current.products
    .filter(p => p.productId)
    .map((now, rankIndex) => {
      const old = before.get(now.productId);
      const delta = (a, b) => a == null || b == null ? null : a - b;

      return {
        productId: now.productId,
        currentRank: rankIndex + 1,
        sales30dChange: old ? delta(now.sales30d, old.sales30d) : null,
        gmv30dChange: old ? delta(now.gmv30d, old.gmv30d) : null,
        creatorChange: old ? delta(now.creators, old.creators) : null,
        videoChange: old ? delta(now.videos, old.videos) : null,
        firstSeenInComparison: !old,
      };
    });
}
Enter fullscreen mode Exit fullscreen mode

Do not describe a higher creator count as the cause of higher sales. The data can flag acceleration for review, but it does not establish why the change happened.

Production Safeguards

  • Partition snapshots by requested date and region
  • Store retrieval timestamps and the complete raw response
  • Keep long identifiers as strings
  • Log status codes without logging API keys
  • Treat missing data as unknown
  • Document the scoring formula and version
  • Recheck current source, freshness, coverage, and rate-limit notes

The current endpoint documentation explicitly says that source, freshness, coverage, rate limits, and estimation notes are unspecified unless documented in request or response fields. Keep that boundary in the report.

Next Step

Use the live TikTok New Product Rank API reference to confirm the current contract before running the pipeline.

Open the API Documentation →

Disclosure: This article was prepared with AI-assisted editing using the current published API documentation as its technical source of truth.

Top comments (0)