Competitive intelligence

Every competitor page,
captured every week

Send your list of competitor pricing, plan and landing pages in one batch request. Each page renders with banners removed, a webhook tells you when the captures are ready, and a dated folder of screenshots builds itself.

200 renders a month free, no card. Every feature on every plan.

Try it on your URL

Live capture, no signup. You get the screenshot and the exact API call that produced it.

Why this is harder than it looks

Pricing pages are where competitors change things quietly: a tier renamed, a limit lowered, an annual discount added. If nobody checks, you find out from a lost deal. If someone checks by hand, it happens for a month and then stops.

Scraping prices directly breaks every time the markup changes, and many pricing pages render their tables with JavaScript. A rendered screenshot is the stable record, and the rendered text is the easy thing to diff.

The useful output is a history: what the page looked like each week, side by side. That is a folder of dated captures, which is exactly what a batch job and a small webhook handler produce.

Who does what

SnapRender is the capture layer. The schedule and the history stay in your systems, which keeps your data where your policies say it should be.

SnapRender does

  • Accept up to 50 URLs in one POST /v1/screenshot/batch request and return a job id at once
  • Render each page fresh, full page if you ask, with ads and cookie banners removed
  • Call your webhook with a signed screenshot.completed event when the batch finishes
  • Give you download links for every capture, valid for 24 hours

You wire up

  • Keep the list of competitor URLs and a weekly trigger
  • Download the captures into dated storage when the webhook arrives
  • Diff prices or text if you want alerts, not just a history

The code

One weekly trigger submits the batch. A webhook handler verifies the signature, fetches the job, and saves every capture into a folder named for the date.

submit.sh: the weekly batch
Shell
curl -s -X POST "https://app.snap-render.com/v1/screenshot/batch" \
  -H "X-API-Key: $SNAPRENDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "https://competitor-one.example/pricing",
      "https://competitor-two.example/pricing",
      "https://competitor-two.example/enterprise"
    ],
    "format": "png",
    "full_page": true
  }'
# Response: {"jobId": "...", "status": "pending", "statusUrl": "/v1/screenshot/batch/...", ...}

Register a webhook once (POST /v1/webhooks with events ["screenshot.completed"]) and every batch tells you when it is done. Run this from cron or a scheduled workflow every Monday.

webhook.mjs: verify, fetch, save
Node.js
// npm install express
import crypto from 'node:crypto';
import fs from 'node:fs/promises';
import express from 'express';

const API = 'https://app.snap-render.com';
const KEY = process.env.SNAPRENDER_API_KEY;
const SECRET = process.env.SNAPRENDER_WEBHOOK_SECRET; // returned when you created the webhook
const app = express();

app.post('/snaprender', express.raw({ type: 'application/json' }), async (req, res) => {
  const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');
  const given = req.get('X-SnapRender-Signature') || '';
  if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
    return res.status(401).end();
  }
  res.status(200).end(); // acknowledge fast, work after

  const { jobId } = JSON.parse(req.body.toString());
  const job = await (await fetch(`${API}/v1/screenshot/batch/${jobId}`, { headers: { 'X-API-Key': KEY } })).json();
  const dir = `captures/${new Date().toISOString().slice(0, 10)}`;
  await fs.mkdir(dir, { recursive: true });
  for (const item of job.items) {
    if (item.status !== 'completed') { console.warn('failed', item.url, item.error); continue; }
    const img = Buffer.from(await (await fetch(item.downloadUrl)).arrayBuffer());
    await fs.writeFile(`${dir}/${new URL(item.url).hostname}${new URL(item.url).pathname.replace(/\//g, '_')}.png`, img);
  }
});

app.listen(3000);

The signature is an HMAC-SHA256 of the raw body with your webhook secret. Failed items are listed with their error and refunded to your quota.

prices.py: diff the pricing text
Python
import json, os, pathlib, re, requests

API = "https://app.snap-render.com"
HEADERS = {"X-API-Key": os.environ["SNAPRENDER_API_KEY"]}
url = "https://competitor-one.example/pricing"
seen = pathlib.Path("prices.json")

text = requests.get(f"{API}/v1/extract", headers=HEADERS, timeout=60,
                    params={"url": url, "type": "text"}).json()["content"]
prices = sorted(set(re.findall(r"[$€£]\s?\d[\d,.]*", text)))

old = json.loads(seen.read_text()) if seen.exists() else {}
if old.get(url) and old[url] != prices:
    print("Price change on", url, "\nwas:", old[url], "\nnow:", prices)
old[url] = prices
seen.write_text(json.dumps(old, indent=2))

Extracting the rendered text works on pricing tables built with JavaScript, where scraping the raw HTML returns nothing useful.

Every parameter is in the API reference and the OpenAPI spec. Errors come back as { "error": { "code", "message", "status" } }; a 429 with QUOTA_EXCEEDED means the monthly allowance is used.

What it costs at your volume

Thirty competitor pages captured weekly are about 130 renders a month, inside the free plan. Daily captures of 100 pages across a market are about 3,000, which is Growth at $29. Agencies tracking competitors for many clients usually land on Business at $79 for 50,000.

About 0 renders a month

Free

Start free

A month counts as 30 days. Failed captures are refunded. Yearly billing on paid plans is ten months for twelve. All plans.

Get a key and run the first recipe today

Every script on this page works with a free key: 200 renders a month, every feature included, no card. Your first capture takes about two minutes.

Paid plans from $9 for 2,000 renders. Yearly billing is ten months for twelve. Failed captures are refunded.

Frequently asked questions

How many URLs can one batch take?+

Up to 50 per request. Larger lists are split into several batches; each returns its own job id and fires its own webhook when it finishes.

Do I have to poll for results?+

No. Register a webhook for screenshot.completed and SnapRender posts to your endpoint when the batch is done. Polling GET /v1/screenshot/batch/{jobId} also works if you would rather not run an endpoint.

What happens when a competitor blocks the capture?+

The item is marked failed with an error message and the render is refunded to your quota. Sites with aggressive bot protection may fail consistently; SnapRender does not offer residential proxies or geo-targeted rendering.

How long are batch results available?+

Download links last 24 hours. Save the captures into your own storage when the webhook arrives, which is also what builds the weekly history.

Is it legal to screenshot competitor websites?+

Capturing publicly available pages for internal analysis is common practice, but terms of service and local law vary. Do not capture pages behind logins you are not authorized to use, and check with your counsel if you plan to publish the captures.

Related

Other use cases

Your first capture takes two minutes

200 renders a month free, no card. The API key is on the next screen.