Share a Wish

Wishlist API & Save Widget

Add a cross-device wishlist to any store in an afternoon. One script tag, one public key, and a REST API for everything else.

OpenAPI 3.1 · … documented operations · base URL https://nqwfhjycwrtukfszuofi.supabase.co/functions/v1/public-api

Guides

Getting started in three steps

  1. 1. Create a test key

    In the Partner Portal open API & Integrations → API Keys and create a key with environment Test. You get a pk_test_… key. Test keys work on every plan, can be used on localhost and staging domains and hit the same live API as production keys — there is no separate sandbox, so the data you save is real.

    Production keys (pk_live_…) need at least one allow-listed domain when created after 2026-09-01; POST /widget/init rejects other origins with origin_not_allowed.

  2. 2. Embed the Save Button

    <script src="https://shareawish.shop/sdk/v1/widget.js" data-shareawish-key="pk_test_xxx" defer></script>
    
    <button data-shareawish
            data-url="https://shop.example.com/p/trail-shoes"
            data-title="Trail Running Shoes"
            data-image-url="https://shop.example.com/img/shoes.jpg"
            data-price="12999"
            data-currency="EUR">
      Save to wishlist
    </button>

    Or programmatically: ShareAWish.init({ key: 'pk_test_xxx' }) and ShareAWish.open({ productUrl, title, imageUrl, price, currency }) from a click handler. The SDK calls POST /widget/init with your key and the page origin, receives a widget token (60 minutes) and opens the hosted save dialog at https://shareawish.shop/save (URL carries token, key, origin). Details: Save Button guide.

    Canonical SDK URL: https://shareawish.shop/sdk/v1/widget.js. The legacy /sdk/save-sdk.js stays served but is deprecated (no redirect, security fixes only); cdn.shareawish.shop/sdk/v1/save-sdk.js, cdn.shareawish.com/widget.js and the data-saw-key attribute are deprecated.

  3. 3. Show the basket

    The basket is a hosted page (https://shareawish.shop/basket) that the SDK mounts in an iframe. It shows the products the shopper saved from your shop, handles sign-in and offers "Add to cart" / "View product". The modern default is a side panel (drawer) opened from a wishlist button in your header; the iframe loads on first open.

    <button type="button" id="wishlist-button">Wishlist</button>
    <script src="https://shareawish.shop/sdk/v1/basket-integration.js"></script>
    <script>
      var basket = ShareWishBasket.init({
        apiKey: 'pk_test_xxx',
        configId: 'bkt_xxx',                     // from Partner Portal → Basket Integration (or baskets_create via MCP)
        onAddToCart: function (d) {              // the shopper pressed "Add to cart" in the basket
          addToCart(d.variantId, d.quantity).then(function (ok) { d.respond(ok); });
        }
      });
      var drawer = basket.drawer({ trigger: '#wishlist-button', side: 'right', width: '440px', title: 'My wishlist' });
      ShareAWish.on('saved', function () { drawer.refresh(); });   // keep it in sync with the save button
    </script>

    Sign-in once per device. Browsers partition storage inside third-party iframes, so the basket signs shoppers in through a small first-party window on shareawish.shop and adopts that session. A shopper who is already signed in there (from another shop or the save button) sees the window open and close; nobody has to sign in per shop. "View full wishlist" hands the session over to shareawish.de the same way.

    Other placements: an inline section (basket.renderInto('#sharewish-basket'), e.g. on an account or "My wishlist" page) or a standalone /wishlist page with that section as its only content. Shopify shops use the app's theme block instead (no code).

    Configuration — layout (grid, list, cards, compact), colours, font (inherit recommended), border radius, hover animation, button labels, shop name and empty-state links, cart URL pattern and webhook URL — lives in the basket configuration (GET /baskets/{id}), so you can restyle without redeploying.

    How "Add to cart" reaches your shop — the hosted basket cannot touch your cart directly, so on click it tries in this order: (1) webhookUrl set → it POSTs { type: 'basket_checkout' | 'basket_bulk_checkout', product | products, meta, origin } to your server and stops; (2) the item carries a variant id → the SDK dispatches sharewish:basket.addToCart / …addToCartBulk (also available as onAddToCart / onAddToCartBulk init options) with detail.respond(ok, error) — call your platform's cart API (Shopify /cart/add.js, WooCommerce ?wc-ajax=add_to_cart, your own endpoint) and confirm; (3) a cart URL pattern such as https://shop.example.com/cart/add?id={variantId}&quantity={quantity} is opened in a new tab; (4) otherwise the product/affiliate URL is opened ("View product"). The variant id comes from the save button: data-metadata='{"variantId":"123","quantity":1}'.

    Events on window (prefix sharewish:, or basket.on(name, fn)): basket.mounted, basket.drawer { open }, basket.addToCart, basket.addToCartBulk, basket.checkout, basket.error. The basket reads its configuration from GET /baskets/{id}, tracks opens/checkouts via POST /baskets/{id}/event and lists the shopper's products saved from your shop through GET /widget/saved-products.

    The embedding page must be served over https (localhost excepted). The basket renders in an iframe from https://shareawish.shop/basket, which is delivered with Content-Security-Policy: frame-ancestors 'self' https: http://localhost:* http://127.0.0.1:* — http://localhost works for local development, any other plain http:// page (LAN IP, http staging host) shows an empty iframe. The SDK detects this, logs insecure_origin and dispatches sharewish:basket.error.

    Errors are surfaced as a DOM event: window.addEventListener('sharewish:basket.error', e => console.warn(e.detail.code)) — codes: insecure_origin, config_required, key_required, config_not_found, mount_target_not_found, plus the /widget/init codes (unknown_key, origin_not_allowed, …). See error codes.

Authentication at a glance

TokenHeaderUsed for
Public keyAuthorization: Bearer pk_live_… | pk_test_…POST /widget/init, GET /api-usage/{id}. Safe in browsers.
Widget tokenX-Widget-Token: <jwt>All /widget/* calls after init. Bound to the page origin, 60 min TTL; expired → 401 widget_token_invalid, re-run init.
User tokenAuthorization: Bearer <supabase access token>Everything that reads or writes a user's lists (/widget/save, /hosted/*, /me/*).

Full details, request and response examples: Wishlist API.

Rate limits & usage

Plans are metered in wishlist actions per month — a save, a share or a hosted list view — from 200 on Free to 60,000 on Scale. Requests are never rejected for quota reasons (only the abuse protection on /widget/init, /widget/oauth-* and /baskets/{id}/event answers 429 with Retry-After); you are notified at 80% and 100% of the quota. Paid plans keep working above the quota and pay per additional action (Starter 2.5 ct, Growth 1.0 ct, Scale 0.5 ct); the Free plan is paused at 100% and POST /widget/save answers 402 usage_limit_reached. Responses on /widget/*, /hosted/*, /shops/* and the basket routes carry informational X-RateLimit-* and X-Usage-* headers. See Rate Limits & Usage.

Endpoint overview

Generated from the OpenAPI spec. Endpoints tagged consumer or internal serve the Share a Wish app and portal and are documented for completeness; the partner-facing surface is Widget, Hosted, Baskets, Shops, Discover and Stores.

Loading /openapi/summary.json…

Errors

Errors are JSON with a machine-readable code: { "error": "origin_not_allowed" }, optionally with message. Common codes: invalid_input (400), unauthorized (401), origin_not_allowed / unknown_key / allowlist_required / subscription_required (403 on /widget/init), invalid_wishlist (403), not_found (404), product_create_failed (500, includes db_error). Complete list: Error Codes.