Penny APIPenny.de ↗
Access Penny.de weekly promotional offers and store locations via two endpoints. Filter by selling region, category, and week. Returns prices, discounts, and store geodata.
What is the Penny API?
The Penny.de API exposes 2 endpoints covering Germany's PENNY supermarket chain: current and upcoming weekly promotional offers (Angebote) with shelf prices, UVP comparisons, discount percentages, and app-exclusive pricing — plus a store directory endpoint (search_markets) that finds PENNY locations by postal code or city and returns the selling region codes needed to pull region-specific offers.
curl -X GET 'https://api.parse.bot/scraper/fc539534-08a8-4ad5-97a7-ad870ad270c1/list_weekly_offers?week=current&category=top-angebote' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace penny-de-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""Walkthrough: PENNY weekly offers – find a store, then browse its regional deals."""
from parse_apis.penny_de_api import Penny, Week, InputNotFound
client = Penny()
# Find the nearest PENNY store by postal code.
market = client.markets.search(zip_code="10115", limit=5).first()
if market is not None:
print(market.name, market.street, market.city)
print("Opening hours:", market.opening_hours)
print("Selling region:", market.selling_region)
# Fetch this week's offers for that store's selling region.
week = client.offer_weeks.get(
week=Week.CURRENT,
selling_region=market.selling_region,
)
print(f"\nWeek {week.week_id} — {week.total_offers} offers")
# Walk the offer categories returned alongside the offers.
if week.categories:
for cat in week.categories:
print(f" {cat.title} ({cat.offer_count} offers, {cat.period})")
# Show some offers with prices.
if week.offers:
for offer in week.offers[:5]:
savings = f" (was {offer.regular_price})" if offer.regular_price else ""
print(f" {offer.title}: €{offer.price}{savings} — {offer.quantity}")
# Point lookup that may fail: a non-existent category.
try:
client.offer_weeks.get(category="does-not-exist")
except InputNotFound:
print("\nCategory not found — handled gracefully.")
print("\nexercised: markets.search / offer_weeks.get / InputNotFound")
Returns the weekly promotional offers published on penny.de for the current or the next week. One row per offer tile, carrying the shelf price, the regular/UVP price it is compared against, the discount percentage, the PENNY-app-only price where one exists, pack size, unit price text, offer category and validity period (e.g. Mo-Sa, Do-Sa, Fr-Sa). Without a category the endpoint walks every offer category of the week (about 25 upstream requests, roughly 200 offers); with a category it is a single cheap call. Category slugs and their human titles are listed in the categories array of a full-week call; the set changes from week to week, so an unknown slug returns a stale_input (input_not_found) error. Passing a selling_region (from search_markets) adds the region-specific offers of that store region on top of the nationwide ones. The next week is only published by the site from Friday onward; before that week=next returns week_available=false with empty arrays (the data branch for week=next was not observed during the build). Prices are EUR as numbers; unit-price texts (base_price, app_base_price) are kept as the site prints them. lowest_price_30_days was null on every offer observed.
| Param | Type | Description |
|---|---|---|
| week | string | Which offer week to read. |
| category | string | Offer category slug as emitted in categories[*].category (one shape: top-angebote). Omitted = all categories of the week. |
| selling_region | string | Selling region code of a store, from search_markets.markets[*].selling_region. Omitted = nationwide offers only. |
{
"type": "object",
"fields": {
"week": "requested week selector (current|next)",
"offers": "array of offers: offer_id, title, slug, category, period, quantity, price, regular_price, regular_price_type (uvp|previous|striketrough|null), discount_percent, base_price text, app_price, app_discount_percent, app_base_price text, action_price, bargain_price, lowest_price_30_days, promotion, product_type, tile_type, image_url, url",
"week_id": "site's ISO week id of that week, e.g. 2026-41",
"categories": "array of offer categories fetched: category slug, title, product_type (food|nonfood|null), period (validity days label), offer_count",
"total_offers": "number of offers returned",
"selling_region": "echo of the region filter or null",
"week_available": "false when the site has not published that week's categories yet",
"failed_categories": "array of categories whose upstream call failed, with status_code"
},
"sample": {
"data": {
"week": "current",
"offers": [
{
"url": "https://www.penny.de/angebote/top-angebote/red-bull-energy-drink",
"slug": "red-bull-energy-drink",
"price": 0.99,
"title": "RED BULL Energy-Drink*",
"period": "Mo-Sa",
"category": "top-angebote",
"offer_id": "322b3a83-659a-4156-8b7b-83ada0379292",
"quantity": "je 250 ml",
"app_price": 0.85,
"image_url": "https://cdn.penny.de/dam/jcr:a06ab81f-3df6-44c4-a8a5-8c07a4f3bc00/55748200.png?impolicy=penny&imwidth=600",
"promotion": "reduzierter Preis",
"tile_type": "promoPlus",
"base_price": "(1 l = 3.96)",
"action_price": false,
"product_type": "food",
"bargain_price": false,
"regular_price": 1.49,
"app_base_price": "(1 l = 3.40)",
"discount_percent": 33,
"regular_price_type": "uvp",
"app_discount_percent": 42,
"lowest_price_30_days": null
},
{
"url": "https://www.penny.de/angebote/top-angebote/helle-kernlose-tafeltrauben",
"slug": "helle-kernlose-tafeltrauben",
"price": 2.99,
"title": "Helle, kernlose Tafeltrauben*",
"period": "Mo-Sa",
"category": "top-angebote",
"offer_id": "ac7ffe81-dd23-4d2d-b54b-992db2cbea60",
"quantity": "je kg",
"app_price": null,
"image_url": "https://cdn.penny.de/dam/jcr:013d9158-26c7-48a0-ad96-368e6bdae41d/45086880.png?impolicy=penny&imwidth=600",
"promotion": "normaler Preis",
"tile_type": "offer",
"base_price": null,
"action_price": true,
"product_type": "food",
"bargain_price": false,
"regular_price": null,
"app_base_price": null,
"discount_percent": null,
"regular_price_type": null,
"app_discount_percent": null,
"lowest_price_30_days": null
}
],
"week_id": "2026-41",
"categories": [
{
"title": "Top Angebote",
"period": "Mo-Sa",
"category": "top-angebote",
"offer_count": 6,
"product_type": "food"
}
],
"total_offers": 6,
"selling_region": null,
"week_available": true,
"failed_categories": []
},
"status": "success"
}
}About the Penny API
Weekly Promotional Offers
The list_weekly_offers endpoint returns all offer tiles published on penny.de for either the current or the next week. Each offer object includes offer_id, title, slug, category, price, regular_price, regular_price_type (distinguishing UVP, previous price, or strikethrough), quantity, and a discount_percentage. Where PENNY publishes an app-exclusive price for an item, that field is present in the offer object. The response also surfaces week_id (an ISO week string such as 2026-41), a categories array with per-category product_type (food, nonfood, or null) and offer_count, and a failed_categories list so callers can detect partial responses.
Filtering Offers by Region and Category
By default, list_weekly_offers returns nationwide offers only. Pass a selling_region code (obtained from search_markets) to include region-specific promotions. Pass a category slug — matching values emitted in categories[*].category, for example top-angebote — to narrow the result set. If the site has not published the requested week yet, week_available is returned as false rather than an error, so callers can check before rendering.
Store Search and Selling Regions
The search_markets endpoint accepts a zip_code (3–5 leading digits, with leading zeros preserved) and/or a city substring. It returns up to 200 matching stores, each with market_id, name, street, zip_code, city, state, latitude, longitude, selling_region, next_week_selling_region, and opening_hours. The selling_region and next_week_selling_region fields feed directly into the selling_region parameter of list_weekly_offers, letting you retrieve the exact offer set a given store's customers see.
The Penny API is a managed, monitored endpoint for Penny.de — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when Penny.de changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official Penny.de API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Build a weekly grocery deal tracker that surfaces the lowest-priced food items from PENNY's current Angebote by category.
- Compare PENNY's UVP-referenced prices against
regular_priceto identify items with the largest nominal discounts. - Resolve a user's postal code to a nearby store's
selling_region, then fetch region-specific offers tailored to that store. - Detect PENNY app-exclusive prices and alert users who have the app about additional savings on specific offer tiles.
- Monitor
week_availableon thenextweek to know the exact moment new weekly promotions go live. - Aggregate store geodata (
latitude,longitude) to plot all PENNY locations within a city for a store-finder feature. - Track week-over-week changes in
discount_percentageandpricefor specific product categories liketop-angebote.
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 200 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
| Team | $300/mo | 20,000 | 300 req/min |
| Company | $1,000/mo | 100,000 | 500 req/min |
Each endpoint has a fixed posted price per successful call — most fall between 1 and 10 credits — shown on this API's page before you run it. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does Penny.de have an official developer API?+
What does `regular_price_type` mean in the offers response?+
regular_price is being compared against: uvp means the manufacturer's suggested retail price, previous means the item's own previous shelf price, and strikethrough means a crossed-out price shown on the offer tile. This lets you correctly label the price comparison in your UI.How do selling regions work, and do all stores share the same offers?+
search_markets carries a selling_region and a next_week_selling_region code. Passing one of those codes to list_weekly_offers returns the offer set specific to stores in that region. Without a selling_region filter, the endpoint returns only nationwide offers.Does the API return historical weekly offers from past weeks?+
Does the API return individual product detail pages or nutritional information?+
slug field already present in each offer object.