API guide

Storefront integration

Storefront integration

Sell personalized print from any storefront — Odoo, WooCommerce, Shopify, or a bespoke cart — with the same five touchpoints. StackFill stays platform-neutral: your product, variant, cart, and order references are opaque strings it never interprets, the widget speaks postMessage, and every server call is a Bearer-authed /v1 request. An integration is glue, not business logic — if you find yourself encoding print rules in the storefront, that logic belongs in StackFill instead.

The contract at a glance

# Touchpoint Direction Mechanism
1 Personalize browser → StackFill Load widget/v1.js; open with a publishable key + your product/variant ids.
2 Attach to cart browser → your platform Catch stackfill:saved; store fill.id on the cart line.
3 Finalize on order your server → StackFill POST /v1/fills/{id}/finalize with your external_order_id.
4 Fulfill your server → StackFill GET /v1/renders?external_order_id=… → signed production-PDF URL.
5 Live custom preview browser ← StackFill GET /v1/embed/fills/{id}/preview for a settled PNG in your own editor.
6 Listing imagery your platform ← StackFill POST /v1/previews for product/listing thumbnails.

That is the whole surface. A WooCommerce plugin, a Shopify app, and the Odoo stackfill_connector module all implement the same platform-neutral steps — nothing in StackFill core is platform-specific.

1 · Personalize (browser)

Embed the widget on the product page. The publishable key is origin-allowlisted and safe to ship to the browser; the product and variant ids are whatever your platform calls them — StackFill resolves them through product mappings.

<script src="https://stackfill.com/widget/v1.js" async></script>
<button
  data-stackfill-key="pk_live_…"
  data-stackfill-product="SKU-OR-PRODUCT-ID"
  data-stackfill-variant="VARIANT-ID"
  data-stackfill-cart="CART-OR-SESSION-ID">
  Personalize
</button>

2 · Attach to cart (browser)

When the shopper saves, the widget emits a stackfill:saved message whose payload carries both the embed_session and the fill. Take fill.id and write it onto the cart line however your platform stores line metadata — a hidden input, a cart attribute, a line-item property.

window.addEventListener('message', (e) => {
  if (e.data?.type !== 'stackfill:saved') return;
  const fillId = e.data.fill.id;
  // e.g. Odoo: POST /shop/cart/update (form-encoded, with csrf_token),
  // writing fillId into the order-line attributes.
  attachToCartLine(fillId, e.data.fill.preview_url);
});

See Widget events for the full payload.

3 · Finalize on order (server)

On order confirmation, finalize the fill from your server with your order reference. This is the platform-neutral fulfillment primitive — StackFill records external_order_id and returns the production render id.

curl https://api.stackfill.com/v1/fills/FILL_ID/finalize \
  -H "Authorization: Bearer stackfill_live_sk_…" \
  -H "Content-Type: application/json" \
  -d '{ "external_order_id": "YOUR-ORDER-REF" }'

Store the returned render_id on the order.

4 · Fulfill (server)

Resolve the production PDF by your own order reference — no StackFill ids to persist beyond what you chose to keep:

curl "https://api.stackfill.com/v1/renders?external_order_id=YOUR-ORDER-REF" \
  -H "Authorization: Bearer stackfill_live_sk_…"

Each render carries a one-hour signed_url to the press-ready PDF. On a refund or cancellation, flip the fill's status through the fill-status endpoint — your adapter decides what a "credit note" or "cancel" means; the contract only needs the status change.

5 · Live preview in a custom editor (browser)

Use the widget when StackFill should own the personalization UI. If your store owns the form and swatch layout, create an embed fill, PATCH its current state, then request the canonical raster directly. Use fields[].id from the template fields endpoint as the keys in values and assignments; human labels remain accepted for existing integrations.

await fetch(`https://api.stackfill.com/v1/embed/fills/${fillId}`, {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'X-StackFill-Client-Secret': clientSecret,
  },
  body: JSON.stringify({
    values: { fld_name: 'Eleanor Ashcroft', fld_title: 'Director' },
    colors: [
      { index: 1, key: 'black', name: 'Black', hex: '#111111' },
      { index: 2, key: 'merlot', name: 'Merlot', hex: '#6e1f2e' },
    ],
    assignments: { fld_name: 1, fld_title: 2 },
    substrate: { key: 'navy', name: 'Navy', hex: '#2a3550' },
  }),
});

const preview = await fetch(
  `https://api.stackfill.com/v1/embed/fills/${fillId}/preview?w=880&dpr=2`,
  { headers: { 'X-StackFill-Client-Secret': clientSecret } },
);
const imageUrl = URL.createObjectURL(await preview.blob());
document.querySelector('[data-design-preview]').src = imageUrl;

The preview response is image/png. page is 0-based; w is CSS pixels; physical width is min(w × dpr, 2560). StackFill exposes a strong ETag, X-StackFill-Content-Hash, physical dimensions, and X-StackFill-Cache. Identical fill state plus render parameters is served from a content-addressed cache. The preview budget is 120 requests per embed-session secret per 60 seconds; debounce text input before PATCH + preview.

Use X-StackFill-Client-Secret so the credential cannot enter browser history, copied image URLs, referrer headers, or ordinary proxy URL logs. The legacy query form remains supported for compatibility, but new integrations should fetch the image with the header and display a local blob URL. A fill from another session returns 404.

6 · Listing imagery (platform)

Pre-generate product and listing thumbnails from the same engine that prints the card, so what a shopper sees can't drift from what ships:

curl https://api.stackfill.com/v1/previews \
  -H "Authorization: Bearer stackfill_live_sk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "tpl_…",
    "page": 0,
    "values": { "fld_name": "Sample Name" },
    "assignments": { "fld_name": 1 }
  }' --output listing.png

Store the PNG as the product image. Refresh it from the render.succeeded / batch.completed webhooks if the template changes.

The rule for adapters

An adapter is glue only: no rendering, no print rules, no StackFill semantics. If a platform genuinely needs something these touchpoints don't cover, extend the contract — platform-neutrally, so every adapter benefits — rather than special-casing one storefront in StackFill core.