Unofficial, agent-friendly CLI for tienda.mercadona.es — search the catalog, read prices, build a cart, and check out. Single static Go binary, no runtime deps, structured --json output for programmatic/agent use. Unofficial. Mercadona has no public API.
$ npx -y skills add ivorpad/mercadona-cli --agent claude-code
Repo: ivorpad/mercadona-cli
What's inside
Unofficial, agent-friendly CLI for tienda.mercadona.es — search the catalog, read
prices, build a cart, and check out. Single static Go binary, no runtime
deps, structured --json output for programmatic/agent use.
Unofficial. Mercadona has no public API. Bring your own credentials; use at a sane request rate. This talks to the same HTTP endpoints the website does.
npm — downloads the prebuilt binary for your platform on install:
npm install -g @ivorpad/mercadona # puts `mercadona` on your PATH
npx @ivorpad/mercadona search queso # …or run without installing
curl (macOS / Linux):
curl -fsSL https://raw.githubusercontent.com/ivorpad/mercadona-cli/main/install.sh | sh
Override with MERCADONA_VERSION=v0.1.0 (pin a tag) or MERCADONA_INSTALL_DIR=/path
(install location; defaults to /usr/local/bin, else ~/.local/bin).
Manual — download a tarball for your OS/arch from the
releases page, extract, and put
mercadona on your PATH.
From source (Go 1.26+) — clone, then:
go build -o mercadona ./cmd/mercadona
(go install <module>@latest isn't wired up yet: the module path is
github.com/ivorjpc/mercadona, which doesn't match the repo URL.)
mercadona search queso # full-text product search
mercadona search --limit 5 --json mayonesa # structured output for agents
mercadona batch -f lista.txt # many terms in ONE request (≈100 items / call)
printf 'queso\ncarne\nmayonesa\n' | mercadona batch -f -
mercadona product 13406 # detail, price, nutrition (when available)
mercadona categories # category tree
mercadona categories --id 112 --json # one category's products (raw JSON)
Common flags: --wh mad1 (warehouse), --lang es, --json — and they can go anywhere after the (sub)command, not just up front.
Data goes to stdout, logs/errors to stderr, exit code 1 on error — friendly to scripts and agents.
mercadona set-postal 28022 # → resolves to warehouse mad1, saves it as the default
Product ids and prices are per-warehouse, and online checkout needs the cart's warehouse to
match your delivery address — so pin it to the warehouse that serves your postal code (no login
needed). Precedence: --wh flag > config.toml [defaults] > built-in mad1. import-har also
auto-detects and saves the warehouse from your session. (Within a city it varies: 28022 → mad1,
28013 → mad3.)
Example:
$ mercadona batch -f lista.txt
• queso → [51110] Queso rallado mozzarella pizza-Roma Hacendado — 1.60€ (8.000€/kg)
• carne → [34157] Carne de pimiento choricero Hacendado — 1.55€ (11.072€/kg)
• mayonesa → [13406] Mayonesa Hacendado — 1.20€ (2.400€/L)
import-har (preferred) / import-curl / login, whoami, cart, checkoutThe API authenticates with a Bearer token (a SimpleJWT). The first sign-in must happen in a
browser (password login needs a Google reCAPTCHA Enterprise token; Google-account users have no
password at all). After that, the refresh token renews the session headlessly, forever —
POST /api/auth/tokens/ {refresh_token} needs no captcha and rotates the token. Verified.
Two login methods, one outcome. However you sign in, the response carries the same durable
refresh_token, so the CLI automates identically:
| Method | Endpoint | Request body | Response |
|---|---|---|---|
| Email + password | POST /api/auth/tokens/ | {username, password, recaptcha_token} | {access_token, customer_id, refresh_token} |
| Google sign-in | POST /api/auth/social/google/ | {id_token, postal_code} | {access_token, customer_uuid, refresh_token} |
✅ Preferred login method — import-har. One browser login (email or Google), then
headless forever. Export a HAR after signing in and let the CLI pull the refresh token out for you:
# DevTools → Network → ⤓ "Export HAR…" (after you've logged in, by either method), then:
mercadona import-har --file tienda.mercadona.es.har
mercadona whoami # confirms it's authenticated
import-har seeds refresh_token into ~/.mercadona/config.toml (0600) and caches the current
access token + cookie. From then on every 401 token_not_valid triggers an automatic refresh +
retry — no browser, no captcha, unattended. (It reads only auth responses and Bearer/Cookie
headers; the password in the request body is never touched.)
Prefer to do it by hand? Write the token yourself — mercadona set-refresh <token> (or edit
~/.mercadona/config.toml):
[auth]
refresh_token = "<your refresh token>" # the durable, headless-renewable credential
[defaults]
warehouse = "mad1" # or: `mercadona set-postal 28022` resolves + writes this for you
postal_code = "28022"
MERCADONA_TOKEN/MERCADONA_COOKIE/MERCADONA_CUSTOMER (and MERCADONA_USER/MERCADONA_PASS) env vars also work for one-off runs.
Quick one-off (no refresh): mercadona import-curl --file s.txt from a DevTools "Copy as
cURL" of any …/api/customers/… request extracts the Bearer token + cookie + customer id. It has
no refresh token, so it can't auto-renew — re-import when the access token expires.
mercadona login --user … --password …exists but will fail without arecaptcha_token(browser-only), and does nothing for Google accounts; prefer the HAR/refresh-token flow above.
The customer id is read automatically from the token's customer_uuid claim, so
you never pass it (the literal me alias is rejected with 403). Token/cookie/
customer can also come from MERCADONA_TOKEN / MERCADONA_COOKIE /
MERCADONA_CUSTOMER. Secrets are read from env/files, never taken as flags.
mercadona import-har --file tienda.mercadona.es.har # auth (preferred; or import-curl)
mercadona whoami # → "ok — customer id=…" (confirms auth)
mercadona cart get --json # inspect current cart (names, qty × unit_price, total)
mercadona cart add 51110 2 --max 80 # add 2× a product (capped at 80 €)
printf '51110 2\n13406 1\n' | mercadona cart set-many -f - --max 80 # many '<id> <qty>' in ONE write (0 removes)
mercadona cart clear # empty the cart in one write
mercadona checkout create --json # open a checkout → id + default address
mercadona checkout addresses # list saved delivery addresses
mercadona checkout slots --address <id> # delivery slots (they hang off the address, not the checkout)
mercadona checkout get --checkout <id> # show a checkout: total, address, slot
mercadona checkout set-delivery --checkout <id> --address <id> --slot <id>
mercadona checkout submit --checkout <id> --max 80 --yes # IRREVERSIBLE — places the order
cart add adds to the existing quantity; cart set sets the absolute quantity (0 removes). For a
whole basket, cart set-many -f - applies many <id> <qty> lines in a single write — and prices
it first, so --max refuses before writing — while cart clear empties it. All accept --max.
The access token (a SimpleJWT) lasts ~6 weeks; when whoami starts returning
401 token_not_valid, re-import a fresh Copy as cURL (or use login).
When an agent drives the CLI, cap how much it can ever spend. Any cart/checkout over the cap is
refused with a non-zero exit and an error: line — so the agent stops instead of running up a huge
order. Pass it as a flag (which can go anywhere on the line):
mercadona cart add 10379 99 --max 50 # → error: BUDGET EXCEEDED … refusing (exit 1)
mercadona checkout submit --checkout <id> --max 80 --yes # submits only if total ≤ 80 €
Or set it once so every command is capped — MERCADONA_MAX_EUR=100 (env), or in config:
# ~/.mercadona/config.toml
[limits]
max_eur = 100 # refuse any cart/checkout whose total exceeds 100 €
Precedence is flag > env > config; 0/unset = no limit. Enforced on cart add/set/set-many,
checkout create, checkout set-delivery, and — critically — checkout submit, which fails
closed: with a cap set, if it can't read the order total it refuses rather than spend blind.
(With no cap, submit prints a warning.)
The interesting part isn't "an AI does your shopping." It's that one person can now do things that used to need a developer or an analyst: track your own inflation, rank a category by €/kg, catch genuine price drops, build an allergen-safe basket. Every output below is live CLI — and since reads need no login, most are copy-paste.
You think in names; the cart API thinks in ids. batch bridges them in one request — the top
hit per term, with its price:
$ printf 'arroz redondo hacendado\ngambón grande congelado\nmejillón mediterráneo\ntomate triturado hacendado\naceite oliva virgen extra hacendado\n' | mercadona batch -f -
• arroz redondo hacendado → [5044] Arroz redondo Hacendado — 1.20€ (1.200€/kg)
• gambón grande congelado → [60393] Gambón grande congelado — 6.00€ (12.000€/kg)
• mejillón mediterráneo → [85499] Mejillón mediterráneo — 5.80€ (5.800€/kg)
• tomate triturado hacendado → [16044] Tomate triturado Hacendado — 0.55€ (1.375€/kg)
• aceite oliva virgen extra hacendado → [4740] Aceite de oliva virgen extra Hacendado — 4.95€ (4.950€/L)
Put your picks in a basket file — it takes inline # comments, so it reads like the list you
started with, not a wall of ids (paella.txt):
# paella base — 3 personas
5044 1 # Arroz redondo Hacendado
60393 1 # Gambón grande congelado
85499 1 # Mejillón mediterráneo
16044 1 # Tomate triturado Hacendado
4740 0.5 # Aceite de oliva virgen extra
Then price it — total sums unit_price × qty in integer cents (exact; fractional quantities
work for weight items):
$ mercadona total -f paella.txt
[5044] Arroz redondo Hacendado — 1 × 1.20€ = 1.20€
[60393] Gambón grande congelado — 1 × 6.00€ = 6.00€
[85499] Mejillón mediterráneo — 1 × 5.80€ = 5.80€
[16044] Tomate triturado Hacendado — 1 × 0.55€ = 0.55€
[4740] Aceite de oliva virgen extra Hacendado — 0.5 × 4.95€ = 2.48€
total: 16.03€ (5 líneas)
→ 16.03 € for the basket; a paella base for 3 ≈ 5.34 €/serving. (The same # comment
basket feeds cart set-many to fill the cart in one write; add --json for {lines, total, count, complete}.)
A bare term often top-ranks the frozen or canned version. --fresh drops the Congelados + Conservas
aisles, so the fresh product surfaces:
$ mercadona search mejillon --limit 1
[18615] Mejillones de Chile en escabeche Hacendado pequeños — 2.65€ (Conservas, caldos y cremas)
$ mercadona search mejillon --fresh --limit 1
[85499] Mejillón mediterráneo — 5.80€ (Marisco y pescado)
reference_price is the unit-normalised price (€/kg, €/L) on every product. Pull a whole category and
rank by it to surface the genuine value buys:
$ mercadona categories --id 118 --json # 118 = Arroz
| id | product | price | per kilo |
|---|---|---|---|
5044 | Arroz redondo Hacendado | 1.20€ | 1.200 €/kg |
5063 | Arroz largo Hacendado | 1.20€ | 1.200 €/kg |
5020 | Arroz vaporizado Hacendado | 1.55€ | 1.550 €/kg |
5042 | Arroz redondo J Sendra Hacendado | 1.60€ | 1.600 €/kg |
5184 | Arroz integral largo Hacendado | 1.65€ | 1.650 €/kg |
FAQ
mercadona-cli is a Claude Code plugin with 1 hand-picked skill for automation work, indexed on Flowy. Install it with the command on its page. It includes mercadona-shop. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.
Is this plugin yours?
Claim it with GitHubSubmit a pluginPromote it