Wishlist API
The Share a Wish Public API is a plain JSON REST API (OpenAPI 3.1). This page covers authentication and the endpoints a shop integration needs: minting a widget token, saving products, reading what a shopper saved from your store, and managing hosted wishlists. Everything else is in the interactive reference and the OpenAPI YAML.
Base URL
https://nqwfhjycwrtukfszuofi.supabase.co/functions/v1/public-api
All paths on this page are relative to that URL. There is a single production environment; test keys (pk_test_…) talk to the same server and database. The spec notes that requests from outside Share a Wish's own domains must also carry the Supabase anon apikey header — the SDK sets it for you. Version: 1.1.0.
Authentication
Three token types, matching the securitySchemes in the spec:
| Token | Header | Used for |
|---|---|---|
| Public key | Authorization: Bearer pk_live_… or pk_test_… | POST /widget/init, GET /api-usage/{apiKeyId}. Identifies the partner. Safe in browsers: it can only mint widget tokens for allow-listed origins. |
| Widget token | X-Widget-Token: <jwt> | All /widget/* endpoints after init. Signed JWT, 10-minute TTL, bound to the requesting origin. |
| User token | Authorization: Bearer <supabase access token> | Everything that reads or writes an end user's lists: /widget/save, /widget/saved-products, /hosted/*, /me/*. |
Keys and environments
Create keys in the Partner Portal under API & Integrations → API Keys. pk_test_… keys work on every plan, on localhost and staging domains. pk_live_… keys are for production; live keys created after 2026-09-01 must have at least one allowed domain (wildcards like *.example.com are supported), otherwise /widget/init answers 403 allowlist_required. An origin that is not on the list gets 403 origin_not_allowed.
POST /widget/init — mint a widget token
First call of every SDK session. Validates the public key (status active), checks the request origin against the key's domain allow-list and returns a signed widget token plus the partner's basket configuration. Auth: public key.
POST /widget/init
Authorization: Bearer pk_test_XXXXXXXXXXXXXXXX
Content-Type: application/json
{ "origin": "https://shop.example.com" }
origin (required) is window.location.origin of the embedding page. public_key may be sent in the body instead of the header.
HTTP/1.1 200 OK
X-Usage-Total: 1234
X-Usage-Limit: 15000
X-Usage-Remaining: 13766
{
"token": "eyJhbGciOi…",
"partner_id": 42,
"api_key_id": 7,
"exp_minutes": 10,
"overage": false,
"basketConfig": { "layout": "grid", "primaryColor": "#2563EB", "checkout": "ajax", … }
}
WidgetInitResponse: token (string, value for X-Widget-Token), partner_id (integer), api_key_id (integer or null), exp_minutes (integer, 10), overage (boolean, informational — the widget keeps working), basketConfig (object or null). The X-Usage-Total / X-Usage-Limit / X-Usage-Remaining headers on this response count the partner's wishlist actions of the current month against the plan limit (see rate limits & usage).
Errors: 400 invalid_input, 403 unknown_key, 403 origin_not_allowed, 403 allowlist_required, 403 subscription_required (Shopify shop whose trial expired; body contains trial_expired: true).
POST /widget/save — save a product
Upserts the product by canonical URL (tracking parameters stripped), stores localized title/description/image, uploads the image, records a price point and links the product to the wishlist. Auth: widget token and user token. The hosted save page does this for you; call it directly only if you build your own save UI on top of a Share a Wish user session.
POST /widget/save
X-Widget-Token: eyJhbGciOi…
Authorization: Bearer <supabase access token>
Content-Type: application/json
{
"wishlistId": 1234,
"product": {
"url": "https://shop.example.com/products/42",
"title": "Blue Sneaker",
"description": "Lightweight everyday sneaker",
"imageUrl": "https://shop.example.com/img/42.jpg",
"price": 7990,
"currency": "EUR",
"marketCode": "de-DE",
"offerId": 42,
"metadata": { "variantId": "987", "quantity": 1 }
}
}
SaveRequest: wishlistId (integer, required — the numeric db_id of a list, not the share token) and product (SaveProductInput, url required). price is an integer in minor units. partnerMeta / metadata (variant ids, cart parameters) are stored when siteDomain matches the widget origin.
{ "ok": true, "item_id": 98765, "product_id": 55521 }
Errors: 400 missing_wishlist, 400 missing_url, 401 unauthorized, 403 invalid_wishlist (list does not belong to the user), 500 product_create_failed (includes db_error), 500 save_failed.
GET /widget/saved-products — what the user saved from your shop
Returns the user's saved products filtered to the current partner (source_partner_id, fallback: the product's source_domain equals the widget origin). If the filter would return an empty set while the user has products, all products are returned as a fallback. Auth: widget token + user token. A POST variant with the same response exists for clients that cannot send custom headers on GET. DELETE /widget/saved-products/{itemId} removes an item saved from this domain.
GET /widget/saved-products
X-Widget-Token: eyJhbGciOi…
Authorization: Bearer <supabase access token>
{
"products": [
{
"id": 55521,
"wishlist_id": 1234,
"item_id": 98765,
"source_partner_id": 42,
"source_shop_id": null,
"source_domain": "shop.example.com",
"title": "Blue Sneaker",
"description": "Lightweight everyday sneaker",
"imageUrl": "https://…/products/55521.jpg",
"affiliateUrl": "https://shop.example.com/products/42",
"url": "https://shop.example.com/products/42",
"price_minor": 7990,
"currency": "EUR",
"compare_at_price_minor": null,
"brand": null,
"product_type": null,
"tags": null,
"checkoutMeta": { "variantId": "987", "quantity": 1 },
"variantId": "987"
}
]
}
SavedProduct fields: id, wishlist_id, item_id, source_partner_id, source_shop_id, source_domain, title, description, imageUrl / image_url, affiliateUrl, url, price_minor / price, currency, compare_at_price_minor, brand, product_type, tags, checkoutMeta, variantId. The response carries the X-RateLimit-* and X-Usage-* headers.
Hosted lists — CRUD
Hosted lists are the user's wishlists as shown on shareawish.de and in the app. All calls take a user token. Lists are addressed by their share token; the numeric db_id is what you pass as wishlistId to save calls. See Hosted Wishlists & Share Links for the product flow.
| Method | Path | Purpose |
|---|---|---|
| POST | /hosted/lists | Lists owned by or shared (editor) with the user, sorted by updated_at desc. |
| POST | /hosted/me/lists | Create a list. Body { "name": string, "description"?: string }. Returns { ok, list, token }. |
| GET | /hosted/lists/{token} | List metadata (owner or editor). Query: market, lang. |
| PUT | /hosted/lists/{token} | Update name (alias title), description, share_public, image_path, occasion_slug. |
| DELETE | /hosted/lists/{token} | Delete the list and its items (owner only). |
| GET | /hosted/lists/{token}/items | Items of the list as { products: HostedProduct[] }. |
| POST | /hosted/save | Same contract as /widget/save, user token only; also accepts product.productId, rawProductId, sourceLabel, priority (1–4). |
| PUT | /hosted/lists/{token}/items/{itemId}/purchased | Toggle the purchased flag. Body { "purchased": boolean }. |
POST /hosted/lists
Authorization: Bearer <supabase access token>
{
"lists": [
{
"id": "k3Jd9…", // share token → /hosted/lists/{token}
"db_id": 1234, // wishlistId for save calls
"name": "Birthday",
"description": "",
"image_path": "",
"share_public": true,
"occasion_slug": "birthday",
"productCount": 12,
"collaboratorCount": 0,
"is_shared": false,
"role": "owner",
"owner_id": "3f1c…",
"owner_name": "Emma",
"owner_avatar": null,
"updated_at": "2026-09-01T10:12:00Z"
}
]
}
HostedList fields: id (share token), db_id, name, description, image_path, share_public, occasion_slug, productCount, collaboratorCount, is_shared, role (owner | editor | viewer), owner_id, owner_name, owner_avatar, updated_at.
GET /hosted/public/lists/{token} — public share page data
No authentication. Only lists with share_public = true are returned; includes the owner's display name and avatar. GET /hosted/public/lists/{token}/items returns the products ({ products: HostedProduct[] } with purchased and added_at), and PUT /hosted/public/lists/{token}/items/{itemId}/purchased lets a gift-giver mark an item as bought without an account.
GET /hosted/public/lists/k3Jd9…?lang=en&market=de-DE
{
"list": {
"id": "k3Jd9…",
"internal_id": 1234,
"name": "Birthday",
"description": "",
"image_path": "",
"share_public": true,
"created_at": "2026-08-01T08:00:00Z",
"updated_at": "2026-09-01T10:12:00Z",
"occasion_slug": "birthday",
"occasion_title": "Birthday",
"productCount": 12,
"owner_username": "emma",
"owner_avatar": null,
"owner_is_premium": false
}
}
HostedProduct fields: id, wishlist_id, item_id, purchased, title, description, imageUrl, affiliateUrl, price_minor, currency, source_label, priority, rating, review_count, brand, added_at. Errors: 404 not_found for unknown or private tokens.
Errors
Every error is JSON { "error": "<snake_case_code>", "message"?: string } with a 4xx/5xx status; some responses add fields such as db_error or trial_expired. Handlers that catch unexpected exceptions return { "error": "Internal server error" } with status 500. The complete list is on the error codes page.
Download the machine-readable spec: /openapi/shareawish-public-api.yaml · endpoint summary: /openapi/summary.json