Skip to content

REST API

Plan: Premium.

A JSON API for your servers: an ERP, a CRM, a marketing tool or a headless storefront’s backend. Read a customer’s lists, save and remove items for them, and see your most-saved products.

  • Base URL: https://covewishlist.com/api/v1
  • OpenAPI 3.1 document: https://covewishlist.com/developers/openapi.json (import it into Postman, Insomnia or a code generator).
  • JSON in and out. Every response, success or error, carries Cache-Control: no-store.
  • Customers are identified by their numeric Shopify customer ID only. The API never accepts or returns an email, name or any other customer detail, and a request body with a field it doesn’t know (an email, say) is refused with 400 invalid_request.
  • Every operation is idempotent: saving an item twice, or removing something that isn’t saved, changes nothing. Retrying is always safe.
  • IDs are strings in responses (JSON numbers can’t hold every 64-bit ID).
  1. In Cove, go to Settings › Account. In the Developer API section, click Manage keys. This opens the Developers page.
  2. Under New key, give the key a Name (after whatever will use it; only you see it) and choose its Access:
    • Read: lists, items and most-saved products (the GET endpoints).
    • Read and write: also save and remove items.
  3. Click Create key. The key is shown once, in a Your new API key banner with a Copy key button. Copy it now and keep it on your server.

Keys look like cove_live_ followed by 32 characters, for example cove_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. Cove stores only a one-way hash of the key and a short prefix to show in the list, so the key can’t be shown again or recovered: if you lose it, revoke it and create another.

The key list shows each key’s name, prefix, access, when it was created and when it was last used. A shop can have up to 10 active keys.

Click Revoke next to the key and confirm. It stops working at once, and anything using it loses access. This can’t be undone. Every key is also revoked automatically when the app is uninstalled.

Send the key as a bearer token in the Authorization header:

Terminal window
curl https://covewishlist.com/api/v1/me \
-H "Authorization: Bearer $COVE_API_KEY"

A missing header answers 401 missing_api_key; a malformed, unknown or revoked key (or one for a shop that uninstalled the app) answers 401 invalid_api_key, with a WWW-Authenticate: Bearer header. The API doesn’t tell those cases apart.

Keys work while your plan includes the developer API (Premium). After a downgrade, every call answers 403 plan_required and Settings › Account shows your keys as paused; the keys themselves are kept, and they work again as soon as you’re back on Premium. You can revoke keys on any plan, but you can only create them on Premium. See changing plans.

  • 120 requests a minute per key, and 600 a minute per shop across all its keys.
  • Every authenticated response carries the key’s window:
Header What it holds
X-RateLimit-Limit Requests this key may make per minute.
X-RateLimit-Remaining Requests left in the current minute.
X-RateLimit-Reset When the window resets (Unix seconds).
  • Over a limit, the API answers 429 rate_limited with a Retry-After header in seconds. Back off and retry.
  • Repeated failed authentication from one IP address is limited too: after 30 failures in a minute it answers 429 rate_limited with Retry-After: 60.

Each new save through POST /customers/{customerId}/items counts as one wishlist action toward the shop’s monthly quota, exactly like a save on the storefront. Saving something that’s already saved uses nothing, and if a save fails the action is handed back. At the limit, adds answer 429 quota_reached until the next month or an upgrade; reads and removals keep working. See wishlist actions.

Errors are JSON with the HTTP status below:

{ "error": "list_not_found" }

Match on the error code, not on any message. plan_required also carries feature (developerApi) and plan (the lowest plan that includes it, PREMIUM).

Status Code Meaning
400 invalid_request The body or query failed validation (unknown fields included).
400 invalid_json The body isn’t JSON.
400 invalid_customer_id The customer ID isn’t a numeric Shopify customer ID.
400 invalid_product_id The product ID isn’t a numeric Shopify product ID.
400 invalid_limit limit is not an integer from 1 to 250.
401 missing_api_key No Authorization header.
401 invalid_api_key The key is malformed, unknown or revoked, or the app is uninstalled.
403 plan_required The shop’s plan doesn’t include the developer API. Also carries feature and plan.
403 insufficient_scope A write with a read-only key.
404 list_not_found The listId isn’t one of this customer’s lists.
404 not_found No such endpoint.
405 method_not_allowed Wrong HTTP method; Allow names the right one.
409 list_full The list already holds 500 items.
413 body_too_large The body is over 16 KB.
429 rate_limited Too many requests; wait Retry-After seconds.
429 quota_reached The shop has used its monthly wishlist actions; adds are refused until the next month or an upgrade.
500 internal_error Our fault. Safe to retry: every operation is idempotent.
503 paused The app’s storefront features are paused on this shop.

Every endpoint can answer 401, 403, 429 and 503 as above, besides the errors listed under it.

Where Accepted
customerId and productId in a path Numeric Shopify ID only: 1 to 19 digits, no leading zero, at most 9223372036854775807. Not a GID, never an email.
variantId in a query string Numeric ID or GID (gid://shopify/ProductVariant/…).
productId and variantId in a request body A numeric string, a JSON number (safe integers only, up to 9007199254740991) or a GID (gid://shopify/Product/…, gid://shopify/ProductVariant/…).
listId Cove’s list ID, as returned by the API: 20 to 32 lowercase letters and digits.

The shop and access of the key. Use it to check credentials. Needs a Read or Read and write key.

Terminal window
curl https://covewishlist.com/api/v1/me \
-H "Authorization: Bearer $COVE_API_KEY"

200:

{ "shop": "your-store.myshopify.com", "scope": "READ_WRITE" }

scope is READ or READ_WRITE.

A customer’s lists and saved items. Needs a Read or Read and write key.

Parameter In Required What it is
customerId path yes Numeric Shopify customer ID.

Every list with its live items, newest first. A customer who never saved anything gets "lists": [], never a 404, and reading creates nothing.

Terminal window
curl https://covewishlist.com/api/v1/customers/7012345678901/wishlist \
-H "Authorization: Bearer $COVE_API_KEY"

200:

{
"customerId": "7012345678901",
"count": 2,
"lists": [
{
"id": "clx0examplelistid0000",
"name": "My wishlist",
"isDefault": true,
"shared": false,
"createdAt": "2026-10-01T09:30:00.000Z",
"count": 2,
"items": [
{
"productId": "8123456789",
"variantId": "45123456789",
"quantity": 1,
"addedAt": "2026-10-20T18:02:11.000Z"
},
{
"productId": "8123456790",
"variantId": null,
"quantity": 1,
"addedAt": "2026-10-02T10:00:00.000Z"
}
]
}
]
}
Field What it holds
customerId The customer ID you asked for.
count Distinct saved products and variants across all lists (an item in two lists counts once).
lists[].id Cove’s list ID.
lists[].name The list’s name. It’s shopper text: show it as text, never as HTML.
lists[].isDefault Where one-tap storefront saves go.
lists[].shared Whether the list has a live share link. The link itself is never returned.
lists[].createdAt When the list was created.
lists[].count Items in this list.
lists[].items[].productId Shopify product ID.
lists[].items[].variantId Shopify variant ID, or null when the product was saved without a variant.
lists[].items[].quantity Quantity.
lists[].items[].addedAt When it was saved.

Shoppers’ private notes are never returned.

Errors: 400 invalid_customer_id.

Save a product, or one variant of it, for a customer. Needs a Read and write key.

Parameter In Required What it is
customerId path yes Numeric Shopify customer ID.
productId body yes The product to save.
variantId body no The variant to save. Omit it or send null for a product-level save.
listId body no The list to save to, which must be one of this customer’s.

Without listId, the item goes to the customer’s default list, which is created on first use. With it, the item goes to that list; unlike a save on the storefront’s list picker, this doesn’t make that list the default. The body accepts only these three fields.

Terminal window
curl -X POST https://covewishlist.com/api/v1/customers/7012345678901/items \
-H "Authorization: Bearer $COVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "productId": "8123456789", "variantId": "45123456789" }'

201 when it was saved, 200 when it was already in that list and nothing changed. Both carry added and the customer’s wishlist after the call, in the same shape as GET /customers/{customerId}/wishlist:

{
"added": true,
"wishlist": {
"customerId": "7012345678901",
"count": 1,
"lists": [
{
"id": "clx0examplelistid0000",
"name": "My wishlist",
"isDefault": true,
"shared": false,
"createdAt": "2026-10-20T18:02:11.000Z",
"count": 1,
"items": [
{
"productId": "8123456789",
"variantId": "45123456789",
"quantity": 1,
"addedAt": "2026-10-20T18:02:11.000Z"
}
]
}
]
}
}

A new save counts as one wishlist action (see quota). A list holds up to 500 items.

Errors: 400 invalid_request, 400 invalid_json, 400 invalid_customer_id, 403 insufficient_scope, 404 list_not_found, 409 list_full, 413 body_too_large, 429 rate_limited, 429 quota_reached.

DELETE /customers/{customerId}/items/{productId}

Section titled “DELETE /customers/{customerId}/items/{productId}”

Remove a saved item for a customer. Needs a Read and write key.

Parameter In Required What it is
customerId path yes Numeric Shopify customer ID.
productId path yes Numeric Shopify product ID.
variantId query no Remove the save of this variant (numeric ID or GID). Without it, the product-level save is removed.
listId query no Remove it from this list only. Without it, the item leaves every list (like the storefront heart).

The removal is exact: a variant’s save and a product-level save are separate, so remove each one you mean to.

Terminal window
curl -X DELETE "https://covewishlist.com/api/v1/customers/7012345678901/items/8123456789?variantId=45123456789" \
-H "Authorization: Bearer $COVE_API_KEY"

200, with removed, the number of saved rows removed (an item can sit in several lists, so it can be more than 1, and it is 0 when nothing was saved), and the wishlist after the call:

{
"removed": 1,
"wishlist": {
"customerId": "7012345678901",
"count": 0,
"lists": [
{
"id": "clx0examplelistid0000",
"name": "My wishlist",
"isDefault": true,
"shared": false,
"createdAt": "2026-10-20T18:02:11.000Z",
"count": 0,
"items": []
}
]
}
}

Removals never use a wishlist action.

Errors: 400 invalid_request (an unknown query parameter, a bad variantId or listId), 400 invalid_customer_id, 400 invalid_product_id, 403 insufficient_scope, 404 list_not_found.

The shop’s products by how many shoppers have them saved now. Needs a Read or Read and write key.

Parameter In Required What it is
limit query no How many products, 1 to 250. Default 50.
Terminal window
curl "https://covewishlist.com/api/v1/products/most-saved?limit=10" \
-H "Authorization: Bearer $COVE_API_KEY"

200, most-saved first:

{ "products": [{ "productId": "8123456789", "shoppers": 42, "saves": 47 }] }
Field What it holds
productId Shopify product ID.
shoppers Shoppers with it saved now. A shopper counts once, however many lists hold it.
saves Live saved rows (variants and lists counted separately).

Counts only: never who saved.

Errors: 400 invalid_limit.

Any other path under /api/v1 answers 404 not_found in JSON. The right path with the wrong method answers 405 method_not_allowed with an Allow header naming the method it takes.

const BASE = 'https://covewishlist.com/api/v1';
async function cove(path, init = {}) {
const res = await fetch(BASE + path, {
...init,
headers: {
Authorization: `Bearer ${process.env.COVE_API_KEY}`,
'Content-Type': 'application/json',
...init.headers,
},
});
const body = await res.json();
if (!res.ok) {
const error = new Error(`Cove API ${res.status}: ${body.error}`);
error.code = body.error;
error.retryAfter = Number(res.headers.get('Retry-After')) || null;
throw error;
}
return body;
}
// Save a product for a customer, then read their wishlist.
await cove('/customers/7012345678901/items', {
method: 'POST',
body: JSON.stringify({ productId: '8123456789' }),
});
const { count, lists } = await cove('/customers/7012345678901/wishlist');