Branch Opening & Relocation Signal Monitor avatar

Branch Opening & Relocation Signal Monitor

Pricing

from $200.00 / 1,000 branch events

Go to Apify Store
Branch Opening & Relocation Signal Monitor

Branch Opening & Relocation Signal Monitor

Watch official store-locator sitemaps and location pages for chains you sell to. Keep a branch registry across runs and emit newly listed, coming-soon, likely-relocation, and confirmed-removal events with evidence and confidence.

Pricing

from $200.00 / 1,000 branch events

Rating

0.0

(0)

Developer

Olalekan Johnson

Olalekan Johnson

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

11 days ago

Last modified

Security

Limited permissions

Share

Unofficial monitor for restaurant suppliers, commercial cleaning companies, signage vendors, and service agencies that sell to expanding chains. You give it the brands you already care about. It reads each brand's own location sitemap and location pages, keeps a branch registry between runs, and reports a small set of evidence-backed changes: a newly listed branch, a coming-soon announcement, a likely relocation, a removal that survived a second successful scan, or an identity it could not resolve.

The first time a branch shows up here is not an opening date. openingDateVerified is always false. A date is copied into announcedOpeningDate only when the brand's own page states one next to coming-soon language.

This Actor is not affiliated with Chipotle, Trader Joe's, Wendy's, or any other brand you add. It collects public location pages only. It does not collect phone numbers, email addresses, or staff names.

Who it is for

Use it when a new official branch, or a branch that moved down the street, is worth a sales visit and a stale national directory is not. A weekly run over your target chains is the intended schedule. It is a poor fit if you need a person's contact details, a permit, or a guaranteed grand-opening day.

What you provide

Each brand needs a name and one official source:

FieldRole
sitemapUrlLocation sitemap or sitemap index. This is the inventory. Additions and removals are judged from it.
jsonUrlLocation JSON endpoint, used when you do not have a sitemap.
locatorPageUrlOne page that publishes a single branch as JSON-LD or microdata.
urlIncludeRegex / urlExcludeRegexOptional filters. Without an include regex, a URL counts as a branch only when its last path segment contains a digit, which skips state and city index pages on the demo sites.
countryUsed only when the page itself omits a country. It does not drop other countries.

Leave brands empty and the run uses three public location sitemaps: Chipotle (https://locations.chipotle.com/sitemap.xml), Trader Joe's (https://locations.traderjoes.com/sitemap.xml), and Wendy's (https://locations.wendys.com/sitemap.xml). The default opens 3 location pages per brand after reading the sitemaps, which is enough to return a registry sample in well under two minutes. On a check from this environment that sample finished in about 7 seconds and returned 9 baseline branches plus a summary.

Other limits you can set: maxLocationPagesPerBrand (1–30), maxLocationChecks, maxBranchEvents, relocationRadiusMeters (default 1,200), alertOnlyOnChange, stateStoreName (default branch-opening-registry), and monitorKey.

What you get

Every row is flat and uses the same columns, so a spreadsheet, webhook, or Make/n8n scenario can filter on recordType.

recordType is one of:

  • baseline — a branch read from the official page. The first complete inventory is entirely baseline. Later runs also use baseline when they open a page that was already in that opening inventory. These rows are not billed as branch events.
  • branch_event — newly_listed, coming_soon, likely_relocation, or removed.
  • unresolved — the page did not identify one branch, two nearby closures could match one new listing, or the address changed in a way that is not a relocation. Unresolved rows are not billed.
  • summary — one row per run with counts and coverage.

Coverage on every row is complete, partial, empty, blocked, or unsupported. A partial scan never turns a missing URL into a removal. A failed or suspiciously shrunken sitemap does not replace the last good registry.

Baseline row (default-run shape)

This is the kind of row the default input returns. Addresses are normalized (suffixes, directionals, suite pulled into its own field) so the same shop does not look new because the page wrote "Street" one week and "St" the next.

{
"recordType": "baseline",
"eventState": "baseline",
"locationStatus": "open",
"brand": "Chipotle",
"formattedAddress": "725 1st st s, Alabaster AL 35007, US",
"suite": null,
"latitude": 33.23544,
"longitude": -86.812709,
"locationUrl": "https://locations.chipotle.com/al/alabaster/725-1st-st-s",
"evidenceUrls": "https://locations.chipotle.com/al/alabaster/725-1st-st-s | https://locations.chipotle.com/sitemap.xml",
"sourceTypes": "json_ld",
"confidence": 0.95,
"confidenceLabel": "high",
"corroborated": true,
"coverageStatus": "complete",
"firstObservedAt": "2026-10-23T04:37:23Z",
"announcedOpeningDate": null,
"openingDateVerified": false,
"billable": false,
"charged": false,
"reason": "Baseline registry row from the brand's own location page. The first time this monitor sees a branch is not an opening date."
}

Those coordinates were on that page's JSON-LD when checked on 23 October 2026. Unofficial, not affiliated with Chipotle.

Later-run events

These shapes appear only after a baseline exists. They are not what the first default run emits.

Newly listed, after the URL is absent from the previous complete sitemap and the new page has a structured address:

{
"recordType": "branch_event",
"eventState": "newly_listed",
"brand": "Example Grill",
"formattedAddress": "100 elm st, Dallas TX 75201, US",
"locationUrl": "https://locations.example.com/tx/dallas/100-elm-st",
"confidence": 0.95,
"corroborated": true,
"openingDateVerified": false,
"billable": true,
"charged": true,
"billingEvent": "branch-event",
"reason": "This official location URL was not in the previous complete sitemap, and the page publishes a structured address."
}

Coming soon. The announced date is the page's date, not a verified opening:

{
"recordType": "branch_event",
"eventState": "coming_soon",
"locationStatus": "coming_soon",
"announcedOpeningDate": "2027-09-02",
"announcedOpeningDateRaw": "September 2, 2027",
"openingDateVerified": false,
"firstObservedAt": "2026-10-08T00:00:00Z",
"billable": true,
"charged": true
}

Likely relocation. Pending until the old page is confirmed gone; the confirmation row is not billed again:

{
"recordType": "branch_event",
"eventState": "likely_relocation",
"confirmation": "pending",
"removalConfirmed": false,
"formattedAddress": "9 congress ave, Austin TX 78701, US",
"previousFormattedAddress": "1 main st, Austin TX 78701, US",
"previousLocationUrl": "https://locations.example.com/tx/austin/1-main-st",
"relatedBranchId": "br_previous",
"confidenceLabel": "high",
"billable": true,
"charged": true,
"reason": "A new official address appeared within 1200 meters of a branch that left the location sitemap. This is a likely relocation, not proof of a move date."
}

Removal, only after the URL is missing from two consecutive complete sitemap reads and the official page is gone (HTTP 404 or 410, or a not-found title):

{
"recordType": "branch_event",
"eventState": "removed",
"locationStatus": "removed",
"removalConfirmed": true,
"consecutiveMisses": 2,
"openingDateVerified": false,
"billable": true,
"charged": true,
"reason": "The location URL was missing from two consecutive complete sitemap scans and the official page is now gone. This is a confirmed removal, not a closing date."
}

Unresolved identities are written and not charged. A run summary row is always written. The same counts are stored as the OUTPUT record in the run's default key-value store.

How a repeat run behaves

The registry lives in a named key-value store (branch-opening-registry unless you rename it), one record per brand and source. Scheduled runs must keep that store.

  1. The first complete sitemap becomes the baseline. New URLs in that file are not called openings.
  2. On the next complete scan, a URL that was not in the inventory is fetched ahead of older pages. A corroborated structured address becomes newly_listed or coming_soon.
  3. If that new address is within the relocation radius of exactly one branch that has disappeared from the sitemap, the event is likely_relocation instead. confirmation stays pending until a later scan sees the old page gone. That confirmation is not charged again.
  4. A URL missing once is only pending. It is removed only on the next complete scan, and only if a re-fetch shows the page is gone. If the page is still live, the row is unresolved_identity and the branch is kept.
  5. A suite change, or a coordinate wobble under 75 meters at the same street, updates the registry quietly.
  6. If this run's sitemap is less than half the size of a previous inventory of 40 or more locations, or the fetch fails, the previous registry is left in place.
  7. alertOnlyOnChange drops baseline rows. Branch events, unresolved rows, and the summary remain.

Page fetches rotate. With the default cap of 3, each run opens new URLs first, then never-opened URLs from the original inventory, then the oldest pages. Opening those older pages later does not create a false "newly listed" event.

Pricing

This Actor uses pay-per-event pricing. There is no per-dataset-item charge. The platform start event is $0.005 per GB, about $0.0025 per run at the default 512 MB.

EventWhen it is billedPrice
location-page-checkedA sitemap document or location page was fetched and answered with a definitive result (200, or 404/410 for a location page). Failures, timeouts, robots blocks, and unsafe URLs are not billed.$0.005
branch-eventA new corroborated newly_listed, coming_soon, likely_relocation, or removed row was delivered. Baseline rows, unresolved rows, quiet suite edits, and a relocation confirmation already billed once are not billed. The same event ID is not billed twice.$0.20

Worked examples, before the platform start event:

RunWhat happenedLocation checksBranch eventsUsage charge
Demo, first run3 chains, their sitemaps, 3 pages each. About 16 checks and 9 baseline rows.16 × $0.0050$0.08
Weekly watch40 chains, 2 sitemap files and 8 pages each, 12 real changes400 × $0.005 = $2.0012 × $0.20 = $2.40$4.40 per run, about $18 across four weekly runs
Heavy month10,000 checks and 250 corroborated changes. The 250 is an assumption to measure, not a forecast. Sparse territories will be cheaper and less informative.$50$50$100

A platform spending cap stops the run after the last unit that fit. Unbilled branch events stay queued on the brand's registry and are delivered on a later run instead of being dropped or billed twice.

Schedule it

Run the same input weekly. Point the integration at the default dataset and keep rows where recordType is branch_event and charged is true. Use eventId as the dedupe key.

  • Webhook: Apify run-succeeded webhook to your endpoint. Read {{links.apiDefaultDatasetUrl}}/items?clean=1.
  • Make or n8n: Apify "get dataset items" module, then a filter on recordType = branch_event, then your CRM or email step. Map formattedAddress, locationUrl, eventState, reason, and confidence.
  • Google Sheets: Apify's Google Sheets integration on the default dataset. The columns are already one cell each; evidenceUrls is a single text field separated by |.
  • Named registry: the store named in stateStoreName is the memory. The run's OUTPUT record is the summary for that week.

Run it locally

Python 3.12.

pip install -r requirements-dev.txt
python -m src
pytest

An empty input uses the demo brands. Local runs are not pay-per-event unless you set ACTOR_TEST_PAY_PER_EVENT=true; charges are then logged and nothing is billed. Set ACTOR_MAX_TOTAL_CHARGE_USD if you want to see the run stop at a cap.

$ACTOR_TEST_PAY_PER_EVENT=true python -m src

Limits

  • Static HTTP only. A locator that renders locations only in a browser, with no sitemap, JSON, or JSON-LD/microdata, comes back unsupported or unresolved.
  • The default URL rule needs a digit in the last path segment. Chains that publish unnumbered slugs need urlIncludeRegex.
  • A likely relocation needs coordinates on both branches and exactly one candidate inside the radius. Two candidates become unresolved_identity rather than a guess.
  • At most 5 disappeared URLs per brand are re-checked per run, so a large closure wave is confirmed over more than one run.
  • Robots.txt disallow rules are honored. Private hosts, link-local addresses, and embedded credentials are rejected before a request.
  • Sitemap files are capped (8 per brand) and tracked URLs are capped (25,000). Past either cap, coverage is partial and removals are not inferred.
  • Hours are recorded only as "published or not." Phone, email, and contact names are dropped even when the page publishes them.
  • Source pages belong to their publishers. Keep their terms in mind before you store or resell the rows. A public sitemap is not a license to build a contact list.

FAQ

Why did the first run not list any openings? That run is the baseline. Openings are the difference from the next complete scan. You still get the branches whose pages were read, and the sitemap inventory is saved.

A store vanished from the sitemap this week. Why is there no removal? One miss is not enough. The next complete scan has to miss it again, and the location page has to be gone. A page that is still up is reported as unresolved, not closed.

Can two official URLs for the same new shop create two events? A second URL with the same normalized street, city, and postal code is the same address key. A suite-only difference at the same URL is saved and not billed.

What does confidence mean? 0.95 is a structured address plus coordinates plus a sitemap hit. A branch event is billed only when the address is structured and at least two of those signals agree. A removal is billed when two complete sitemap misses and a gone page agree.

The dataset has a summary and no events. Did it fail? No. A quiet week is a successful run. coverageStatus on the summary tells you whether each brand's inventory was complete.

Support

Questions and reproducible run IDs: plusonecares@gmail.com.