# List wishlist items `GET /api/v1/wishlist-items` Every product a shopper on this store has saved and not bought, newest first. This is the record back-in-stock, price-drop and win-back programmes are built on, and it was reachable only from the storefront until now, which authenticates a shopper rather than the merchant. ONE LIST ANSWERS BOTH QUESTIONS. ?customer_id= is "what has this person saved", which is what the storefront widget shows; ?product_id= is "who is waiting on this product", which the storefront cannot ask at all and which is the reason to integrate. Send both to intersect them, or neither for the store's whole wishlist, which is the shape a first sync wants. AN ID THAT NAMES NOTHING IS AN EMPTY PAGE, not a 404. The honest answer for a customer with nothing saved and for a customer who does not exist is the same empty list, and answering not-found to the second would make this route a probe for which customer ids exist in the store. PAGED BY CREATION TIME, which is the only ordered column: wishlist_item has no updated_at, and a row is written once and never rewritten, so re-saving a product the shopper already has keeps the original date rather than refreshing it. Read created_at as "wanted since", which is what makes it worth segmenting on. THIS FAMILY IS READ ONLY and there is no write half to add later. Saving a product asserts that a NAMED PERSON wants it, and on this surface the caller supplies the customer id, so a key could write a row indistinguishable from one the shopper made by tapping a heart, which a back-in-stock programme would then send on. Removing one destroys a shopper's saved list with no restore. Both stay on the storefront, where the shopper owns the decision, and apiscope mints no write_wishlists at all. ## Query parameters - `customer_id` (string) — Narrow the page to one shopper's saved products, which is what the storefront wishlist widget shows. NOT validated against the customer table: an id that names nobody returns an empty page rather than a 404, because the honest answer for a customer with nothing saved is the same empty list, and not-found would make this a probe for which customer ids exist. - `product_id` (string) — Narrow the page to everyone waiting on one product, which is the question a back-in-stock or price-drop programme asks and the storefront cannot answer at all. An id that names nothing is an empty page, as above. Send it together with customer_id to intersect the two rather than either being ignored. - `limit` (integer) — Rows per page. Out of range is a 400 rather than a silent clamp, so a client asking for more than 100 learns it did not get it. - `after` (string) — The next_cursor from the previous page. Opaque: decode nothing from it and construct nothing by hand, since its encoding is not part of this contract. Omit it to read the first page. ## Responses - `200` — Success - `400` — The request was refused before any state changed. `code` is one of: `invalid_body`, a write body this route will not take. `reason` partitions it and `field` names the key when one key is at fault. `invalid_query`, a query parameter, including limit and after. `invalid_text`, a NUL byte or bytes that are not valid UTF-8 anywhere in the path, the query or the body. Strip control characters before sending. `idempotency_key_required`, a write sent without the Idempotency-Key header. `invalid_request`, an Idempotency-Key longer than 255 bytes. Routes add their own codes for rules only they know. Switch with a default arm. - `401` — No credential, or one this API does not accept. `code` is always `unauthorized`. THE BODY IS DELIBERATELY UNINFORMATIVE. An expired key, a revoked key, a publishable key, a key belonging to another merchant and a key that never existed are all refused with the same bytes, so this response cannot be used to probe which keys exist. Check the key's state in the dashboard rather than inferring it here. Send the key as `Authorization: Bearer ` or as `X-API-Key: `. It is never accepted in a query string. - `403` — The key lacks the read_wishlists scope. `code` is `insufficient_scope` and the message names the scope to ask the merchant for. - `429` — Too many requests. `code` is `rate_limited`. Two limits apply independently: one on the credential and the route family, one on the client address. The headers describe whichever has less left, so honouring Retry-After always clears the window that bound. - `default` — Any status this operation does not list, in the same envelope. A 5xx means the request may or may not have applied. Retry it with the SAME Idempotency-Key: that is the only way to find out without risking a duplicate, and it is what the key is for. A few 4xx conditions arrive here rather than as a listed status because they depend on the merchant's plan or on a module being wired: 402 when a quota or a plan limit is reached, and 503 when a capability the route needs is not configured on this deployment. Both carry a `code` naming which. ## Example ```bash curl --request GET \ --url 'https://api.mercemur.com/api/v1/wishlist-items' \ --header 'Authorization: Bearer ' ```