Product Catalog · Push API

Products: read, archive, restore, rename

Writing a product is a push (jobs and batches). These routes read what you pushed and change its state. They are synchronous, and a vendor key reaches only its own products.

Read

GET /v1/catalog/products answers one product when you pass exactly one of externalId, sku or handle, and otherwise a page of the caller's products with a nextCursor (null at the end; limit 1 to 100, default 50). A product that is not the caller's answers 404, so a vendor cannot tell it exists.

Read a product, and page through all of them
import { createCatalogAdmin } from '@usethrottle/catalog/admin';

const admin = createCatalogAdmin({ key: process.env.CATALOG_ADMIN_KEY ?? '' });

// A ref is exactly one of externalId, sku or handle.
const product = await admin.products.get({ externalId: 'hr-1001' });
console.log(product);

// Page through the caller's products (a vendor key sees only its own).
let cursor: string | undefined;
do {
  const page = await admin.products.list(cursor === undefined ? { limit: 100 } : { cursor, limit: 100 });
  for (const item of page.products) console.log(item);
  cursor = page.nextCursor ?? undefined;
} while (cursor !== undefined);

Naming products

The archive, restore and rename routes name a product with a ref: an object with exactly one of externalId, sku or handle.

RouteBodyAnswer
POST /v1/catalog/products/archive{ "products": [ref, …] }{ results: [{ index, status }] }, status archived, unchanged, held or not_found.
POST /v1/catalog/products/restore{ "products": [ref, …] }The same shape, status restored, unchanged, held or not_found.
POST /v1/catalog/products/rename{ "product": ref, "handle": "new-handle" }The renamed product. 409 HANDLE_TAKEN when the handle is in use.
PUT /v1/catalog/products/vendorStore key only. { "vendorId": string | null, "products": [ref, …] }, at most 100 refs; a null vendorId unassigns.Per product: assigned, unchanged or not_found.

Each result carries the index of the product in your request, so one missing product does not fail the rest.

Archive and restore

Deleting through the API archives. The product leaves the storefront at once (it answers 404) and can be restored for 30 days. An archived product stays archived when later pushes resend it, until an explicit restore or a row that sends status: "active" for it.

  • Only the key that may write the product can archive or restore it. A vendor cannot archive the merchant's products.
  • Holds stick. If the merchant archived or drafted the product in the dashboard, the vendor's restore answers held and changes nothing until the merchant lifts the hold (see Vendors and keys).
  • A push to an archived product keeps updating its data; it just does not bring it back.
Archive, restore and rename
import { createCatalogAdmin } from '@usethrottle/catalog/admin';

const admin = createCatalogAdmin({ key: process.env.CATALOG_ADMIN_KEY ?? '' });

// Archive takes the product off the storefront at once (it answers 404) and
// starts the 30-day clock; restore brings it back before then.
const archived = await admin.products.archive([{ externalId: 'hr-1001' }, { sku: 'HR-KAA-250' }]);
for (const result of archived.results) console.log(result.index, result.status);

const restored = await admin.products.restore([{ externalId: 'hr-1001' }]);
console.log(restored.results);

// Rename keeps the product, its id and its reviews. The old URL answers a
// permanent redirect to the new handle.
const renamed = await admin.products.rename({ externalId: 'hr-1002' }, 'kenya-aa-lot-12');
console.log(renamed);

The 30-day purge

A product archived through the API is purged 30 days after the archive. Merchant dashboard archives never purge. What a purge removes:

  • The whole product (its variants, images, reviews as the existing product delete rules do, and its data) when no other non-manual source still supplies one of its variants and it has no archive or draft hold.
  • Otherwise only the archiving source's own values, and the product stays archived.
  • Past orders keep their own copy of what they bought.
A hand edit of an archived vendor product is lost at the purge
If the merchant edits an archived vendor product by hand and the purge then removes it, those edits go with it, unless another source supplies the product. Restore a product before the 30 days if its edits matter.

Rename

Rename changes the URL handle and nothing else: the product keeps its id, its reviews and its inbound links, and the old URL answers a permanent redirect to the new one (the storefront's redirectTo). A handle is also an ordinary field in a push row, so a row with a new productHandle and the same externalId renames too.

  • 409 HANDLE_TAKEN: another product already has that handle.
  • 409 IDENTIFIER_LOCKED: a rename of a product that has no externalId and that an import or the API supplies. The lock is lifted for any product with an externalId: push the rename by externalId instead.
  • On a vendor-owned product the vendor's rename wins over a handle the merchant set in the dashboard.

Categories

A product's categories are category ids or paths such as Coffee > Single origin; missing levels are created. Categories go up to 10 levels deep (a deeper path is refused with CATEGORY_TOO_DEEP), and the storefront's category depth takes 1 to 10. See Collections, categories and tags.