Metadata-Version: 2.4
Name: kroger-api
Version: 0.4.0
Summary: A Python client library for the Kroger Public API
Author-email: Stephen Thoemmes <thoemmes.stephen@gmail.com>
License: MIT License
        
        Copyright (c) 2025 Stephen Thoemmes
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
Project-URL: Homepage, https://github.com/CupOfOwls/kroger-api
Project-URL: Repository, https://github.com/CupOfOwls/kroger-api
Project-URL: Documentation, https://developer.kroger.com/documentation/public/
Project-URL: Bug Reports, https://github.com/CupOfOwls/kroger-api/issues
Project-URL: Demo Video, https://github.com/CupOfOwls/kroger-api/blob/main/assets/kroger-api-python-add-to-cart-demo.mp4
Keywords: kroger,api,grocery,shopping,retail
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Office/Business
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.25.0
Requires-Dist: python-dotenv>=0.15.0
Requires-Dist: certifi>=2021.0.0
Requires-Dist: urllib3>=1.26.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-mock>=3.0.0; extra == "dev"
Requires-Dist: requests-mock>=1.9.0; extra == "dev"
Requires-Dist: cryptography; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: flake8; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Provides-Extra: examples
Requires-Dist: tabulate>=0.8.0; extra == "examples"
Dynamic: license-file

# 🛒 Kroger Public API 🛍️  --  with Examples in Python 🐍

A comprehensive Python client library for the Kroger Public API, featuring robust token management, comprehensive examples, and easy-to-use interfaces for all available endpoints.

📚 **Docs:** https://cupofowls.github.io/kroger-api/ — tutorials, how-to guides, concepts, and the full reference.

## 📺 Demo

Adding an item to your cart via an interactive Python script, and checking that it appears in your account:

https://github.com/user-attachments/assets/0079cbc7-5af0-473b-909a-d43508fe43d5

You can also check out [kroger-mcp](https://github.com/CupOfOwls/kroger-mcp), which is an implementation of the Model Context Protocol leveraging this kroger-api package!

## 🚀 Quick Start

### Installation

#### From PyPI (Recommended)
```bash
pip install kroger-api
```

#### From Source
```bash
git clone https://github.com/CupOfOwls/kroger-api.git
cd kroger-api
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install -r requirements.txt
pip install -e .
```

### Basic Usage

```python
from kroger_api import KrogerAPI
from kroger_api.utils.env import load_and_validate_env, get_zip_code

# Env -- set in .env
load_and_validate_env(["KROGER_CLIENT_ID", "KROGER_CLIENT_SECRET"])
zip_code = get_zip_code(default="10001")

# Initialize the client
kroger = KrogerAPI()

# Get a client credentials token for public data
token_info = kroger.authorization.get_token_with_client_credentials("product.compact")

locations = kroger.location.search_locations(
                zip_code=zip_code,
                radius_in_miles=10,
                limit=1
            )

# Search for products
products = kroger.product.search_products(
        term="milk",
        location_id=locations["data"][0]["locationId"],
        limit=5
    )

print(f"Found {len(products['data'])} products!")
```

## 🔐 Getting Started with Credentials

### 1. Create a Kroger Developer Account
Visit the [Kroger Developer Portal](https://developer.kroger.com/manage/apps/register) to:
1. Create a developer account
2. Register your application
3. Get your `CLIENT_ID`, `CLIENT_SECRET`, and set your `REDIRECT_URI`

### 2. Set Up Environment Variables
Copy `.env.example` to `.env` and fill in your credentials:

```bash
# Kroger API Credentials
KROGER_CLIENT_ID=your_client_id_here
KROGER_CLIENT_SECRET=your_client_secret_here
KROGER_REDIRECT_URI=http://localhost:8000/callback

# Optional (Recommended): Your zip code for location-based searches
KROGER_USER_ZIP_CODE=90210
```

**Important:** Set your `KROGER_REDIRECT_URI` during app registration. While marked as optional in the form, the OAuth flow requires it.

### 3. First Run Authorization
The first time you run a script requiring user authentication, you'll be prompted to authorize your app through your web browser. You're granting permission to **your own registered app**, not to any third party.

## 🔄 Token Management

This library implements robust, automatic token management:

### ✨ Features
- **Automatic token refresh** - No manual token handling required
- **Persistent storage** - Tokens saved securely to avoid repeated logins  
- **Expiry tracking** - Checks a token's expiry time before use, with no API call
- **Reactive recovery** - Automatically refreshes expired tokens during API calls
- **PKCE Support** - Enhanced OAuth security with Proof Key for Code Exchange
- **Shared sign-ins** - Processes and threads that share a token file renew it one at a time, so they never sign each other out

### 🔧 How it Works

**Proactive Approach:**
1. Loads saved tokens and checks their expiry time (no API request)
2. Automatically refreshes if token is expired and refresh token is available, while it holds a lock on the token file (`<token file>.lock`). A process that waited for the lock uses the token that another process renewed meanwhile, so a refresh token is never sent twice. A new sign-in and `clear_token` wait for the same lock, and a token whose file `clear_token` deleted is not renewed.

**Reactive Approach:**
1. Makes API requests with current token
2. On 401 Unauthorized errors, refreshes the user token, or requests a new client-credentials token
3. Retries the original request once with the new token

Token files (automatically managed):
- `.kroger_token_client_product.compact.json` - Client credentials tokens
- `.kroger_token_user.json` - User authorization tokens
These tokens are stored in the first matching location:
1. `$KROGER_TOKEN_DIR` — set this environment variable to choose the directory yourself
2. `$XDG_DATA_HOME/kroger-mcp/` (defaults to `~/.local/share/kroger-mcp/`) on Unix-like machines (macOS, Linux)
3. `%APPDATA%\kroger-mcp\` on Windows

The directory is shared with the [kroger-mcp](https://github.com/CupOfOwls/kroger-mcp) package so both reuse the same tokens, and it is always writable even when the process is launched with a read-only working directory (e.g. MCP servers under Claude Desktop on Windows).

### 🔒 Enhanced Security with PKCE

This library supports PKCE (Proof Key for Code Exchange) for enhanced security in the OAuth flow. `authenticate_user` uses it automatically; if you build the flow yourself, pass the parameters as shown:

```python
from kroger_api import KrogerAPI
from kroger_api.utils import generate_pkce_parameters

# Generate PKCE parameters
pkce_params = generate_pkce_parameters()

# Initialize the client
kroger = KrogerAPI()

# Get authorization URL with PKCE
auth_url = kroger.authorization.get_authorization_url(
    scope="cart.basic:write profile.compact",
    state="random_state_value",
    code_challenge=pkce_params['code_challenge'],
    code_challenge_method=pkce_params['code_challenge_method']
)

# After user authorization and redirect, exchange code for token with verifier
token_info = kroger.authorization.get_token_with_authorization_code(
    code="authorization_code_from_redirect",
    code_verifier=pkce_params['code_verifier']
)
```

PKCE helps protect against authorization code interception attacks, particularly important for public clients or those using external tools to manage OAuth flows.

## ⚠️ Error Handling

Every exception the library raises derives from `KrogerAPIError`. Invalid arguments are rejected before any request is sent, and every non-2xx response raises an exception that carries Kroger's own error code and message:

```python
from kroger_api import KrogerAPI, KrogerAPIError, InvalidParameterError, NotFoundError

try:
    kroger.location.get_location("99999999")
except NotFoundError as e:
    print(e)
    # Kroger API error 404 Not Found: (no error message in the response body)
    #   request: GET /v1/locations/99999999
    #   correlation id: akam_1792606007_ba10cee2-f276-444a-aeac-082b98ac9ca3
    #   hint: No store has this locationId. Find valid IDs with a location search.
except KrogerAPIError as e:
    print(e.to_dict())  # the same details as JSON-safe data
```

| Exception | When |
|-----------|------|
| `InvalidParameterError` | An argument breaks a documented API constraint, such as `fulfillment="delivery"` (valid: `ais`, `csp`, `dth`, `sth`). No request is sent. |
| `ConfigurationError` | Credentials or the redirect URI are missing |
| `NotAuthenticatedError` | A call was made before any token was obtained |
| `BadRequestError` (400) | The API rejected a parameter; `kroger_message` names it |
| `AuthenticationError` (401) | The token, or at the token endpoint the client credentials, were rejected after one automatic refresh-and-retry |
| `PermissionDeniedError` (403) | The token type or scope cannot use this endpoint, such as a client-credentials token on the cart |
| `NotFoundError` (404) | The location, chain, or department does not exist |
| `RateLimitError` (429) | The endpoint's daily rate limit is used up |
| `InternalServerError` (5xx) | Kroger's server failed |
| `APIConnectionError`, `APITimeoutError` | No response arrived (requests time out after 30 seconds by default; set `KrogerAPI(timeout=...)`) |

HTTP and network errors also subclass the matching `requests` exceptions, so existing `except requests.exceptions.HTTPError` code still catches every error response from the API. An argument that fails a parameter check raises `InvalidParameterError` (a `ValueError`) without sending a request, so catch `KrogerAPIError` to handle both. A failed cart add whose `outcome_unknown` is `True` may have gone through: check the cart before sending it again. See the [error handling reference](https://cupofowls.github.io/kroger-api/reference/errors) for every attribute.

## 📚 Example Scripts

The `examples/` directory contains comprehensive demonstrations:

| Script | Description | Authentication Required |
|--------|-------------|------------------------|
| `location_api_examples.py` | Search stores, get details about locations, chains, and departments | Client credentials |
| `product_api_examples.py` | Search products, get details, filter by various criteria | Client credentials |
| `cart_api_examples.py` | Add items to user's cart, full shopping workflow | User authorization |
| `identity_api_examples.py` | Get user profile information | User authorization |
| `oauth_flow.py` | Complete OAuth2 authorization code flow example | User authorization |
| `token_refresh_example.py` | Demonstrates automatic token refresh functionality | Both |
| `authorization_api_examples.py` | All authorization endpoints and flows | Both |
| `clear_tokens.py` | Utility to delete all saved token files | None |

### 🏃‍♂️ Running Examples

```bash
# Make sure your .env file is configured first!

# Public API examples (no user login required)
python examples/location_api_examples.py
python examples/product_api_examples.py

# User-specific examples (requires browser login)
python examples/cart_api_examples.py
python examples/identity_api_examples.py
python examples/oauth_flow.py

# Utility scripts
python examples/clear_tokens.py  # Clear saved tokens
```

Here's a quick demo of browsing via the Product API with `examples/product_api_examples.py`:

<div align="center">
  <img src="assets/product_api_script.gif" alt="Kroger API Python Demo">
</div>

## 🏪 Kroger Public API Information

### API Versions & Rate Limits

| API | Version | Rate Limit | Notes |
|-----|---------|------------|-------|
| **Authorization** | 1.0.13 | No specific limit | Token management |
| **Products** | 1.2.4 | 10,000 calls/day | Search and product details |
| **Locations** | 1.2.2 | 1,600 calls/day per endpoint | Store locations and details |
| **Cart** | 1.2.3 | 5,000 calls/day | Add/manage cart items |
| **Identity** | 1.2.3 | 5,000 calls/day | User profile information |

**Note:** Rate limits are enforced per endpoint, not per operation. You can distribute calls across operations using the same endpoint as needed.

### 🔑 Available Scopes

When requesting user authorization, you can specify these scopes:

- `product.compact` - Read product information
- `cart.basic:write` - Add items to cart  
- `profile.compact` - Read user profile information

Request only the scopes of the API products your app is registered for (`cart.basic:write` needs the Cart API, `profile.compact` the Identity API). Kroger refuses the whole sign-in with `invalid_scope` when one is missing.

## 📖 API Documentation

For complete API documentation, visit:
- [Kroger Developer Portal](https://developer.kroger.com/)
- [API Documentation](https://developer.kroger.com/documentation/public/)
- [Acceptable Use Policy](https://developer.kroger.com/documentation/public/getting-started/acceptable-use)

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

## 📄 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## ⚠️ Disclaimer

This is an unofficial Python client for the Kroger Public API. It is not affiliated with, endorsed by, or sponsored by Kroger.
