If you’re running a headless storefront — Next.js, Nuxt, Astro, Remix, or any custom front end — minipim serves as the product data layer via its REST API and webhook event bus. Your storefront fetches product data from minipim (or from a CDN/ISR cache backed by minipim), and your build or edge layer stays up to date via webhooks when products change.
The model
minipim catalog
│
├── REST API → your storefront (SSR, ISR, or static build)
└── Webhooks → your cache invalidation / build trigger
minipim is the source of truth. Your front end never writes back to it — it reads product data, renders it, and optionally caches it. When a merchandiser publishes a product change in minipim, a webhook fires and your storefront invalidates or rebuilds the affected pages.
Authentication
All API requests require a Bearer token. Create one under Settings → API keys in your minipim instance. On Hosted, tokens are scoped (read-only vs. read-write); use a read-only token for your storefront.
Authorization: Bearer YOUR_API_TOKEN
Fetching products
List products
GET /api/v1/products?family=apparel&channel=storefront&locale=en&page=1&limit=50
Returns a paginated list of products with their attributes scoped to the given channel and locale. Attributes not scoped to storefront are omitted automatically.
Response:
{
"data": [
{
"id": "prod_01abc",
"sku": "CDR-CRW-018",
"family": "apparel",
"attributes": {
"title": "Cedar Crew Sweater",
"material": "100% Merino Wool",
"price": { "USD": "148.00", "CAD": "199.00" },
"gallery": ["https://cdn.minipim.com/img/..."]
},
"variants": [...],
"updatedAt": "2026-06-11T09:00:00Z"
}
],
"meta": { "total": 284, "page": 1, "limit": 50 }
}
Fetch a single product
GET /api/v1/products/CDR-CRW-018?channel=storefront&locale=en
You can look up by SKU, internal ID, or slug. The channel and locale params scope the response — only attributes available on that channel in that locale are returned.
Filter by category or collection
GET /api/v1/products?category=outerwear&channel=storefront&locale=en
GET /api/v1/products?collection=new-arrivals&channel=storefront&locale=en
Fetch variants
Variants are included in the product response by default. To fetch a specific variant:
GET /api/v1/products/CDR-CRW-018/variants/CDR-CRW-018-M-NAT
Webhooks
Webhooks let your infrastructure react to catalog changes without polling. Register an endpoint under Settings → Webhooks and select the events you want:
| Event | Fires when |
|---|---|
product.published | A product is published to a channel |
product.updated | Any attribute changes on a product |
product.unpublished | A product is unpublished from a channel |
variant.updated | A variant’s attributes or price changes |
family.updated | An attribute family definition changes |
The payload includes the product ID, SKU, channel, and a timestamp. Your endpoint should respond with a 200 within 5 seconds — minipim retries up to 3 times on failure with exponential backoff.
Example payload:
{
"event": "product.published",
"channel": "storefront",
"data": {
"id": "prod_01abc",
"sku": "CDR-CRW-018",
"updatedAt": "2026-06-11T09:00:00Z"
}
}
Keeping the front end in sync
Next.js with ISR
Fetch products at build time with getStaticProps and revalidate on demand via webhook:
// pages/products/[slug].tsx
export async function getStaticProps({ params }) {
const product = await fetch(
`${process.env.MINIPIM_URL}/api/v1/products/${params.slug}?channel=storefront&locale=en`,
{ headers: { Authorization: `Bearer ${process.env.MINIPIM_TOKEN}` } }
).then(r => r.json());
return { props: { product }, revalidate: 3600 };
}
In your webhook handler, call res.revalidate('/products/' + sku) to flush the specific page immediately when a product publishes.
Astro with on-demand rendering
// src/pages/products/[slug].astro
const { slug } = Astro.params;
const res = await fetch(
`${import.meta.env.MINIPIM_URL}/api/v1/products/${slug}?channel=storefront&locale=en`,
{ headers: { Authorization: `Bearer ${import.meta.env.MINIPIM_TOKEN}` } }
);
const { data: product } = await res.json();
Static build with full catalog fetch
For a fully static site, fetch all products at build time:
const res = await fetch(
`${process.env.MINIPIM_URL}/api/v1/products?channel=storefront&locale=en&limit=1000`,
{ headers: { Authorization: `Bearer ${process.env.MINIPIM_TOKEN}` } }
);
const { data: products } = await res.json();
Then trigger a rebuild via your CI/CD pipeline from your webhook handler. This is simple and works well for catalogs under a few thousand products.
Multi-locale and multi-market
Pass locale and market params to get the right copy and pricing:
GET /api/v1/products/CDR-CRW-018?channel=storefront&locale=fr&market=CA
The response will include the French copy for text/richtext attributes and CAD pricing for money attributes. Attributes not localized fall back to the default locale automatically.
GraphQL API
minipim also exposes a GraphQL endpoint at /api/graphql for frontends that prefer it:
query ProductPage($sku: String!, $locale: String!) {
product(sku: $sku) {
id
attributes(locale: $locale, channel: "storefront") {
title
material
price(market: "USD")
gallery { url alt }
}
variants {
sku
attributes(locale: $locale) {
size
color
price(market: "USD")
}
}
}
}
The GraphQL schema is generated from your attribute families, so it’s always in sync with your actual catalog structure.
Readiness and the storefront channel
Before a product appears in the API, it needs to be published to the storefront channel. The readiness rules for your storefront channel control the minimum required attributes. Until a product is published, it won’t appear in list endpoints — protecting you from half-complete products reaching your front end.
Caching recommendations
- Cache product list responses at the CDN edge with a short TTL (60–300 seconds) and invalidate via webhook on
product.published - Cache individual product pages more aggressively (up to 24 hours) and invalidate on demand
- Never cache the
/api/v1/productsresponse in your application code without a cache-busting strategy — a stale product list is worse than a slow one
Known rough edges
List response shapes are inconsistent across endpoints. Most list endpoints return { data: [...], meta: { total, page, limit } }, but some return a bare array. Write your API client to handle both until this is standardized — something like:
const body = await res.json();
const items = Array.isArray(body) ? body : body.data;
This is most likely to bite you on /v1/attributes and a few other metadata endpoints. Product and variant list endpoints use the paginated envelope consistently.
Bulk attribute operations set one value across all products. The current bulk-set-attribute endpoint takes a single value and applies it to every product in the batch — useful for shared values (brand, season) but not for per-product values. For operations where each product needs its own value (like normalizing keyword arrays), you’ll need individual PATCH calls per product. A per-product bulk format ([{ productId, value }]) is on the roadmap.