picnic-api
v4.10.0
Published
Unofficial wrapper for the API of the online supermarket Picnic
Maintainers
Readme
Picnic-API
Unofficial Node.js wrapper for the API of the Picnic online supermarket. Not affiliated with Picnic.
Installation
npm install picnic-apiQuick start
Import 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.246.1-15599;")
});Authentication
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");Usage examples
// 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");Pages (Fusion and RSC)
Most pages are Fusion pages (JSON), fetched with app.getPage(pageId). Some pages are served as a
React Server Components payload instead, depending
on the page and the agent version. Fetch those with app.getRscPage(pageId), which splits the
payload into its JSON rows; the page data is in the props of the React elements (["$", type, key, props]).
getPage throws an UnexpectedPageFormatError when a page comes back as RSC (and getRscPage when it
comes back as a Fusion page), so you can fall back to the other method:
try {
const page = await picnicClient.app.getPage("home_page_root");
} catch (error) {
if (!PicnicClient.isUnexpectedPageFormatError(error)) throw error;
const rscPage = await picnicClient.app.getRscPage(error.pageId);
}Known RSC pages: category-tree-root, profile-root and promo-group-deep-dive?promo_group_id=<id>.
Custom requests
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: "[email protected]" });Responses served as an RSC payload (text/x-component) are returned as raw text instead of parsed JSON.
API reference
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?).
Contributing
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. 😄
