cdpfleet Docs GitHub Dashboard

Cases / #51 · 2026-10-16 · Medium

Cut residential spend: the page on Tier 1, the images on Tier 4, in one session

The cdpfleet residential proxy has four price tiers, from $5 to $0.50 per GB. Send a gallery page through Tier 1 and its image hosts through Tier 4 with `proxy_rules`, count the bytes of each, and check them against what the balance actually billed per tier.

Chromium

Run on production on 2026-10-16: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go

The problem

Residential traffic is billed per GB, and on most pages the bulk of the bytes isn't the HTML you came for but images and static files from a CDN. The tier in the cdpfleet-resi token sets the price per GB, so the question is practical: can one session send the page through one tier and the heavy hosts through a cheaper one, how much of a real page is "heavy", and does the bill really follow the split?

What we used, and why

WhatWhy
proxy: "cdpfleet-resi-country-us"The default route: every host no rule matches. No -tier-N means Tier 1 ($5/GB), US exits.
proxy_rules → cdpfleet-resi-tier-4-country-usThe two image hosts (thumb.wikimedia.org, upload.wikimedia.org) through Tier 4 ($0.50/GB). A token can be the session's proxy or a single rule's proxy (not part of a list of proxies).
request.sizes() on requestfinishedHeaders and body of every request and response, as the browser saw them, added up per host group.
GET /v1/me/proxy-balanceused_today lists today's billed bytes per tier. Read before and after, the difference is what this run was billed — metering trails by about a minute, so the program waits for it.

How it works

  1. Read today's billed bytes per tier.
  2. Launch headless Chromium with Tier 1 as the default and a Tier 4 rule for the image hosts.
  3. Open a Wikimedia Commons category gallery, scroll to the end so the lazy thumbnails load, and add up the bytes of the page host and of the image hosts.
  4. Close the session, wait for metering, and compare the billed bytes per tier with what the browser counted; work out what 1,000 such loads cost on Tier 1 only and with the split.

The code

The same program in five languages (also on GitHub, with the raw output). Set these environment variables first:

// npm install [email protected]
// env: CDPFLEET_API_KEY (no proxy of your own needed: the cdpfleet residential proxy, billed per GB by tier)
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;
const PAGE = 'https://commons.wikimedia.org/wiki/Category:Cats';
const PRICE = { 1: 5, 4: 0.5 }; // USD per GB (10^9 bytes)
const CDN = ['thumb.wikimedia.org', 'upload.wikimedia.org']; // the images; HTML, CSS and JS come from commons.wikimedia.org

// One session, two prices: the page through Tier 1, the image hosts through Tier 4.
const LAUNCH = {
  headless: 'new',
  record: false,
  proxy: 'cdpfleet-resi-country-us',
  proxy_rules: [{ hosts: CDN, proxy: 'cdpfleet-resi-tier-4-country-us' }],
};

// Today's residential traffic per tier, as billed.
async function usedToday() {
  const b = await (await fetch('https://cdpfleet.com/v1/me/proxy-balance', { headers: { 'x-api-key': KEY } })).json();
  return Object.fromEntries(b.used_today.map((t) => [t.tier, t.bytes]));
}

// Load the gallery, scroll to the end, and add up every request's bytes by host group.
async function load() {
  const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
    method: 'POST',
    headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
    body: JSON.stringify(LAUNCH),
  });
  if (!res.ok) throw new Error(`launch ${res.status} ${await res.text()}`);
  const { wsUrl } = await res.json();
  const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
  try {
    const page = await browser.newPage();
    const done = [];
    page.on('requestfinished', (req) => done.push(req));
    // Residential peers drop the odd connection: retry the navigation once.
    for (let attempt = 1; ; attempt++) {
      try { await page.goto(PAGE, { waitUntil: 'load', timeout: 60000 }); break; } catch (err) { if (attempt === 2) throw err; }
    }
    for (let i = 0; i < 20; i++) { // lazy-loaded thumbnails load as they scroll into view
      await page.mouse.wheel(0, 1500);
      await page.waitForTimeout(250);
    }
    await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
    const bytes = { page: 0, images: 0 };
    let images = 0;
    for (const req of done) {
      const s = await req.sizes().catch(() => null);
      if (!s) continue;
      const total = s.requestHeadersSize + s.requestBodySize + s.responseHeadersSize + s.responseBodySize;
      if (CDN.includes(new URL(req.url()).hostname)) { bytes.images += total; images++; } else bytes.page += total;
    }
    return { requests: done.length, image_requests: images, page_kb: Math.round(bytes.page / 1000), image_kb: Math.round(bytes.images / 1000) };
  } finally {
    await browser.close();
  }
}

const before = await usedToday();
const run = await load();

// Metering trails the traffic by about a minute: wait until Tier 4 shows the images.
let after = before;
for (let i = 0; i < 18 && ((after[4] || 0) - (before[4] || 0)) / 1000 < run.image_kb * 0.8; i++) {
  await new Promise((r) => setTimeout(r, 10000));
  after = await usedToday();
}

// What 1,000 such loads cost: everything on Tier 1 versus the split.
const per1000 = (pageTier, imageTier) => Math.round((run.page_kb * PRICE[pageTier] + run.image_kb * PRICE[imageTier]) / 1e6 * 1000 * 100) / 100;
console.log(JSON.stringify({
  page: PAGE,
  run,
  image_share: Math.round((run.image_kb / (run.image_kb + run.page_kb)) * 100) / 100,
  cost_per_1000_loads_usd: { all_tier_1: per1000(1, 1), page_tier_1_images_tier_4: per1000(1, 4) },
  billed_kb: {
    tier_1: Math.round(((after[1] || 0) - (before[1] || 0)) / 1000),
    tier_4: Math.round(((after[4] || 0) - (before[4] || 0)) / 1000),
  },
}, null, 2));

What we got

MeasuredResult
Requests223, of which 172 to the image hosts
Bytes seen by the browserpage host 849 KB (Tier 1), image hosts 1856 KB (Tier 4) — images 69 %
Bytes billed for the runTier 1 838 KB, Tier 4 1747 KB
1,000 loads, all on Tier 1$13.53
1,000 loads, images on Tier 4$5.17 (−62 %)

From the Node.js run on 2026-10-16. IP addresses are replaced with placeholders (203.0.113.x); equal addresses stay equal. The other languages produced the same findings.

Raw output (Node.js)
{
  "page": "https://commons.wikimedia.org/wiki/Category:Cats",
  "run": {
    "requests": 223,
    "image_requests": 172,
    "page_kb": 849,
    "image_kb": 1856
  },
  "image_share": 0.69,
  "cost_per_1000_loads_usd": {
    "all_tier_1": 13.53,
    "page_tier_1_images_tier_4": 5.17
  },
  "billed_kb": {
    "tier_1": 838,
    "tier_4": 1747
  }
}

Takeaways