Documentation Menu

Project Configurations

In RegressionBot, a project serves as both a baseline namespace and a saved execution configuration. Once created, a project locks in its testing parameters — preventing baseline drift and ensuring that every run compares against the same set of pages, devices, and masks.

Projects operate in managed mode (where screenshots are stored as baselines and compared on subsequent runs) by default. They work the same way across all interfaces: the REST API, the Node.js SDK, and MCP agentic tools. The first run against a new project name creates the project and saves its configuration. Repeat runs can omit all parameters and load the saved configuration automatically.

Project Configuration Locking

Configuration locking applies exclusively to managed mode runs (where no baseOrigin is supplied and screenshots are stored as baselines for comparison on future runs). In live-vs-live mode (where baseOrigin is present), parameters are always accepted since there are no stored baselines to protect.

First Run (Creation)

Executing a job for a new project name automatically creates the project entity. The parameters you provide (testOrigin, paths, devices, masks) are saved as the configuration for all future runs.

Subsequent Runs (Enforcement)

Subsequent runs against the same project name must either omit parameters (inheriting them from the stored configuration) or pass identical values. Providing differing parameters returns a 400 validation error.

Param Matching Rules

When verifying parameters, RegressionBot compares the following fields against the stored project configuration. Only fields you supply are checked — omitting a field is always allowed.

FieldComparison
testOriginExact string match
devicesSet equality (order-insensitive)
pathsSet equality on path values and label descriptions
masksSet equality on CSS selector strings
customCssExact string match (whitespace-trimmed)
sitemapUrlExact string match
scansSet equality on pattern values
concurrencyExact numeric match

Capture Options

Sticky and fixed elements are handled automatically on every full-page capture — there is no flag to toggle. For content that needs custom treatment, customCss can be set at project creation or updated later, and applies to server-side (cloud worker) capture.

OptionTypeDescription
customCssstring
max 4096 chars
CSS injected into the page immediately before screenshotting — e.g. #chat-widget { display: none !important; } to fully remove a dynamic widget. Applied last, so it can also override the automatic sticky/fixed handling for a specific element. Because it changes the rendered screenshot, it is a config-locked parameter (see above). See Masking for the difference between hiding via CSS and selector masks.
testAuth
baseAuth
objectCredentials for a gated preview or staging environment — Vercel preview protection, a Netlify or Amplify branch password, nginx .htpasswd, or a Cloudflare Access service token. See Gated Environments below. testAuth applies to testOrigin, baseAuth to baseOrigin.
Note: Like masks, customCss is config-locked: because it changes the rendered screenshot, passing a different value inline on a managed project is rejected as a parameter mismatch — use update_project (or PUT /project/{name}) to change it, which re-baselines. Sticky/fixed handling is automatic, so existing projects may show a one-time wave of sticky-element diffs on their next run; see Troubleshooting.

Scheduled Checks

A project can run on its own, with nobody triggering it. This is what catches the changes no deploy caused — a third-party script updating itself, an edit made in your CMS, a web font that stopped loading, a certificate that expired. Your CI never sees any of those, because none of them came from a commit.

OptionValuesDescription
scheduledaily
weekly
How often the project runs unattended. Omit it and the project only runs when you trigger it.
scheduleHourUtc023The UTC hour a daily or weekly project runs — 3 means 03:00 UTC. Omit it and the slot anchors to whenever the first sweep picked the project up. Hours only, and UTC only: the sweep runs once an hour, and there is no timezone setting anywhere in RegressionBot to convert from. Projects sharing an hour start one at a time, so several pinned to the same slot run minutes apart rather than all at once.
baselinePolicyapproved (default)
rolling
approved: you accept a run and its captures become the new baseline. rolling: every completed run advances the baseline automatically.

Why a schedule requires a rolling baseline

Setting schedule on a managed project without baselinePolicy: "rolling" is rejected by the API — live-vs-live projects store no baseline, so the policy does not apply to them and neither does the requirement. The reason is worth understanding rather than working around: on the default policy the baseline only moves when a person approves a run. So a change that is reported and not approved is still a difference from the baseline tomorrow — and the day after. A daily check would email you the same change every morning until you acted on it, which is how people learn to ignore alerts.

Rolling advances the baseline on every run, so each check compares against yesterday and reports only what is genuinely new. It advances even when a run finds something wrong — otherwise you are back to the repeat-alert problem. The evidence is not lost: the run keeps its before and after images for 14 days, and the alert links to them.

Cost scales with frequency. Every run is a fresh capture of every page on every device, and billing is per comparison. daily across 20 pages on 3 devices is 1,800 comparisons a month. Most marketing sites want daily;weekly is a quarter of the cost.

Turning a schedule off

Send {"schedule": null}. Omitting the field does nothing — a project update is a patch, so leaving schedule out of the body leaves the existing schedule running. Setting or clearing a schedule is metadata: it does not touch your baselines, so you can turn a check on to try it and off again without re-priming the project.

When a scheduled run happens

schedule sets how often, not what time. A sweep runs every hour and starts every project that has come due, and the interval is measured from the moment the last scheduled run started. So turning on a daily schedule at 09:20 gets its first run at the next sweep — some time before 10:20 — and every check after that anchors to whenever that first one began. A run may start up to 30 minutes early, which is the tolerance that stops a daily check drifting later by a few minutes every day.

There is no time-of-day or day-of-week setting yet. If you need a check at a specific hour, set the schedule at that hour, or trigger the run yourself from cron or a CI schedule and leave schedule unset.

A run that never starts — a lapsed plan, an exhausted monthly allowance — does not consume its slot: the project stays due and the next sweep tries again rather than silently losing a day. That is a different thing from a run that starts and cannot finish, which is covered under Alerts below. An unreachable site is the second kind: the run starts, every page fails to capture, and the check reports that it could not check anything.

Which key a scheduled run is billed to

Nobody is holding an API key when an unattended run starts, so the key that set the schedule is recorded with it and every scheduled run is attributed to that key. Set your schedule with an API key, not from a browser session: a run with no key recorded against it cannot meter AI usage, so its summaries are skipped and the alert email lists the changed pages without saying what changed on them. GET /project/{name} returns scheduleKeyId — if it is absent, set the schedule again using an API key.

Deleting that key does not stop the schedule — checks carry on running, and their usage shows against the deleted key's id rather than its name. To re-attribute a schedule to a different key, set schedule again using that key; that is a metadata change, so it leaves your baselines alone.

Alerts

When a scheduled run finds changes, your organisation's owners get an email describing what changed in plain English, with a link to the comparison. Runs you trigger yourself do not send email — the results are already in front of you.

A scheduled run that could not check your site also emails, and this is the one that matters most: nobody is watching an unattended check, so a check that stopped working would otherwise look exactly like a site with nothing wrong. Three shapes, all covered — a run that stopped outright (the sitemap would not load), a run that finished having failed to capture a single page (the site was down, or its credentials expired), and a run that never started at all (a lapsed plan, an exhausted allowance). Each names what stopped it and confirms the schedule is untouched. The first two are sent once per run; the third is sent at most once a day per project, because a lapsed plan stays lapsed and would otherwise mail you hourly.

A run where some pages failed and others succeeded is a real comparison, not a failure: it reports normally, and the failed pages appear as errors in the results. A clean run stays silent — there is no "all is well" email.

curl -X PUT https://api.regressionbot.com/project/marketing-site \
  -H "Authorization: Bearer $REGRESSIONBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schedule": "daily",
    "baselinePolicy": "rolling"
  }'

Gated Environments

Preview and staging environments are usually behind a door. testAuth and baseAuth carry the credential that opens it, so the crawler can reach the site at all — including the sitemap, which is otherwise the first thing to get a 401.

GateWhat to send
nginx .htpasswd, Netlify or Amplify branch password{ "basic": { "username": "...", "password": "..." } }
Vercel preview protection{ "headers": { "x-vercel-protection-bypass": "..." } }
Cloudflare Access service token{ "headers": { "CF-Access-Client-Id": "...", "CF-Access-Client-Secret": "..." } }
curl -X PUT https://api.regressionbot.com/project/staging-site \
  -H "Authorization: Bearer $REGRESSIONBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "testAuth": {
      "basic": { "username": "preview", "password": "..." }
    }
  }'
How these are handled. Credentials are encrypted before storage and are never returned by the API — reading a project shows "testAuth": { "configured": true } and nothing else. They are also scoped to the origin the run resolves to, so a credential cannot follow a redirect to another host. Because the scoping is per run rather than per hostname, ephemeral preview URLs work without reconfiguring anything.
This is for environment gates, not user logins. These are long-lived credentials that get past a door in front of an otherwise ordinary site. Capturing pages behind a per-user session — a logged-in dashboard, a checkout flow — is a different problem and is not what this solves.

Baseline Invalidation & Rollback

When you need to update a project's parameters (for example, adding a new page path or a new device), the existing baselines are no longer compatible with the new configuration.

To update a project's configuration via the REST API, use PUT /project/{name}. This updates the stored config and invalidates existing baselines:

curl -X PUT https://api.regressionbot.com/project/my-project \
  -H "x-api-key: $REGRESSIONBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"testOrigin": "https://new-origin.com", "devices": ["Desktop Chrome"]}'

This triggers a baseline invalidation — because baselineVersion changes, the next run creates new baselines from scratch. All results on the next job will show isNewBaseline: true. Run the job a second time to begin detecting regressions.

What happens on invalidation:

  • No destructive deletes: Historical baseline screenshots are never deleted when a configuration changes.
  • Superseded in place: The next job run captures new screenshots and saves them as fresh baselines (isNewBaseline: true), bypassing any comparison against the old configuration.
  • New baselines on next run: After PUT /project/{name}, the next run re-baselines everything. Run it a second time to start detecting regressions against the updated baselines.

Sitemap Crawling & Discovery

Instead of manually specifying every single page path to test, you can configure RegressionBot to discover pages dynamically by crawling your environment's sitemap.

How it Works

When sitemap crawling is triggered, RegressionBot downloads the target sitemap XML (e.g., your staging sitemap), extracts only the pathnames from the URLs (e.g. https://staging.regressionbot.com/docs/ becomes /docs/), and then reconstructs the final target URL by prepending your current testOrigin (e.g. http://localhost:3000/docs/).

This decoupling allows you to supply a sitemap hosted on production (e.g., https://regressionbot.com/sitemap.xml) to crawl and test your local dev server running on http://localhost:3000.

Absolute URLs Only

The sitemapUrl parameter must always be a fully qualified, absolute URL. Relative paths (like /sitemap.xml) fail input validation and return a 400 Bad Request.

On-the-fly Baseline Seeding

When you deploy a new page to staging and it gets added to the sitemap, the next run automatically detects it. The system captures it and seeds a new baseline (isNewBaseline: true) for that specific path without invalidating the rest of the project's baselines.

Sitemap Defaults & Config Locking

  • Default Fallback: If sitemap scanning is active (either via `/crawl` or loaded from project config) but you omit sitemapUrl, it automatically falls back to {testOrigin}/sitemap.xml.
  • Managed Mode: The sitemapUrl is locked to prevent baseline drift. You cannot override it inline during a run; you must use PUT /project/{name} to update it (which resets the baselines).
  • Live-vs-Live Mode: Config locking is disabled. You are free to pass any sitemapUrl inline for each test run.

How to Configure

Projects can be configured and run across all interfaces. Use the selectors below to choose your environment:

1. Creating a Project (First Run)

Executing a job for a new project name automatically creates it. The parameters you provide (origin, paths, devices) are locked in as the configuration for all future runs.

import { RegressionBot } from '@regressionbot/sdk';

const client = new RegressionBot(process.env.REGRESSIONBOT_API_KEY!);

const job = await client
  .test('https://staging.myapp.com')
  .forProject('my-billing-app')
  .on(['Desktop Chrome', 'iPhone 12'])
  .check('/invoice')
  .check('/settings')
  .run();

2. Parameter-less Reruns

Once a project exists, you can trigger a run by providing only the project name. The stored configuration is loaded automatically.

import { RegressionBot } from '@regressionbot/sdk';

const client = new RegressionBot(process.env.REGRESSIONBOT_API_KEY!);

// Triggers a run against the saved project configuration
const job = await client.runProject('my-billing-app');

Troubleshooting

400 Bad RequestParameter mismatch

Why this happens: You triggered a job against an existing project but supplied parameters — such as testOrigin, devices, or paths — that differ from the saved config. The error message names the specific fields that conflict.

Solution: Either omit those parameters so the saved config is used automatically, or use POST /project/{name}/run with no body. To intentionally change the config (for example, to add new pages), use the MCP update_project tool or PUT /project/{name} via REST — note that updating the config means the next job will re-baseline everything.

404 Not FoundProject not found

Why this happens: You called GET /project/{name} or POST /project/{name} /run but no project with that name exists in your organisation.

Solution: Check the name for typos. Use GET /projects to list all projects in your org. To create a project, run a managed-mode job against the name via POST /crawl with the full parameter set.

400 Bad RequestMissing testOrigin on parameter-less run

Why this happens: You called POST /project/{name} /run with no body, but the project exists without a saved testOrigin. This can happen if the project was created before the config-locking feature was introduced.

Solution: Run a full job via POST /crawl with the project name and all required parameters. This saves the config to the project for future parameter-less runs.

isNewBaseline: trueAll results show isNewBaseline: true

Why this happens: This is expected on the first run for a new project — no comparison has been made yet, baselines were created. It also occurs after a PUT /project/{name} update, which resets baselineVersion and forces re-baselining.

Solution: Run the job a second time to detect regressions against the newly created baselines. Use POST /approve (or the approve_job MCP tool) after the first run if you want to lock in these screenshots as baselines immediately without a second run.