REST API
Use the REST API to read and change wishlists from your own systems, such as a CRM, an ERP or a headless storefront.
Authentication
Create an API key in the app under Developers → API keys. The key is shown once; store it somewhere safe. Send it in the Authorization header:
GET /api/v1/products/top HTTP/1.1
Host: <edge base URL>
Authorization: Bearer wl_live_…
The edge base URL is shown on the Developers page.
Each key has one or both scopes:
| Scope | Allows |
|---|---|
read | Reading lists, items, product stats and analytics |
write | Adding and removing items |
You can have up to 10 keys. Revoking a key takes effect within a few minutes.
Endpoints
| Method | Path | Scope | Description |
|---|---|---|---|
GET | /api/v1/customers/{id}/lists | read | A customer's lists. Requires protected customer data access. |
GET | /api/v1/lists/{id}/items | read | Items of a list. |
POST | /api/v1/lists/{id}/items | write | Adds an item: { "productId": "123", "variantId": "456" }. |
DELETE | /api/v1/lists/{id}/items | write | Removes an item: { "productId": "123", "variantId": "456" }. |
GET | /api/v1/products/{id}/stats | read | Saves, add-to-cart and orders for one product. |
GET | /api/v1/products/top | read | Most saved products. |
GET | /api/v1/analytics/summary | read | Totals for a date range (?from=YYYY-MM-DD&to=YYYY-MM-DD). |
Ids are numeric Shopify ids as strings, not GIDs. Leave variantId out, or send "", for a product-level item.
Responses
GET /customers/{id}/listsreturns{ "customerId", "lists": [...] }. Each list hasid,name,isDefault,kind,itemCount,sharedandupdatedAt.GET /lists/{id}/itemsreturns a page of items withproductId,variantId,listId,quantity,addedAt,addedByandpriceAtAdd.POSTandDELETEon items return{ "changed": true }, orfalsewhen nothing changed (the item was already saved, or was not in the list). A new item answers201.GET /products/topreturns a page of products withproductId,count(current saves),adds,removes,addToCart,ordersandrevenue.GET /products/{id}/statsreturns the same fields for one product plus a dailyseries.GET /analytics/summaryreturnsfrom,to,adds,removes,addToCart,orders,revenue,customersandguests.
Times are Unix milliseconds. Prices and revenue are integers in the minor unit of the shop currency (cents for USD).
Pagination
List endpoints use cursor pagination. Pass limit (1–250, default 50) and the nextCursor from the previous response as cursor.
GET /api/v1/products/top?limit=100&cursor=eyJ… HTTP/1.1
{
"items": [],
"nextCursor": null
}
nextCursor is null on the last page.
Errors
Every error uses the same shape, with an HTTP status that matches the code:
{
"error": {
"code": "not_found",
"message": "List not found"
}
}
| Code | Status | Meaning |
|---|---|---|
bad_request | 400 | The request body or query is invalid. |
unauthorized | 401 | The key is missing, invalid or revoked. |
forbidden | 403 | The key doesn't have the required scope. |
not_found | 404 | The resource doesn't exist. |
conflict | 409 | The change conflicts with the current state. |
limit_reached | 422 | A limit was reached, such as 500 items per list. |
rate_limited | 429 | Too many requests (about 100 per 10 seconds per key). Retry after the number of seconds in the Retry-After header. |
internal | 500 | Something went wrong on our side. |
Versioning
The version is part of the path (/v1). Breaking changes ship as a new version.