cdpfleet Docs GitHub Dashboard

Cases / #47 · 2026-10-15 · Easy

A first free scrape on the Community plan: no proxy to bring, country and sticky exits included

On the free Community plan a launch without a proxy goes out through the plan's own shared proxy. Scrape a catalogue page, then pick a country and keep the same exit across sessions with a `cdpfleet-plan` token — and read the plan's limits from the API.

Chromium

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

The problem

Every cdpfleet session goes through a proxy, and on paid plans you bring your own. On the free Community plan the proxy is included: what happens when the launch names no proxy at all, how do you choose a country or keep the same IP, and what are the plan's limits — without guessing from the docs?

What we used, and why

WhatWhy
GET /v1/meThe plan as the account sees it: threads, daily minutes, session length, per-session page and memory limits, and which options the included proxy supports.
A launch without proxyOn Community the plan's included proxy is used. On a paid plan the same launch gets 400 proxy_required.
cdpfleet-plan-country-de-session-…-lifetime-10A token in place of a proxy URL: exit country Germany, and the same exit for 10 minutes for every session that uses the same session name. The dashboard's Proxies → Community proxy tab builds it.
browser.close()Ends the session at once, so the 60 daily minutes per thread go further.

How it works

  1. Read the plan from GET /v1/me.
  2. Launch headless Chromium without a proxy, check the exit country, and scrape the 20 books on the first page of books.toscrape.com.
  3. Launch twice more with the same cdpfleet-plan country + session token and compare the two exits.

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: COMMUNITY_API_KEY
import { chromium } from 'playwright';

const KEY = process.env.COMMUNITY_API_KEY;

// 503 means the fleet is momentarily busy: wait Retry-After and try again.
async function launch(body) {
  for (let attempt = 1; ; attempt++) {
    const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
      method: 'POST',
      headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
      body: JSON.stringify(body),
    });
    if (res.ok) return res.json();
    if (res.status !== 503 || attempt === 5) throw new Error(`launch ${res.status} ${await res.text()}`);
    await new Promise((r) => setTimeout(r, (Number(res.headers.get('retry-after')) || 5) * 1000));
  }
}

// The exit the site sees. Shared proxies drop the odd connection: retry a few times.
async function exitOf(page) {
  for (let i = 1; ; i++) {
    try {
      await page.goto('http://ip-api.com/json', { timeout: 30000 });
      const j = JSON.parse(await page.textContent('body'));
      return { ip: j.query, country: j.countryCode };
    } catch (err) {
      if (i === 4) throw err;
      await page.waitForTimeout(2000 * i);
    }
  }
}

async function session(body, work) {
  const s = await launch({ headless: true, record: false, ...body });
  const browser = await chromium.connect(s.wsUrl, { headers: { 'x-api-key': KEY } });
  try {
    return { weight: s.weight, ...(await work(await browser.newPage())) };
  } finally {
    await browser.close(); // ends the session and stops the clock
  }
}

// 1. What the free plan includes.
const me = await (await fetch('https://cdpfleet.com/v1/me', { headers: { 'x-api-key': KEY } })).json();
const sub = me.subscription;
const plan = {
  plan: sub.plan_id, threads: sub.threads, minutes_per_thread_per_day: sub.daily_minutes_per_thread,
  max_session_minutes: sub.max_session_seconds / 60, max_pages: sub.features.max_pages, memory_mb: sub.features.memory_mb,
  proxy_options: sub.plan_proxy?.options,
};

// 2. No proxy in the launch: the plan's included proxy is used. Scrape a catalogue page.
const first = await session({}, async (page) => {
  const exit = await exitOf(page);
  await page.goto('https://books.toscrape.com/', { timeout: 45000 });
  const books = await page.$$eval('article.product_pod', (els) => els.map((e) => ({
    title: e.querySelector('h3 a').getAttribute('title'),
    price: e.querySelector('.price_color').textContent.trim(),
  })));
  return { exit, books: books.length, first_book: books[0] };
});

// 3. A cdpfleet-plan token picks a country and keeps the same exit across sessions.
const token = `cdpfleet-plan-country-de-session-c47${Math.random().toString(36).slice(2, 7)}-lifetime-10`;
const a = await session({ proxy: token }, async (page) => ({ exit: await exitOf(page) }));
const b = await session({ proxy: token }, async (page) => ({ exit: await exitOf(page) }));

console.log(JSON.stringify({
  plan,
  default_proxy: { weight: first.weight, exit_country: first.exit.country, exit_ip: first.exit.ip, books: first.books, first_book: first.first_book },
  plan_token: { token: token.replace(/session-\w+/, 'session-…'), first_country: a.exit.country, second_country: b.exit.country, same_ip: a.exit.ip === b.exit.ip },
}, null, 2));

What we got

CheckResult
Plancommunity_60: 2 threads × 60 min/day, sessions ≤ 15 min, 5 pages / 3 GB per session
Included proxy optionscountry, session, lifetime
Launch without a proxyweight 1 (headless), exit US
Scraped20 books, first: "A Light in the Attic" £51.77
`cdpfleet-plan` country + sessionDE then DE, same IP

From the Node.js run on 2026-10-15. 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)
{
  "plan": {
    "plan": "community_60",
    "threads": 2,
    "minutes_per_thread_per_day": 60,
    "max_session_minutes": 15,
    "max_pages": 5,
    "memory_mb": 3072,
    "proxy_options": {
      "country": true,
      "os": false,
      "session": true,
      "lifetime": true
    }
  },
  "default_proxy": {
    "weight": 1,
    "exit_country": "US",
    "exit_ip": "203.0.113.1",
    "books": 20,
    "first_book": {
      "title": "A Light in the Attic",
      "price": "£51.77"
    }
  },
  "plan_token": {
    "token": "cdpfleet-plan-country-de-session-…-lifetime-10",
    "first_country": "DE",
    "second_country": "DE",
    "same_ip": true
  }
}

Takeaways