How it works
What the tool is for, why you cannot look it up yourself, and what arrives each morning. The mechanics come after that.
"Read data off the web" sounds simple. The hard part is not the reading. It is that the number you get back can be trusted.
In one sentence
Every morning, we tell you which company is selling your product, at what price, with the evidence attached.
Not "around $1,200." Rather: Retailer A at $1,180, Retailer B at $1,348, Retailer C at $1,095 — here are the pages, read at 06:00 this morning, at this exchange rate.
Why "around" is not enough
People ask us to find out what something costs. Most answers come back as a market average.
An average is a correct number. It just cannot be acted on.
- To negotiate with a supplier, you need who, and how much
- To decide whether to cut your own price, you need a real competitor you are losing to
- To decide whether to sell abroad, you need the price actually being charged there
"Around $1,200" answers none of the three.
Why you cannot just look it up yourself
You can. Once.
Two things get in the way.
1. 100 products × 5 sites, every morning, is not a job for a person
Five hundred pages opened, prices copied, compared against yesterday. It does not finish in a day. And it is worthless unless done daily — yesterday's price is gone forever if nobody wrote it down.
2. From your own country, you can only see your own country's prices
This is the part that is hardest to convey, so here it is slowly.
The same URL returns different content depending on where you opened it from. Different price. Different currency. Different stock status. Different ranking, shipping, and promotions.
This is not a trick. It is completely ordinary — showing Japanese visitors Japanese pricing is simply how a shop is built.
Which means: from a computer in Tokyo, the price your competitor shows a German customer is permanently invisible to you.
We open the page through a server in the country you specify, and can read the same page from several countries at once and set the results side by side.
"Couldn't I just ask ChatGPT?"
We get asked this a lot. We tried it, so here is what happened.
| Asked a conversational AI | "The two markets are at broadly similar levels" |
| Measured, same day, same item | The US market was 17% higher |
The interesting part comes next. Both numbers the AI used were real and correct. One was a buy-back price; the other a market index.
It had placed two fundamentally different quantities side by side and subtracted them, and nowhere in the answer was that distinction stated. That difference alone flips the sign of the gap.
This is not a knock on the model. None of the five gaps below close with better prompting.
- You get a market level, not a seller — a correct answer, but not one you can act on
- It sees only from where it stands — there is no prompt for "as seen from São Paulo"
- It does not say where the number came from — so you cannot catch the error
- It has no yesterday — nobody stored it, so it cannot be bought later either
- Ask twice, get two answers — last week's deck and this week's cannot be compared
This is a description of what an AI is for, not a criticism of it. Reading a specific page, from a specific country, at a specific time, and keeping the record — that is a different job.
What arrives each morning
Every row carries all of this.
| Seller | The merchant as shown on the page |
| Price (ex-tax) | $1,180 |
| Change | −$42 |
| vs. your price | +8.6% |
| Read from | Which country's server we went through |
| Page URL | The page actually read |
| Retrieved at | 2026-08-22T02:20:04.756Z |
| FX rate | The rate applied, and the date that rate was published |
These fields do not come apart. Not an add-on, not a higher tier — always all of them.
One reason: a price with no origin cannot be defended in a meeting. Report "the competitor is at $1,000", get asked "from where?", "as of when?", "tax in or out?", have no answer — and that document is never used again.
Four ways to receive it: the console (this morning's table), CSV, a morning email, and a webhook.
The dullest part, and the one that matters most
Say you read a competitor's site daily. One morning they redesign it and the read fails. What comes back is zero rows.
Written naively, that gets reported as: "the competitor sold out overnight."
In the data, "we failed to fetch" and "everything sold" are the same shape. And it is exactly the kind of number people act on.
What we do instead:
- We check first whether that source answered today at all. Listings from a source that did not answer are excluded from any sold-out judgement
- One disappearance is not a sale. Listings drop off a page because the ordering changed — that is a daily occurrence
- A refusal is recorded as a refusal. "Too many requests" never becomes an empty success
And any source we could not read is named — on screen, in the CSV, in the email. Drop it silently and a shorter list reads as a shrinking market.
From here on, it is the mechanics
If the above is enough, go to what people use it for or pricing.
1. Where you look from
A page changes shape depending on who is asking.
The same product page can show yen and in-stock to a visitor in Japan, and dollars and sold-out to one in the United States. Search rankings differ. So does the consent banner. So do the companies an AI assistant names.
So the request has to leave from an address in that country. You name the country per request, and that is where it is read from.
2. How we read — cheapest tier first
| Tier | What it does | Cost |
|---|---|---|
css | Reads the selectors you supply | none |
structured_data | Reads the JSON-LD and OpenGraph the site already publishes | none |
llm | Describes the page to a model | tokens |
A later tier fills only what earlier ones left empty, and if you do not ask for llm, no model is called. Most commerce pages are satisfied by the first two.
That is a cost argument and also a reproducibility argument: selectors and structured data return the same answer for the same page.
Lists come back as rows
We do not return one array per column. One card missing a price shortens that array alone, every later pair is off by one, and it does not raise an error — you get a table of real names against real prices belonging to different products. Nothing downstream can detect it.
With rows, a missing field is a null inside its own record and the rows either side stay aligned.
3. What comes back with it
Data alone cannot be defended in a meeting. Every row carries:
seller | the seller as published |
url | the page it was read from |
retrieved_at | when it was read |
fx_rate / fx_published_on | the rate used, and the date that rate was published |
price_ex_tax_jpy | price normalised to tax-exclusive |
level / attempts | which tier produced it, and every tier tried |
Tax normalisation happens once, at collection, and comparisons read only that column. Subtracting a tax-inclusive Japanese price from a tax-exclusive Hong Kong one once produced a report saying Hong Kong was 12.9% cheaper. The real gap was 4.3%, and some products were dearer in the other market — the sign was wrong, not just the size.
4. Stopping before publication
Those errors do not raise exceptions. They produce plausible numbers. So the assumption that a person will notice was abandoned and each became an executable check.
Thirty-one checks run before a report is generated, and it is not generated if one fails. When a check fails on correct data, the check is fixed rather than relaxed.
What we decline
No fingerprint spoofing. No CAPTCHA bypass. No authentication bypass. No exceeding a stated limit. No collection of personal data from the pages we read.
There is a commercial reason as well as a principled one: a source that refuses us once is a source nobody gets back, and the loss falls on every customer who needed it.
Last updated 2026-08-21
Last updated