Branch Opening & Relocation Signal Monitor
Pricing
from $200.00 / 1,000 branch events
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
11 days ago
Last modified
Categories
Security
Limited permissionsShare
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:
| Field | Role |
|---|---|
sitemapUrl | Location sitemap or sitemap index. This is the inventory. Additions and removals are judged from it. |
jsonUrl | Location JSON endpoint, used when you do not have a sitemap. |
locatorPageUrl | One page that publishes a single branch as JSON-LD or microdata. |
urlIncludeRegex / urlExcludeRegex | Optional 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. |
country | Used 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, orremoved.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.
- The first complete sitemap becomes the baseline. New URLs in that file are not called openings.
- 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_listedorcoming_soon. - If that new address is within the relocation radius of exactly one branch that has disappeared from the sitemap, the event is
likely_relocationinstead.confirmationstayspendinguntil a later scan sees the old page gone. That confirmation is not charged again. - A URL missing once is only pending. It is
removedonly on the next complete scan, and only if a re-fetch shows the page is gone. If the page is still live, the row isunresolved_identityand the branch is kept. - A suite change, or a coordinate wobble under 75 meters at the same street, updates the registry quietly.
- 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.
alertOnlyOnChangedrops 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.
| Event | When it is billed | Price |
|---|---|---|
location-page-checked | A 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-event | A 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:
| Run | What happened | Location checks | Branch events | Usage charge |
|---|---|---|---|---|
| Demo, first run | 3 chains, their sitemaps, 3 pages each. About 16 checks and 9 baseline rows. | 16 × $0.005 | 0 | $0.08 |
| Weekly watch | 40 chains, 2 sitemap files and 8 pages each, 12 real changes | 400 × $0.005 = $2.00 | 12 × $0.20 = $2.40 | $4.40 per run, about $18 across four weekly runs |
| Heavy month | 10,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. MapformattedAddress,locationUrl,eventState,reason, andconfidence. - Google Sheets: Apify's Google Sheets integration on the default dataset. The columns are already one cell each;
evidenceUrlsis a single text field separated by|. - Named registry: the store named in
stateStoreNameis the memory. The run'sOUTPUTrecord is the summary for that week.
Run it locally
Python 3.12.
pip install -r requirements-dev.txtpython -m srcpytest
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
unsupportedorunresolved. - 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_identityrather 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
partialand 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.