vitalityfit
v1.2.0
Published
Compare prices of health products (medicines, supplements, cosmetics) from German online pharmacies and drugstores by name, PZN or EAN barcode: free price API client and CLI with cheapest offers, shipping costs, stock and 90-day low, no API key
Maintainers
Readme
vitalityfit
Compare prices of health products (medicines, supplements, cosmetics) from German online pharmacies and drugstores. Look a product up by name, PZN (the German pharmacy product number) or EAN barcode and get the cheapest current offers: shop, price, shipping cost, stock, delivery time, unit price and the lowest price of the last 90 days.
A small client and command line tool for the free Vitalityfit Price API: no API key, no login, no dependencies.
Deutsch: Preisvergleich für Apotheken- und Drogerieprodukte aus deutschen Online-Shops und Versandapotheken, per Name, PZN oder EAN. Daten von vitality-fit.de.
Command line
npx vitalityfit ibuprofen
npx vitalityfit 01568889 --pack "30 st"
npx vitalityfit "orthomol immun" --jsonThe command searches for the product, then lists the cheapest offers for the best match: shop, price, shipping cost,
stock, delivery time, unit price and shop link, plus the lowest price of the last 90 days. A PZN or EAN shows the offers
for exactly that pack. Affiliate links are marked (affiliate). The exit code is 1 when there is nothing to show (no
product, pack size not found, no current offers) or on an error, and 2 on a usage error.
| Option | Meaning |
|---|---|
| -p, --pack <size> | Pack size, e.g. "20 st", or a pzn_/ean_ key (default: the pack size with the most offers) |
| -l, --limit <n> | Number of products to search, 1-10 (default 5) |
| --json | Print the API answers as JSON |
Library
Node 18 or later, browsers, Deno and Bun (ES module, uses the global fetch). From CommonJS, load it with
await import('vitalityfit').
npm install vitalityfitimport { searchProducts, listOffers } from 'vitalityfit';
const { products } = await searchProducts('ibuprofen 400', { limit: 3 });
const best = products[0];
console.log(best.title, best.cheapest_price, best.pack_sizes.map((p) => p.label));
const result = await listOffers(best.product_key, { packSize: '20 st' });
for (const offer of result.offers) {
console.log(offer.shop, offer.price, offer.shipping_cost, offer.link);
}
// Show these with the offers:
console.log(result.compliance_notice ?? '', result.disclosure);searchProducts(query, { limit }): up to 10 products (default 5), best match first. Each hasproduct_key,title,brand,pack_sizes,cheapest_price(EUR),shop_count,url,image_url(when the product has an image) and, for a PZN or EAN,matched_pack(the cheapest offer of the pack that holds that PZN/EAN). For medicines alsocompliance_notice.listOffers(productKey, { packSize }): up to 10 offers, cheapest first (each withshop_logo_urlwhen the shop has a logo), for one pack size (default: the pack size with the most offers).packSizetakes a pack'sidentity_keywhen it has one, else its label; a pack is one pack-size label as on the product page and can hold several PZNs (an original and its reimports). The answer also hasdisclosure,low_90d,data_date,image_url(when the product has an image) and, for medicines,compliance_notice. An unknown pack size givesmessage: "pack size not found"withavailable_packs.- Both take
{ signal, fetch, baseUrl }as further options. TypeScript types are included.
Errors are thrown as VitalityfitError with status, code and, when rate-limited, retryAfter in seconds. Codes
from the API: invalid_argument, not_found, rate_limited, internal, unavailable; from the client: http_error
(an answer that is not the API's JSON), network (no answer) and no_fetch. A not_found (HTTP 410) for a product
key means the product was merged or delisted: search again by name, PZN or EAN for its current key. A 429 without
retryAfter means the service is busy: retry after a few seconds. Aborts and timeouts from signal are passed through.
Limits
- One request per 10 seconds per IP address on average (12 in 2 minutes); above that the API answers
429withRetry-After. The command line tool uses two requests per search. - Answers are cached for up to 1 hour.
Terms in short
- Free for personal and non-commercial use, including free apps. Commercial use only by agreement: [email protected]
- Name the source "Preise: vitality-fit.de" with a link to https://vitality-fit.de/ and keep shop links unchanged.
- For medicines, show the
compliance_noticetogether with the results. - No bulk download, mirroring or resale of the data.
- Prices only, no medical advice. Prices may have changed since; the price in the shop applies. Shop links can be affiliate links: vitality-fit.de may earn a commission, and the price does not change for the buyer.
Full terms: vitality-fit.de/api (the German version is binding).
For AI assistants
The same data is available as an MCP server at https://vitality-fit.de/mcp (streamable HTTP, no login), listed in
the official MCP Registry as de.vitality-fit/prices. Setup for Claude, ChatGPT and other clients:
vitality-fit.de/mcp.
License
The code is MIT licensed. The price data is subject to the API terms above.
