Unofficial Node.js wrapper for the API of the Picnic online supermarket. Not affiliated with Picnic.
npm install picnic-apiImport the package and create a client. All configuration options are optional.
import PicnicClient from "picnic-api";
const picnicClient = new PicnicClient({
countryCode: "NL", // The country code for the API. Options: "NL" (default), "DE" or "FR".
authKey: "...", // An existing auth key to skip the login step.
apiVersion: "15", // The API version (defaults to "15").
url: "...", // A custom base URL (defaults to https://storefront-prod.<countryCode>.picnicinternational.com/api/<apiVersion>).
deviceId: "...", // Custom device identifier for x-picnic-did header. (defaults to "3C417201548B2E3B")
agent: "...", // Custom agent string for x-picnic-agent header. (defaults to "30100;1.228.1-15480;")
});Most endpoints require authentication. Call auth.login() to obtain an auth key, which is automatically stored in the client and sent with subsequent requests. If you already have a key from a previous session, pass it as authKey in the constructor instead.
If the login endpoint responds with a 2FA requirement (second_factor_authentication_required: true), then call auth.generate2FACode("SMS") to generate a 2FA code and pass it to auth.verify2FA(<code>) to complete the login process.
await picnicClient.auth.login("email", "password");
// If 2FA is required:
await picnicClient.auth.generate2FACode("SMS");
await picnicClient.auth.verify2FA("123456");// Search for products
const results = await picnicClient.catalog.search("Affligem blond");
// Add a product to the cart
await picnicClient.cart.addProductToCart(11295810, 2);
// Bulk add products to the cart
await picnicClient.cart.addProductsToCart([
{ productId: "s11295810", quantity: 2 },
{ productId: "s10000123", quantity: 1 },
]);
// Get available delivery slots
const slots = await picnicClient.cart.getDeliverySlots();
// Get details of a specific delivery
const delivery = await picnicClient.delivery.getDelivery("delivery-id");For endpoints not yet covered by a domain service, use sendRequest directly:
await picnicClient.sendRequest("GET", "/unknown/route");
await picnicClient.sendRequest("POST", "/invite/friend", { email: "friend@example.com" });The client exposes the following domain services, each grouping a set of related endpoints:
| Service | Accessor | Description |
|---|---|---|
| App | client.app |
Bootstrap data, pages, and deeplink resolution. |
| Auth | client.auth |
Login, logout, 2FA, and phone verification. |
| Cart | client.cart |
Cart management, delivery slots and selling units (recipes, meal plans, selling groups). |
| Catalog | client.catalog |
Product search, suggestions, details, and images. |
| Consent | client.consent |
Consent settings and GDPR declarations. |
| Content | client.content |
Static content pages (FAQ, search empty state). |
| Customer Service | client.customerService |
Contact info, messages, reminders, and parcels. |
| Delivery | client.delivery |
Delivery history, live position, ratings, and invoices. |
| Payment | client.payment |
Payment profile and wallet transactions. |
| Recipe | client.recipe |
Recipe browsing and saving, plus creating and editing your own (user defined) recipes: name, portions, ingredients, note and image. |
| User | client.user |
User details, profile, suggestions, and push tokens. |
| User Onboarding | client.userOnboarding |
Household/business details and push subscriptions. |
Each service method is fully typed — explore the type definitions under src/domains/<service>/types.ts for request and response shapes.
Catalog and user-defined recipes share RecipeSummary, RecipeDetails, and
RecipeIngredient types:
const saved = await picnicClient.recipe.getSavedRecipes();
const own = await picnicClient.recipe.getUserDefinedRecipes();
if (saved.length) {
const recipe = await picnicClient.recipe.getRecipe(saved[0].id, 2);
// recipe.name, recipe.imageId, recipe.ingredients; quantities are for 2 portions.
}
// getUserDefinedRecipe(id, portions?) returns the same detail structure.Omit portions to use the stored default. Pass { resolveIngredientNames: true } as
the third argument to look up names missing from recipe tiles. Raw pages remain
available through getCookbookPage() and getRecipeDetailsPage(id, portions?).
Contributions are welcome! Please read the CONTRIBUTING.md file for guidelines.
If you enjoy this package, consider using the discount code MAAR3267 so we both get a discount on our next order. 😄