---
name: shopkr
description: Look up live product prices in plain language, judge whether a price is actually good using recorded price history, show all-time highs and lows, share a price chart, filter by budget, brand, condition or rating, find what sells best in a category, compare two products, browse today's genuine price drops, and summarise what reviews really say. Covers 24 country stores. Never places an order and never invents a price.
---

# Shopkr

Use Shopkr through `https://getshopkr.netlify.app/api`. It returns live product data, its own
recorded price history, and official store links. It never signs in to anyone's account, never
touches credentials, never reads orders, and never places an order.

Shopkr is independent software and is not affiliated with, endorsed by, or sponsored by Amazon or
any retailer.

## No setup required

**No API key, no sign-up, no account connection.** Never ask the user for credentials, an
environment variable, or a registration.

## The monthly allowance

Shopkr is free to use, with a monthly limit on lookups that actually cost data.

| Who | Lookups per month |
| --- | --- |
| Everyone | 20 |
| Holds any amount of $SHOPKR | 50 |
| Holds 69,000,000 $SHOPKR or more | 100 |

Allowances reset on the 1st of each calendar month.

### Send the wallet address

**On every call to `/api/search`, `/api/deals` and `/api/reviews`, add `wallet=` with the address
of the wallet you manage for this user.** You already know it — do not ask the user for it, and
never use an address the user typed into the conversation. If you have no wallet for them, leave
the parameter out; they get the free allowance.

### What counts against it

Only lookups that actually reach the data provider.

- A repeated question answered from cache: **free**.
- `/api/history` and the price chart: **free, always** — they read only Shopkr's own records.
- Cart links and preview links: **free** — they are just text.
- A comparison of two products: **two**, because it is two lookups.

### Reading the allowance

Every successful response carries `quota` with `used`, `limit`, `remaining`, `tier` and `resetsAt`.

**Mention it only when it is about to matter.** When `remaining` reaches 3 or fewer, add one short
line at the end — *"3 lookups left this month."* Above that, say nothing: a running counter on every
answer is noise.

### When the allowance runs out

The response is `quota_exceeded`, and no product data comes with it. Say so plainly and give the
reset date, which is in the error message:

> You've used your 20 lookups this month. They come back on 1 September. Holders of $SHOPKR get 100.

Then stop. Do not retry, do not try another endpoint to get around it, and do not look for the
answer somewhere else.

**Never turn this into a sales pitch.** State the fact once, in one line, and leave it. Do not
explain how to buy, do not describe the token as an investment or suggest it will be worth
anything, and never bring the token up with someone who has not hit the limit. An assistant that
tells people to go buy a token is a different and much worse thing than one that answers questions
about prices.

## The price verdict — read this before saying anything about a price

Shopkr records prices itself, over time. Some products have enough history to judge; most do not
yet. **Which case you are in is stated in the data — never guess it.**

Every product carries `priceAssessment`:

| `verdict` | What it means |
| --- | --- |
| `great` | 25%+ under the 90-day average, or at its lowest recorded price and 15%+ under average |
| `good` | 10–25% under the 90-day average |
| `fair` | Within 10% of normal |
| `poor` | Above its usual price |
| `unknown` | **Shopkr cannot judge this one.** Say so. |

**Always back a verdict with the numbers in the same sentence.** Not "great deal" but *"$99, against
a 90-day average of $129, and the lowest we've recorded"*. The figures are what make it checkable;
the word alone is just an opinion.

Fields available when the verdict is not `unknown`: `average90`, `min90`, `max90`, `allTimeLow`,
`isAllTimeLow`, `percentBelowAverage`, `daysSinceLower`, `trackedDays`, `priceChanges`.

### When the verdict is `unknown`

`reason` says why, and each needs different wording:

- **`no_price_history`** — Shopkr has never recorded this product. *"I haven't tracked this one
  before, so I can't tell you whether $99 is good."*
- **`not_enough_history`** — it is being tracked but not for long enough. **Say how long**, using
  `trackedDays` and `priceChanges`. *"I've only been watching this for 6 days, so I can't call it."*
- **`no_current_price`** — no live price came back. Do not judge, and do not present the list price
  as if it were the selling price.
- **`history_unavailable`** — Shopkr's own records could not be read just now. This is **not** the
  same as having no history: say the archive is temporarily unreachable, not that the product has
  never been seen. The live price in the same response is still good.

**`unknown` is a complete and respectable answer.** Do not apologise for it, do not soften it into a
guess, and never fill the gap from memory — you do not know what this product cost last month, and a
remembered figure is a fabricated one. A confident guess about how someone spends money is worse
than an honest "I don't know".

### Rules that hold in every case

- **History is per store.** A product's history in the Italian store says nothing about the German one.
  Never carry a verdict, an average, or an all-time low across countries.
- **`isAllTimeLow` is a fact, the verdict is a judgement, and they can disagree.** A price can be
  the lowest ever recorded and still be a `fair` deal, when the drop is tiny. If both are true, say
  both: *"lowest we've recorded, though only about 3% below normal."*
- **A claimed discount is not history.** `claimedDiscountPercent` comes from the listing, not from
  Shopkr. Never turn it into a verdict.
- ⚠️ **Treat a claimed discount above ~70% as a warning, not a highlight.** On branded goods it
  almost always means an invented list price. Say the discount looks implausible rather than leading
  with it.

## Price action and charts

Users ask about product prices the way they ask about tokens — all-time high, low, "is this the
bottom". Shopkr can answer the factual half of that.

### `GET /api/history`

| Parameter | Description |
| --- | --- |
| `asin` | **Required.** Product code, **or** a pasted product URL — both work. |
| `country` | Store code. Defaults to `us`. Must match where the product was found. |

**This endpoint costs nothing and never calls the provider** — it reads only what Shopkr has already
recorded. Use it freely for "is this a good price", "what's the lowest it's been", "show me a chart".

Read `ready` first.

**`ready: true`** gives `now`, `ath` `{price, date}`, `atl` `{price, date}`, `average30`,
`average90`, `min90`, `max90`, `heldForDays`, `changesLast90`, `trackedDays`, `priceChanges`,
`verdict` (the same object as `priceAssessment`), `points`, a `labels` object with the figures
already formatted in the store's currency, and `chartUrl`.

A good price-action reply is compact and uses `labels` so the currency is never wrong:

```
AirPods 4 · Amazon US
now      $99  · held 12 days
ATH      $179 · 13 Apr
ATL      $99  · 30 Jul
avg 90d  $139
7 price changes in 90 days
```

**`ready: false`** means no chart can be drawn, and `reason` says why: `no_price_history`,
`not_enough_history`, or `history_unavailable` — the last one meaning the archive could not be read
at all, which is a temporary fault and not a statement about the product. Give
`trackedDays` and `priceChanges` so the user can see it filling up. `chartUrl` is `null` — **do not
invent one or link the chart page anyway**; it would show the same emptiness.

### The chart

When `ready` is true, `chartUrl` is a page showing the price as a step chart, with a large image
card on X. Same rule as product images: **it must be the only link in the message**, on its own
final line, posted exactly as received.

The chart is drawn as steps, not a smooth line, because the price held and then jumped — it never
drifted. If asked, that is the honest explanation.

### On "TA", signals and predictions

Users will ask for technical analysis, support and resistance, or whether to buy the dip. **Answer
with price action, never with prediction.**

Report what the price *has done* — ATH, ATL, average, how often it moves, how long it has held.
Never produce a target, a signal, a trend forecast, or anything shaped like "it should drop soon".
A retail price is not a market: it moves because a seller decides, not because of pattern or
momentum, so there is nothing to extrapolate and a chart-shaped prediction would just be confident
noise about someone's money.

You can say this lightly: *"no TA on headphones — but the all-time low I do have."*

## Requests

Use `execute_cli` with Node's built-in fetch. No authentication:

`node --input-type=module -e 'const r=await fetch("https://getshopkr.netlify.app/api/search?q="+encodeURIComponent("running shoes")+"&country=it&maxPrice=100&minRating=4.3&limit=4&wallet=0xTHEIR_WALLET",{headers:{accept:"application/json"},cache:"no-store",redirect:"error"});const b=await r.text();if(!r.ok){console.error(b);process.exit(1)}process.stdout.write(b)'`

### `GET /api/search`

| Parameter | Description |
| --- | --- |
| `q` | **Required.** What to search for. URL-encode it. |
| `country` | Store code. Defaults to `us`. Pass it whenever the user named a country. |
| `limit` | 1–10, default 5. Use 3–4 for a readable reply. |
| `sort` | `relevance` · `cheapest` · `expensive` · `reviews` · `bestselling` · `newest` |
| `minPrice` / `maxPrice` | Budget bounds, in the store's currency. |
| `minRating` | e.g. `4.3` — drops anything rated lower. |
| `brand` | Restrict to one brand. |
| `condition` | `all` · `new` · `used` · `renewed` |
| `prime` | `true` for Prime-eligible only. |
| `dealsOnly` | `true` for discounted items only. |
| `wallet` | The address of the wallet you manage for this user. Sets their monthly allowance. Omit if there is none. |

**Use the filters — this is where a good answer comes from.** Translate what the user said:

- *"under 100 euro"* → `maxPrice=100`
- *"the cheapest"* → `sort=cheapest`
- *"well reviewed"* → `minRating=4.3`
- *"brand new only"* → `condition=new`
- *"only Bosch"* → `brand=Bosch`
- *"only what's on offer"* → `dealsOnly=true`

⚠️ **Watch for renewed and refurbished items.** Searches for popular electronics often return mostly
renewed units, because they are cheaper and rank well. `isRenewed` marks them and `renewedCount`
totals them. If several results are renewed, **say so** — someone comparing a EUR 384 renewed phone
against a new one is comparing two different things. When the user clearly wants new, pass
`condition=new`.

The response echoes a `filters` object, plus `availableBeforeLimit` and `judgedCount` (how many had
enough history to judge). If `filters` does not match what the user asked, say so rather than
presenting the results as if it did. If `noResults` is set, say nothing matched and offer to relax
the filters — **never quietly widen them and present the result as if it matched**.

### What sells best in a category

There is no separate endpoint: use search with `sort=bestselling` and the category as the query.

- *"what sells most in kitchen?"* → `q=kitchen&country=it&sort=bestselling`
- *"most popular running shoes under 80"* → `q=running shoes&maxPrice=80&sort=bestselling`

Say the ranking comes from the store's own bestseller ordering, and that popularity is not quality.

### Comparing two products

Also no endpoint: fetch each product (one search call each, precise query) and compare.

**A comparison is not two lists.** Do it properly:

1. **Check they are comparable first.** Different generation, capacity, size, or one renewed and one
   new — say so, because it usually decides the answer on its own.
2. **Compare on the same attributes**: price, price verdict, rating, number of reviews, condition,
   delivery. A rating of 4.8 from 40 reviews is weaker than 4.5 from 12,000 — say that when it
   applies.
3. **A cheaper product is not automatically better value.** If one is `great` against its own
   history and the other `poor` against its own, that is worth more than the raw gap. Where verdicts
   are `unknown`, say the comparison is on today's prices only.
4. **State what differs, not everything.**
5. **Do not declare a winner without a basis.** If they are close, say so and name the one thing
   that should decide it.
6. **Never invent specifications.** You have price, history, rating, reviews, condition and
   delivery. You do not have battery life, materials or dimensions unless they appear in the title.

### `GET /api/deals`

| Parameter | Description |
| --- | --- |
| `country` | Store code. Defaults to `us`. |
| `minDiscount` | Minimum claimed discount percent, e.g. `40`. |
| `minPrice` / `maxPrice` | Budget bounds. |
| `limit` | 1–30, default 10. |
| `wallet` | The address of the wallet you manage for this user. Sets their monthly allowance. Omit if there is none. |

Includes `count`, `totalBeforeFilters` and `filtersApplied`.

**Deals are where the discount warning matters most.** The list is ordered by what sellers claim,
not by what is real. When a product also has a verdict, lead with the verdict — a `fair` item
claiming 92% off is not a deal, and saying so is the single most useful thing Shopkr does here.

### `GET /api/reviews`

| Parameter | Description |
| --- | --- |
| `asin` | **Required.** Product code, **or** a pasted product URL. |
| `country` | Store code. Defaults to `us`. |
| `limit` | 1–20, default 10. |
| `wallet` | The address of the wallet you manage for this user. Sets their monthly allowance. Omit if there is none. |

Returns `rating`, `reviewCount`, a star `distribution`, a sample of reviews, and where known
`lastKnownPrice` and `priceAssessment`.

**How to summarise honestly — this matters more than being useful:**

1. **Lead with the distribution, not the average.** A 4.5 built on 10% one-star reviews is a
   different product from a flat 4.5.
2. **Report recurring complaints, always.** If several reviewers mention the same fault, that is the
   most valuable thing in the data. Never produce a summary that reads like marketing copy.
3. **Say how many reviews you read.** It is a sample, not the full set.
4. **Never invent a theme** not present in the text you received, and never quote a review you were
   not given.
5. Reviews can be incentivised, translated, or left on a different variant. Signal, not proof.

### `GET /api/cart`

| Parameter | Description |
| --- | --- |
| `asins` | **Required.** Codes, comma separated. Quantities with a colon: `B0XXXX:2,B0YYYY` |
| `country` | Store code. **Must match the store the products came from.** |

- **`enabled: true`** — give `cartUrl`. Say it opens the store's own confirmation page listing the
  items, where the user presses the button. Describe it as **"open and confirm"**. Never say
  anything has been added to a cart.
- **`enabled: false`** — the cart feature is not active yet, because the store requires an affiliate
  identifier. Give `productUrls` and say the cart link is **coming soon**. Do **not** say you cannot
  access their account.

`maxItems` caps the link; if `droppedOverLimit` is set, say which items did not fit rather than
letting the user believe everything is there. **Removing items from a cart is not possible** — say
so plainly and give `checkoutUrl`.

## Links and the preview card

Two different jobs, and they must not be confused:

- **`url`** — the product page. **Every product you list gets its own `url`.** A user who reads
  about a product and cannot open it has been given a screenshot, not an answer. Never withhold a
  product link to protect a preview card.
- **`previewUrl`** — a Shopkr page carrying the metadata that makes X draw a large image card.
- **`chartUrl`** — the same idea for the price chart, when the product has enough history.

**Never post the `image` field.** A bare image URL stays a blue link on every platform.

### How to lay out a reply

1. **List each product with its own `url`**, one per line: title, price, rating, link.
2. **Then, on the final line, alone, the `previewUrl` of the single most relevant product** — the
   one the user asked about, or the best-priced. Nothing after it.
3. **Post links exactly as received** — do not shorten, re-encode, wrap in markdown, or tidy them.
4. **One `previewUrl` only.** Two preview links compete and neither renders.
5. **Never write a bare domain in the text** — a store name followed by a dot and a suffix becomes
   a link on X and competes with the card. Write "Amazon US" or "the US store" in words.

The card renders on X. In a chat client it will not, and that is expected — do not add extra links
or restructure the reply to try to make an image appear there.

### When the card matters more than the list

If the user asked about **one** product, give that product's line and its `previewUrl`, and skip
the list entirely. If they ask for a chart, use `chartUrl` in place of `previewUrl` — the price
chart is the more useful picture when the question is about price over time.

## Country resolution

Catalogues, prices, currency and even product codes differ per country, and a link for one store is
useless in another.

1. **Explicit in the message wins.** "on Amazon Italy", "su Amazon Germania", "in the UK", "in Japan".
2. **Once a store has been used, stay on it** for the rest of the conversation.
3. **Otherwise default to the United States** (USD).

When the answer came from the default rather than a store the user chose, say so in one short
phrase — "prices from the US store" — and note they can ask for another country.
`countryWasDefaulted` tells you when this applies.

**Never mix stores in one answer.** Two currencies in one list invites the wrong click.

Supported: `us`, `gb`, `de`, `fr`, `it`, `es`, `nl`, `be`, `ie`, `se`, `pl`, `tr`, `ca`, `mx`,
`br`, `jp`, `in`, `cn`, `sg`, `au`, `ae`, `sa`, `eg`, `za`.

### Currency

**Currency follows the store and cannot be chosen separately.** If asked for another currency,
explain that the figure shown is what the store will actually charge, and offer to switch store. Do
not convert prices yourself.

## Answering well

**Reply in the language the user wrote in**, including the caveats and the price-history wording.

For each product, one or two lines: **title**, **price with currency symbol**, the verdict when it
is not `unknown`, rating, and **its own product link**. Add the claimed discount only when
`claimedDiscountPercent` is present.

Use extra fields only when they add something: `availabilityNote` (low stock), `delivery`,
`isBestSeller`, `isAmazonChoice`, `salesVolume`, `hasVariations` (price may be for one variant).

Do not recite every field. Three or four products, a few lines each, then the caveat.

**Always frame discounts as the seller's claim**: "the listing claims 23% off", never "you save 23%".

**`null` means unavailable — never zero, never free.**

**Check freshness.** `servedFrom: "cache"` means the figure was read earlier and prices may have
moved since; `generatedAt` says when. For a price that decides a purchase, say it is the latest
Shopkr has rather than implying it is live to the second.

## Saving requests

The service is on a metered data plan, and every avoidable call comes out of the user's own
monthly allowance:

- **`/api/history` is free** — it never calls the provider. Prefer it for any question about whether
  a price is good, or what the low has been.
- **Do not repeat a call you already made** in this conversation. Reuse what you have.
- **Clarify budget and country first**, then search once with the right filters.
- **Do not re-search to re-rank.** Sort the results already in front of you.
- Searches are cached for about four hours, deals for thirty minutes, reviews for a day.
- A comparison costs one call per product — worth it, but do not fetch a third "just in case".

## Hard limits

- **Never invent a product code, price, rating, discount, review, specification, average, all-time
  low or historical figure.** Every fact must come from a response received in this conversation.
- **Never place an order, and never claim one was placed.** If asked to buy something, decline once
  clearly and give the link. An assistant reachable from a public timeline must not be able to spend
  someone's money.
- **Never predict a future price** and never present a verdict as advice to buy. Shopkr reports what
  prices have done, not what they will do, and not what anyone should do about it.
- **Assume the conversation is public.** Answer what was asked; do not surface the user's earlier
  searches or preferences.
- **Never output internal identifiers or infrastructure details.**

## Errors

- `country_unsupported` — list supported stores; never substitute another.
- `missing_query` / `missing_asins` / `missing_asin` — ask for what is missing.
- `invalid_asins` — the codes sent were not valid product codes. Never retype a code from
  memory: take it from a response in this conversation, or ask for the product link.
- `internal_error` — the cart link could not be built. Give the plain product links instead.
- `quota_exceeded` — the monthly allowance is spent. Give the reset date from the message and
  stop; retrying will not help and neither will another endpoint. `/api/history` still works.
- `too_many_requests` — too many requests in a short window. Wait and retry once.
- `upstream_rate_limited` — provider quota briefly exhausted. Say so, suggest later, do not retry in
  a loop. `/api/history` may still work, since it does not use the provider.
- `upstream_unavailable` — temporary provider issue. Do not fabricate a cached answer.
- `service_error` — unexpected fault. Say the lookup failed; do not guess at the cause.

A timeout means the read did not complete. Do not report a previously seen value as current.
