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.
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.
| Route | Body | Answer |
|---|---|---|
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/vendor | Store 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
heldand 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.
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.
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 noexternalIdand that an import or the API supplies. The lock is lifted for any product with anexternalId: push the rename byexternalIdinstead. - 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.