Headless means minipim doesn’t render anything. It holds the catalog — products, variants, categories, brands, media and content pages — and your storefront reads it over an API, whatever it’s built with. When something changes, a webhook tells your site so it can revalidate just the pages that moved.

The admin UI is built on exactly the same API, so there’s nothing the admin can read that your storefront can’t.

The API

Every endpoint lives under https://api.minipim.com/v1/ (or your own instance’s URL, if you self-host). The full, generated reference — every route, parameter and response shape — is at api.minipim.com/docs.

Authentication. Create a key under System → API keys in the minipim admin and send it as a bearer token:

GET /v1/products?status=active&limit=50
Authorization: Bearer pim_your_key

The key carries its organization, so there’s no tenant header to send. Keep it server-side — it isn’t a public key.

What a storefront typically reads:

EndpointUse
GET /v1/productsList and filter: status, q, slug, categoryId, brandId, tag, updatedSince, numeric attribute ranges, sorting
GET /v1/products/{id}One product; add ?locale= and/or ?channel= for resolved attribute values
GET /v1/products/{id}/variantsA product’s variants (SKUs live here)
GET /v1/products/{id}/relationshipsRelated products
GET /v1/categoriesThe category tree (flat, with parentId)
GET /v1/brandsBrands, by slug or search
GET /v1/content/by-slug/{slug}A content page or blog post

Lists return { data, limit, offset, hasMore } and page with limit (up to 200) and offset. Money is always integer cents plus a currency — { "amount_cents": 14800, "currency": "USD" } — and measurements carry their unit.

Routing by slug. Product detail is looked up by id, so resolve a URL slug through the list filter first: GET /v1/products?slug=merino-crew&status=active&limit=1.

Pass status=active. List endpoints return drafts and archived items too unless you filter, and so does the content-page slug lookup.

Localized values. Attributes can vary by locale and channel. Ask for GET /v1/products/{id}?locale=fr&channel=web and the response adds resolvedAttributes, each value picked by the closest match: exact locale and channel, then locale only, then channel only, then the unscoped value.

The TypeScript SDK

@minipim/sdk is a typed client generated from the same OpenAPI spec, with pagination helpers, money and attribute helpers, and webhook verification:

import { createMinipimClient } from '@minipim/sdk';

const pim = createMinipimClient({
  baseUrl: 'https://api.minipim.com',
  apiKey: process.env.MINIPIM_API_KEY!,
});

const { data } = await pim.GET('/v1/products', {
  params: { query: { slug: 'merino-crew', status: 'active', limit: 1 } },
});

Webhooks

To receive webhooks, install the Headless connector under Connectors and give it:

  • a destination URL — a public https endpoint on your site;
  • a secret of at least 16 characters, used to sign every delivery;
  • the events you want — exact names like product.updated, or patterns like product.*.

By default a headless connector subscribes to changes on products, variants, categories, brands, content pages, media, modifiers and related products, plus completed connector syncs.

Each delivery is a POST with an X-MiniPim-Event header, an X-MiniPim-Signature-256 header (sha256= + an HMAC-SHA256 of the raw body), and a body like:

{
  "name": "product.updated",
  "organization_id": "…",
  "entity_id": "…",
  "actor": { "type": "user", "id": "…" },
  "timestamp": "2026-10-07T16:02:11.000Z",
  "payload": { "changed_fields": ["attributes"] }
}

Payloads are deliberately thin: they say what changed, and your handler re-fetches the entity by entity_id. Verify the signature before trusting anything:

import { verifyMinipimWebhook } from '@minipim/sdk/webhook';

export async function POST(req: Request) {
  const rawBody = await req.text();
  const ok = verifyMinipimWebhook({
    rawBody,
    signature: req.headers.get('x-minipim-signature-256') ?? '',
    secret: process.env.MINIPIM_WEBHOOK_SECRET!,
  });
  if (!ok) return new Response('bad signature', { status: 401 });
  // revalidate the pages for JSON.parse(rawBody).entity_id
  return new Response('ok');
}

On edge runtimes, use verifyMinipimWebhookEdge from @minipim/sdk/webhook-edge instead. The connector page in the admin shows recent deliveries and can send a test event.

Limits to design for

  • Webhooks aren’t retried. A delivery your endpoint misses is not resent. Pair webhooks with a periodic catch-up using GET /v1/products?updatedSince=… and revalidate anything newer than your last sync.
  • Lists return stored values. locale and channel resolution happens on the product detail endpoint, not on lists.
  • Variants are a separate call from the product.
  • No inventory. Stock isn’t part of minipim; keep it in your commerce platform.

Using an AI agent to maintain the catalog behind your storefront? The MCP server writes through this same API, so its edits trigger the same webhooks.