Skip to main content

🛒 Rohlik API Python Client

An async, fully typed Python client for the Rohlik.cz online grocery service and its sister shops Knuspr.de, Gurkerl.at, Kifli.hu and Sezamo.ro — search products, manage your cart, browse recipes (Rohlík Chef), and read your orders and deliveries, all from Python.

⚠️ Unofficial — personal use only

This is an unofficial client for Rohlik.cz's non-public API. It is not affiliated with, authorized by, or endorsed by Rohlik.cz / Rohlik Group.

  • Intended for personal, non-commercial use with your own account only.
  • The private API can change or break at any time, without notice.
  • Your use may be subject to Rohlik.cz's Terms of Service — review them and behave responsibly (don't hammer the API or use it commercially).
  • Provided "as is", with no warranty. Use at your own risk.

Table of contents

Features

  • 🚀 Built on aiohttp; bring your own session (e.g. Home Assistant's shared session)
  • 🔐 Automatic login/logout, plus transparent re-authentication when a session expires (HTTP 401)
  • 🎯 Clean, service-based API (client.cart, client.products, …)
  • 🧩 Fully typed dataclass models for parsed responses (py.typed)
  • 🔄 Works as an async context manager
  • 🍳 Recipe search and ingredient shopping (Rohlík Chef)
  • 📦 Product details, composition/nutrition, prices, and AI summaries
  • 🌍 Works with every Rohlík Group shop: Rohlík.cz, Knuspr.de, Gurkerl.at, Kifli.hu and Sezamo.ro (other shops)

Built on top of this library — handy if you'd rather not write Python:

  • 🤖 rohlik-mcp — a Model Context Protocol server that exposes Rohlik.cz to AI assistants like Claude. Search products, manage your cart, plan meals from recipes, and check orders and deliveries in plain language.
  • 🏠 HA-RohlikCZ — a Home Assistant integration that brings your Rohlik.cz cart, orders and deliveries into your smart home.

Requirements

  • Python 3.13+
  • aiohttp (installed automatically)

Installation

pip install rohlik-api

Quick start

import asyncio
from rohlik_api import RohlikAPI

async def main():
    async with RohlikAPI(username="your_email@example.com", password="your_password") as client:
        # Search for products (returns a SearchResults model)
        results = await client.products.search("mleko", limit=5)
        for product in results.results:
            print(f"{product.name} - {product.price}")

        # Get cart contents (returns a Cart model)
        cart = await client.cart.get_content()
        print(f"Cart total: {cart.total_price} ({cart.total_items} items)")

        # Search recipes (returns a RecipeSearchResults model)
        recipes = await client.recipes.search("rajská", limit=5)
        print(f"Found {recipes.total_hits} recipes")

asyncio.run(main())

The async context manager logs you in on entry and logs out + closes the connection on exit.

Credentials & security

The client authenticates with your normal shop account email and password.

  • Never hard-code credentials in source you commit. Prefer environment variables or a secrets manager:

    import os
    from rohlik_api import RohlikAPI
    
    client = RohlikAPI(
        username=os.environ["ROHLIK_USERNAME"],
        password=os.environ["ROHLIK_PASSWORD"],
    )
    
  • Credentials are only ever sent over HTTPS to the shop you target (Rohlik.cz unless you pass another base_url). This library does not store or transmit them anywhere else.

  • Use a dedicated account if you're uncomfortable automating your primary one.

Typed models

Service methods that parse responses return typed dataclasses (importable from rohlik_api) rather than raw dictionaries, so your editor and type checker know the shape of the data:

from dataclasses import asdict
from rohlik_api import Cart, SearchResults

cart = await client.cart.get_content()   # -> Cart
cart.total_price                         # float
cart.products[0].name                    # str

# Convert any model to a plain dict (e.g. for JSON / Home Assistant / MCP):
asdict(cart)

Raw passthrough endpoints (orders.*, delivery.*, account.get_premium_profile, account.get_bags_info, account.get_announcements, and get_data) return the decoded JSON as dict / list, since they are not reshaped by the client.

Services

Functionality is grouped into services, accessed as properties on the client:

Service Property Description
Cart client.cart Shopping cart operations
Products client.products Product search and details
Orders client.orders Order history
Delivery client.delivery Delivery info and timeslots
Account client.account Account data and shopping lists
Recipes client.recipes Recipe search and ingredients (Rohlík Chef)

API reference

Cart service (client.cart)

# Get cart contents
cart = await client.cart.get_content()
# -> Cart(total_price=199.90, total_items=3, can_make_order=True, products=[CartItem, ...],
#         minimum_order_price=..., currency="CZK")
# can_make_order also requires checkout details (e.g. a delivery slot); to check
# the minimum order value, compare total_price with minimum_order_price.

# Add items to cart
added = await client.cart.add_items([
    {"product_id": 123456, "quantity": 2},
    {"product_id": 789012, "quantity": 1},
])
# -> [123456, 789012]   (list of product IDs successfully added)

# Delete item from cart (raises APIRequestFailedError on failure)
await client.cart.delete_item(order_field_id="abc123")

Products service (client.products)

# Search for products -> SearchResults | None (None only on request failure)
results = await client.products.search("mléko", limit=10, favourite=False)
for product in results.results:  # ProductSearchResult: id, name, price, brand, amount
    print(product.name, product.price)

# AI-generated product summary -> AISummary | None
summary = await client.products.get_ai_summary(product_id=1384964)

# Composition / nutrition / allergens -> ProductComposition | None
composition = await client.products.get_composition(product_id=1425155)

# Current price -> ProductPrice | None
price = await client.products.get_price(product_id=1425155)

# Raw product detail (brand, attributes, …) -> dict | None (None on 404)
detail = await client.products.get_detail(product_id=1425155)

# Category hierarchy -> list[dict] | None (None if discontinued / 404)
categories = await client.products.get_categories(product_id=1425155)

# Basic data for many products in one request -> list[ProductCard] | None
# (same order as the IDs; IDs the API did not return are skipped)
cards = await client.products.get_cards([1425155, 1384964])

# This week's deals ("Akce týdne") as ProductCards -> list[ProductCard] | None
deals = await client.products.get_week_sales(size=30)
for card in deals or []:
    print(card.name, card.price, card.original_price, card.on_sale)

Orders service (client.orders)

next_order = await client.orders.get_next()                       # upcoming order
last_order = await client.orders.get_last()                       # last delivered order
orders = await client.orders.get_delivered(limit=50, offset=0)    # one history page
all_orders = await client.orders.get_all_delivered()              # every page, paginated
detail = await client.orders.get_detail(order_id=12345678)        # full order incl. items

Delivery service (client.delivery)

delivery = await client.delivery.get_info()                    # first available delivery
timeslot = await client.delivery.get_timeslot_reservation()    # reserved slot, if any
slots = await client.delivery.get_next_slots()                 # upcoming slots for your address
announcements = await client.delivery.get_announcements()      # e.g. courier ETA messages
addresses = await client.delivery.get_addresses()              # saved delivery addresses
address_id = await client.delivery.get_active_address_id()     # address used for slots

get_next_slots() needs a delivery address. The login response does not always include one, so the client falls back to your saved addresses (preferring the one you are currently delivered to) and caches the result.

Account service (client.account)

premium = await client.account.get_premium_profile()
bags = await client.account.get_bags_info()
announcements = await client.account.get_announcements()

# Shopping list by ID -> ShoppingList
shopping_list = await client.account.get_shopping_list("list_id_here")
# ShoppingList(name="My List", products_in_list=[...])

Recipes service (client.recipes) — Rohlík Chef

# Search recipes -> RecipeSearchResults(recipes=[RecipeSummary, ...], total_hits=4)
recipes = await client.recipes.search("rajská", limit=10, offset=0)

# Recipe details -> RecipeDetail | None
recipe = await client.recipes.get_detail(recipe_id=59)

# Products for ingredients -> IngredientProducts | None
products = await client.recipes.get_ingredient_products(ingredient_ids=[102, 56], limit=5)

Aggregated data

# Fetch delivery, orders, cart, premium profile, announcements, etc. in one call
all_data = await client.get_data()

Error handling

All errors derive from RohlikAPIError:

from rohlik_api import RohlikAPI, InvalidCredentialsError, APIRequestFailedError

try:
    async with RohlikAPI(username="email@example.com", password="password") as client:
        cart = await client.cart.get_content()
except InvalidCredentialsError:
    print("Wrong username or password")
except APIRequestFailedError as err:
    print(f"Request failed: {err}")

Error contract:

  • Critical / mutating operations (login, cart.get_content, cart.delete_item, account.get_shopping_list) raise APIRequestFailedError on failure. Login raises InvalidCredentialsError for a wrong email or password, and RohlikAPIError for any other status the shop reports.
  • Read / optional fetches (most orders, delivery, account, products, and recipes getters) return None on failure, so an aggregate fetch can continue gracefully.
  • Batch operations do not fail as a whole: cart.add_items returns only the IDs that were added (failures are logged), and orders.get_all_delivered returns the orders gathered so far if a page fails.
  • account.get_shopping_list raises ValueError if called without an ID.

Advanced usage

Other shops

Rohlík Group runs the same API under several brands. SITES holds the known shops (base URL, currency, timezone), keyed by country code:

Code Shop Currency
cz Rohlík.cz (default) CZK
de Knuspr.de EUR
at Gurkerl.at EUR
hu Kifli.hu HUF
ro Sezamo.ro RON
from rohlik_api import SITES, RohlikAPI

site = SITES["de"]
async with RohlikAPI("email@example.com", "password", base_url=site.base_url) as client:
    cart = await client.cart.get_content()
    print(cart.total_price, cart.currency or site.currency)  # e.g. 11.99 EUR

Cart.currency comes from the cart's items, so it is None for an empty cart (or if no item reports one); fall back to site.currency then. Delivery announcements and other texts come back in the shop's language.

Configuration

client = RohlikAPI(
    username="your_email@example.com",
    password="your_password",
    base_url="https://www.rohlik.cz",  # optional; e.g. SITES["de"].base_url
    timeout=30.0,                       # optional
    headers={"Custom-Header": "Value"}, # optional
    auto_login=True,                    # optional, default True
)

Manual session management

from rohlik_api import RohlikAPI

async def main():
    client = RohlikAPI(
        username="email@example.com",
        password="password",
        auto_login=False,  # disable auto-login
    )
    try:
        await client.login()
        print(client.is_logged_in, client.user_id, client.address_id)
        cart = await client.cart.get_content()
        await client.logout()
    finally:
        await client.close()

Reusing an existing aiohttp session

The client is built on aiohttp. By default it creates and owns its own ClientSession, but you can inject an externally managed session instead — useful inside a Home Assistant integration, where the recommended pattern is to share a single session per instance. An injected session is never closed by the client; its lifecycle stays with the owner.

import aiohttp
from rohlik_api import RohlikAPI

async def main(session: aiohttp.ClientSession):
    client = RohlikAPI(
        username="email@example.com",
        password="password",
        session=session,  # reuse the caller's session
    )
    async with client:
        cart = await client.cart.get_content()
    # `session` is left open for the caller to close.

Inside a Home Assistant integration you would pass the shared session, for example RohlikAPI(..., session=async_get_clientsession(hass)).

Development

# Install with development dependencies
pip install -e ".[dev]"

# Run the test suite
pytest

# Lint, format check and type check
ruff check .
black --check .
mypy rohlik_api

Please make sure pytest, ruff, black and mypy all pass before opening a pull request.

Disclaimer

This project is an independent, unofficial client. It is not affiliated with, authorized by, or endorsed by Rohlik.cz, Rohlik Group, or any of its subsidiaries. "Rohlik", "Rohlik.cz" and "Rohlík Chef" are trademarks of their respective owners.

It talks to a private, undocumented API that may change or stop working at any time. It is provided for personal, non-commercial use only, and comes with no warranty of any kind. You are responsible for complying with Rohlik.cz's Terms of Service and applicable law. Use at your own risk.

License

MIT © Daniel Vejsada

Metadata

Release files for rohlik-api 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rohlik-api 0.3.0
File Size Uploaded
rohlik_api-0.3.0.tar.gz 48.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for rohlik-api 0.3.0
File Interpreter ABI Platform
rohlik_api-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 85.4 kB

Release files / rohlik_api-0.3.0.tar.gz

Download URL rohlik_api-0.3.0.tar.gz
Size 48.6 kB
Tags Source
SHA-256 checksum
How to use checksums
da56c9c6746c6deb91d72be435278c5a845fd67f6a8dd91d836cfca64bc9f3cc
BLAKE2b-256 checksum
How to use checksums
0d4b428c63f0d17164b9128feda518f210d32cb93edcda99196c8bbad916fdfb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Release files / rohlik_api-0.3.0-py3-none-any.whl

Download URL rohlik_api-0.3.0-py3-none-any.whl
Size 36.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8ca61d31eb0a4cbedd122a546f737282da868e91f5f5c24ca2f86d1f26af8183
BLAKE2b-256 checksum
How to use checksums
e3cc42cd11232d5ac2c66228c3099e66813d38f49503f8ab8159f29f6ccd3297
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page