Loading...
Loading...
Compare original and translation side by side
marketplace-fulfillmentmarketplace-rate-limitingmarketplace-fulfillmentmarketplace-rate-limitingPOSTapi/catalog_system/pvt/skuseller/changenotification| Route | Path pattern | Meaning |
|---|---|---|
| Change notification with marketplace SKU ID | | |
| Change notification with seller ID and seller SKU ID | | |
changenotification/PUTGEThttps://api.vtex.com/{accountName}/suggestions/{sellerId}/{sellerSkuId}{account}.vtexcommercestable.com.br/api/catalog_system/pvt/sku/seller/...POST /api/catalog_system/pvt/skuseller/changenotification/{sellerId}/{sellerSkuId}POST .../changenotification/{skuId}POST .../changenotification/{skuId}PUT/suggestions/{sellerId}/{sellerSkuId}https://api.vtex.com/{accountName}POST /notificator/{sellerId}/changenotification/{sellerSkuId}/pricePOST /notificator/{sellerId}/changenotification/{sellerSkuId}/inventorychangenotification/skuIdPOST /pvt/orderForms/simulationSeller VTEX Marketplace
│ │
│─── POST changenotification ──────▶│
│◀── 200 (exists) or 404 (new) ────│
│ │
│─── PUT Send SKU Suggestion ──────▶│ (if 404)
│ │── Pending in Received SKUs
│ │── Marketplace approves/denies
│ │
│─── POST price notification ──────▶│
│◀── POST fulfillment simulation ───│ (marketplace fetches data)
│─── Response with price/stock ────▶│api/catalog_system/pvt/skuseller/changenotificationPOST| 路由 | 路径结构 | 含义 |
|---|---|---|
| 携带市场SKU ID的变更通知 | | |
| 携带卖家ID和卖家SKU ID的变更通知 | | |
changenotification/PUTGEThttps://api.vtex.com/{accountName}/suggestions/{sellerId}/{sellerSkuId}{account}.vtexcommercestable.com.br/api/catalog_system/pvt/sku/seller/...POST /api/catalog_system/pvt/skuseller/changenotification/{sellerId}/{sellerSkuId}POST .../changenotification/{skuId}POST .../changenotification/{skuId}https://api.vtex.com/{accountName}/suggestions/{sellerId}/{sellerSkuId}PUTPOST /notificator/{sellerId}/changenotification/{sellerSkuId}/pricePOST /notificator/{sellerId}/changenotification/{sellerSkuId}/inventorychangenotification/skuIdPOST /pvt/orderForms/simulationSeller VTEX Marketplace
│ │
│─── POST changenotification ──────▶│
│◀── 200 (exists) or 404 (new) ────│
│ │
│─── PUT Send SKU Suggestion ──────▶│ (if 404)
│ │── Pending in Received SKUs
│ │── Marketplace approves/denies
│ │
│─── POST price notification ──────▶│
│◀── POST fulfillment simulation ───│ (marketplace fetches data)
│─── Response with price/stock ────▶│POST /api/catalog/pvt/productPOST /api/catalog/pvt/stockkeepingunitPOST /api/catalog/pvt/productPOST /api/catalog/pvt/stockkeepingunitimport axios, { AxiosInstance } from "axios";
interface SkuSuggestion {
ProductName: string;
SkuName: string;
ImageUrl: string;
ProductDescription: string;
BrandName: string;
CategoryFullPath: string;
EAN: string;
Height: number;
Width: number;
Length: number;
WeightKg: number;
SkuSpecifications: Array<{
FieldName: string;
FieldValues: string[];
}>;
}
async function integrateSellerSku(
client: AxiosInstance,
marketplaceAccount: string,
sellerId: string,
sellerSkuId: string,
skuData: SkuSuggestion
): Promise<void> {
const storeBaseUrl = `https://${marketplaceAccount}.vtexcommercestable.com.br`;
const suggestionUrl = `https://api.vtex.com/${marketplaceAccount}/suggestions/${sellerId}/${sellerSkuId}`;
// Step 1: Seller-scoped change notification (Catalog API — store host)
try {
await client.post(
`${storeBaseUrl}/api/catalog_system/pvt/skuseller/changenotification/${sellerId}/${sellerSkuId}`
);
// 200 OK — SKU exists, marketplace will fetch updates via fulfillment simulation
console.log(`SKU ${sellerSkuId} exists in marketplace, update triggered`);
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response?.status === 404) {
// 404 — SKU not found, send suggestion
console.log(`SKU ${sellerSkuId} not found, sending suggestion`);
await client.put(suggestionUrl, skuData);
console.log(`SKU suggestion sent for ${sellerSkuId}`);
} else {
throw error;
}
}
}// WRONG: Marketplace-SKU-only path with the seller's SKU code (misroutes the notification).
// .../changenotification/{skuId} expects the VTEX marketplace SKU ID, not sellerSkuId.
await client.post(
`https://${marketplaceAccount}.vtexcommercestable.com.br/api/catalog_system/pvt/skuseller/changenotification/${sellerSkuId}`
);
// WRONG: SKU Suggestion on the store host + Catalog path — public contract is api.vtex.com + /suggestions/...
await client.put(
`https://${marketplaceAccount}.vtexcommercestable.com.br/api/catalog_system/pvt/sku/seller/${sellerId}/suggestion/${sellerSkuId}`,
skuData
);
// WRONG: Seller writing directly to marketplace catalog — bypasses suggestion/approval; expect 403
async function createSkuDirectly(
client: AxiosInstance,
marketplaceAccount: string,
productData: Record<string, unknown>
): Promise<void> {
// Direct catalog write — sellers don't have permission for this
await client.post(
`https://${marketplaceAccount}.vtexcommercestable.com.br/api/catalog/pvt/product`,
productData
);
// Will fail: 403 Forbidden — seller lacks catalog write permissions
// Will fail: 403 Forbidden — seller lacks catalog write permissions
}POST /api/catalog/pvt/productPOST /api/catalog/pvt/stockkeepingunitPOST /api/catalog/pvt/productPOST /api/catalog/pvt/stockkeepingunitimport axios, { AxiosInstance } from "axios";
interface SkuSuggestion {
ProductName: string;
SkuName: string;
ImageUrl: string;
ProductDescription: string;
BrandName: string;
CategoryFullPath: string;
EAN: string;
Height: number;
Width: number;
Length: number;
WeightKg: number;
SkuSpecifications: Array<{
FieldName: string;
FieldValues: string[];
}>;
}
async function integrateSellerSku(
client: AxiosInstance,
marketplaceAccount: string,
sellerId: string,
sellerSkuId: string,
skuData: SkuSuggestion
): Promise<void> {
const storeBaseUrl = `https://${marketplaceAccount}.vtexcommercestable.com.br`;
const suggestionUrl = `https://api.vtex.com/${marketplaceAccount}/suggestions/${sellerId}/${sellerSkuId}`;
// Step 1: Seller-scoped change notification (Catalog API — store host)
try {
await client.post(
`${storeBaseUrl}/api/catalog_system/pvt/skuseller/changenotification/${sellerId}/${sellerSkuId}`
);
// 200 OK — SKU exists, marketplace will fetch updates via fulfillment simulation
console.log(`SKU ${sellerSkuId} exists in marketplace, update triggered`);
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response?.status === 404) {
// 404 — SKU not found, send suggestion
console.log(`SKU ${sellerSkuId} not found, sending suggestion`);
await client.put(suggestionUrl, skuData);
console.log(`SKU suggestion sent for ${sellerSkuId}`);
} else {
throw error;
}
}
}// WRONG: Marketplace-SKU-only path with the seller's SKU code (misroutes the notification).
// .../changenotification/{skuId} expects the VTEX marketplace SKU ID, not sellerSkuId.
await client.post(
`https://${marketplaceAccount}.vtexcommercestable.com.br/api/catalog_system/pvt/skuseller/changenotification/${sellerSkuId}`
);
// WRONG: SKU Suggestion on the store host + Catalog path — public contract is api.vtex.com + /suggestions/...
await client.put(
`https://${marketplaceAccount}.vtexcommercestable.com.br/api/catalog_system/pvt/sku/seller/${sellerId}/suggestion/${sellerSkuId}`,
skuData
);
// WRONG: Seller writing directly to marketplace catalog — bypasses suggestion/approval; expect 403
async function createSkuDirectly(
client: AxiosInstance,
marketplaceAccount: string,
productData: Record<string, unknown>
): Promise<void> {
// Direct catalog write — sellers don't have permission for this
await client.post(
`https://${marketplaceAccount}.vtexcommercestable.com.br/api/catalog/pvt/product`,
productData
);
// Will fail: 403 Forbidden — seller lacks catalog write permissions
// Will fail: 403 Forbidden — seller lacks catalog write permissions
}async function batchNotifySkus(
client: AxiosInstance,
baseUrl: string,
sellerId: string,
sellerSkuIds: string[],
concurrency: number = 5,
delayMs: number = 200
): Promise<void> {
const results: Array<{ sellerSkuId: string; status: "exists" | "new" | "error" }> = [];
for (let i = 0; i < sellerSkuIds.length; i += concurrency) {
const batch = sellerSkuIds.slice(i, i + concurrency);
const batchResults = await Promise.allSettled(
batch.map(async (sellerSkuId) => {
try {
await client.post(
`${baseUrl}/api/catalog_system/pvt/skuseller/changenotification/${sellerId}/${sellerSkuId}`
);
return { sellerSkuId, status: "exists" as const };
} catch (error: unknown) {
if (
error instanceof Error &&
"response" in error &&
(error as { response?: { status?: number } }).response?.status === 404
) {
return { sellerSkuId, status: "new" as const };
}
if (
error instanceof Error &&
"response" in error &&
(error as { response?: { status?: number; headers?: Record<string, string> } })
.response?.status === 429
) {
const retryAfter = parseInt(
(error as { response: { headers: Record<string, string> } }).response.headers[
"retry-after"
] || "60",
10
);
console.warn(`Rate limited. Waiting ${retryAfter}s before retry.`);
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
return { sellerSkuId, status: "error" as const };
}
throw error;
}
})
);
for (const result of batchResults) {
if (result.status === "fulfilled") {
results.push(result.value);
}
}
// Throttle between batches
if (i + concurrency < sellerSkuIds.length) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
}// WRONG: No rate limiting, no error handling, tight loop
async function notifyAllSkus(
client: AxiosInstance,
baseUrl: string,
sellerId: string,
sellerSkuIds: string[]
): Promise<void> {
// Fires all requests simultaneously — will trigger 429 rate limits
await Promise.all(
sellerSkuIds.map((sellerSkuId) =>
client.post(
`${baseUrl}/api/catalog_system/pvt/skuseller/changenotification/${sellerId}/${sellerSkuId}`
)
)
);
}async function batchNotifySkus(
client: AxiosInstance,
baseUrl: string,
sellerId: string,
sellerSkuIds: string[],
concurrency: number = 5,
delayMs: number = 200
): Promise<void> {
const results: Array<{ sellerSkuId: string; status: "exists" | "new" | "error" }> = [];
for (let i = 0; i < sellerSkuIds.length; i += concurrency) {
const batch = sellerSkuIds.slice(i, i + concurrency);
const batchResults = await Promise.allSettled(
batch.map(async (sellerSkuId) => {
try {
await client.post(
`${baseUrl}/api/catalog_system/pvt/skuseller/changenotification/${sellerId}/${sellerSkuId}`
);
return { sellerSkuId, status: "exists" as const };
} catch (error: unknown) {
if (
error instanceof Error &&
"response" in error &&
(error as { response?: { status?: number } }).response?.status === 404
) {
return { sellerSkuId, status: "new" as const };
}
if (
error instanceof Error &&
"response" in error &&
(error as { response?: { status?: number; headers?: Record<string, string> } })
.response?.status === 429
) {
const retryAfter = parseInt(
(error as { response: { headers: Record<string, string> } }).response.headers[
"retry-after"
] || "60",
10
);
console.warn(`Rate limited. Waiting ${retryAfter}s before retry.`);
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
return { sellerSkuId, status: "error" as const };
}
throw error;
}
})
);
for (const result of batchResults) {
if (result.status === "fulfilled") {
results.push(result.value);
}
}
// Throttle between batches
if (i + concurrency < sellerSkuIds.length) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
}// WRONG: No rate limiting, no error handling, tight loop
async function notifyAllSkus(
client: AxiosInstance,
baseUrl: string,
sellerId: string,
sellerSkuIds: string[]
): Promise<void> {
// Fires all requests simultaneously — will trigger 429 rate limits
await Promise.all(
sellerSkuIds.map((sellerSkuId) =>
client.post(
`${baseUrl}/api/catalog_system/pvt/skuseller/changenotification/${sellerId}/${sellerSkuId}`
)
)
);
}async function updateSkuSuggestion(
client: AxiosInstance,
marketplaceAccount: string,
sellerId: string,
sellerSkuId: string,
updatedData: Record<string, unknown>
): Promise<boolean> {
const suggestionUrl = `https://api.vtex.com/${marketplaceAccount}/suggestions/${sellerId}/${sellerSkuId}`;
// Check current suggestion status before updating
try {
const response = await client.get(suggestionUrl);
const suggestion = response.data;
if (suggestion.Status === "Pending") {
// Safe to update — suggestion hasn't been processed yet
await client.put(suggestionUrl, updatedData);
return true;
}
// Already approved or denied — cannot update
console.warn(
`SKU ${sellerSkuId} suggestion is ${suggestion.Status}, cannot update. ` +
`Use changenotification to update existing SKUs.`
);
return false;
} catch {
// Suggestion may not exist — send as new
await client.put(suggestionUrl, updatedData);
return true;
}
}// WRONG: Blindly sending suggestion update without checking state
async function blindUpdateSuggestion(
client: AxiosInstance,
marketplaceAccount: string,
sellerId: string,
sellerSkuId: string,
data: Record<string, unknown>
): Promise<void> {
// If the suggestion was already approved, this fails silently
// or creates a duplicate that confuses the marketplace operator
await client.put(
`https://api.vtex.com/${marketplaceAccount}/suggestions/${sellerId}/${sellerSkuId}`,
data
);
}async function updateSkuSuggestion(
client: AxiosInstance,
marketplaceAccount: string,
sellerId: string,
sellerSkuId: string,
updatedData: Record<string, unknown>
): Promise<boolean> {
const suggestionUrl = `https://api.vtex.com/${marketplaceAccount}/suggestions/${sellerId}/${sellerSkuId}`;
// Check current suggestion status before updating
try {
const response = await client.get(suggestionUrl);
const suggestion = response.data;
if (suggestion.Status === "Pending") {
// Safe to update — suggestion hasn't been processed yet
await client.put(suggestionUrl, updatedData);
return true;
}
// Already approved or denied — cannot update
console.warn(
`SKU ${sellerSkuId} suggestion is ${suggestion.Status}, cannot update. ` +
`Use changenotification to update existing SKUs.`
);
return false;
} catch {
// Suggestion may not exist — send as new
await client.put(suggestionUrl, updatedData);
return true;
}
}// WRONG: Blindly sending suggestion update without checking state
async function blindUpdateSuggestion(
client: AxiosInstance,
marketplaceAccount: string,
sellerId: string,
sellerSkuId: string,
data: Record<string, unknown>
): Promise<void> {
// If the suggestion was already approved, this fails silently
// or creates a duplicate that confuses the marketplace operator
await client.put(
`https://api.vtex.com/${marketplaceAccount}/suggestions/${sellerId}/${sellerSkuId}`,
data
);
}import axios, { AxiosInstance } from "axios";
interface SellerConnectorConfig {
marketplaceAccount: string;
sellerId: string;
appKey: string;
appToken: string;
}
function createMarketplaceClient(config: SellerConnectorConfig): AxiosInstance {
return axios.create({
// Catalog System routes (e.g. changenotification) use the store host.
baseURL: `https://${config.marketplaceAccount}.vtexcommercestable.com.br`,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-VTEX-API-AppKey": config.appKey,
"X-VTEX-API-AppToken": config.appToken,
},
timeout: 10000,
});
}
// Use the same headers for PUT/GET on https://api.vtex.com/{account}/suggestions/...
// (pass a full URL on the same axios instance, or set baseURL to https://api.vtex.com/{account} for suggestion-only calls).import axios, { AxiosInstance } from "axios";
interface SellerConnectorConfig {
marketplaceAccount: string;
sellerId: string;
appKey: string;
appToken: string;
}
function createMarketplaceClient(config: SellerConnectorConfig): AxiosInstance {
return axios.create({
// Catalog System routes (e.g. changenotification) use the store host.
baseURL: `https://${config.marketplaceAccount}.vtexcommercestable.com.br`,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-VTEX-API-AppKey": config.appKey,
"X-VTEX-API-AppToken": config.appToken,
},
timeout: 10000,
});
}
// Use the same headers for PUT/GET on https://api.vtex.com/{account}/suggestions/...
// (pass a full URL on the same axios instance, or set baseURL to https://api.vtex.com/{account} for suggestion-only calls).interface CatalogNotificationResult {
skuId: string;
action: "updated" | "suggestion_sent" | "error";
error?: string;
}
async function notifyAndSync(
client: AxiosInstance,
marketplaceAccount: string,
sellerId: string,
sellerSkuId: string,
skuData: SkuSuggestion
): Promise<CatalogNotificationResult> {
const suggestionUrl = `https://api.vtex.com/${marketplaceAccount}/suggestions/${sellerId}/${sellerSkuId}`;
try {
await client.post(
`/api/catalog_system/pvt/skuseller/changenotification/${sellerId}/${sellerSkuId}`
);
// SKU exists — marketplace will call fulfillment simulation to get updates
return { skuId: sellerSkuId, action: "updated" };
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response?.status === 404) {
try {
await client.put(suggestionUrl, skuData);
return { skuId: sellerSkuId, action: "suggestion_sent" };
} catch (suggestionError: unknown) {
const message = suggestionError instanceof Error ? suggestionError.message : "Unknown error";
return { skuId: sellerSkuId, action: "error", error: message };
}
}
const message = error instanceof Error ? error.message : "Unknown error";
return { skuId: sellerSkuId, action: "error", error: message };
}
}interface CatalogNotificationResult {
skuId: string;
action: "updated" | "suggestion_sent" | "error";
error?: string;
}
async function notifyAndSync(
client: AxiosInstance,
marketplaceAccount: string,
sellerId: string,
sellerSkuId: string,
skuData: SkuSuggestion
): Promise<CatalogNotificationResult> {
const suggestionUrl = `https://api.vtex.com/${marketplaceAccount}/suggestions/${sellerId}/${sellerSkuId}`;
try {
await client.post(
`/api/catalog_system/pvt/skuseller/changenotification/${sellerId}/${sellerSkuId}`
);
// SKU exists — marketplace will call fulfillment simulation to get updates
return { skuId: sellerSkuId, action: "updated" };
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response?.status === 404) {
try {
await client.put(suggestionUrl, skuData);
return { skuId: sellerSkuId, action: "suggestion_sent" };
} catch (suggestionError: unknown) {
const message = suggestionError instanceof Error ? suggestionError.message : "Unknown error";
return { skuId: sellerSkuId, action: "error", error: message };
}
}
const message = error instanceof Error ? error.message : "Unknown error";
return { skuId: sellerSkuId, action: "error", error: message };
}
}import { RequestHandler } from "express";
interface SimulationItem {
id: string;
quantity: number;
seller: string;
}
interface SimulationRequest {
items: SimulationItem[];
postalCode?: string;
country?: string;
}
interface SimulationResponseItem {
id: string;
requestIndex: number;
quantity: number;
seller: string;
price: number;
listPrice: number;
sellingPrice: number;
priceValidUntil: string;
availability: string;
merchantName: string;
}
const fulfillmentSimulationHandler: RequestHandler = async (req, res) => {
const { items, postalCode, country }: SimulationRequest = req.body;
const responseItems: SimulationResponseItem[] = await Promise.all(
items.map(async (item, index) => {
// Fetch current price and inventory from your system
const skuInfo = await getSkuFromLocalCatalog(item.id);
return {
id: item.id,
requestIndex: index,
quantity: Math.min(item.quantity, skuInfo.availableQuantity),
seller: item.seller,
price: skuInfo.price,
listPrice: skuInfo.listPrice,
sellingPrice: skuInfo.sellingPrice,
priceValidUntil: new Date(Date.now() + 3600000).toISOString(),
availability: skuInfo.availableQuantity > 0 ? "available" : "unavailable",
merchantName: "sellerAccountName",
};
})
);
// CRITICAL: Must respond within 2.5 seconds or products show as unavailable
res.json({
items: responseItems,
postalCode: postalCode ?? "",
country: country ?? "",
});
};
async function getSkuFromLocalCatalog(skuId: string): Promise<{
price: number;
listPrice: number;
sellingPrice: number;
availableQuantity: number;
}> {
// Replace with your actual catalog/inventory lookup
return {
price: 9990,
listPrice: 12990,
sellingPrice: 9990,
availableQuantity: 15,
};
}import { RequestHandler } from "express";
interface SimulationItem {
id: string;
quantity: number;
seller: string;
}
interface SimulationRequest {
items: SimulationItem[];
postalCode?: string;
country?: string;
}
interface SimulationResponseItem {
id: string;
requestIndex: number;
quantity: number;
seller: string;
price: number;
listPrice: number;
sellingPrice: number;
priceValidUntil: string;
availability: string;
merchantName: string;
}
const fulfillmentSimulationHandler: RequestHandler = async (req, res) => {
const { items, postalCode, country }: SimulationRequest = req.body;
const responseItems: SimulationResponseItem[] = await Promise.all(
items.map(async (item, index) => {
// Fetch current price and inventory from your system
const skuInfo = await getSkuFromLocalCatalog(item.id);
return {
id: item.id,
requestIndex: index,
quantity: Math.min(item.quantity, skuInfo.availableQuantity),
seller: item.seller,
price: skuInfo.price,
listPrice: skuInfo.listPrice,
sellingPrice: skuInfo.sellingPrice,
priceValidUntil: new Date(Date.now() + 3600000).toISOString(),
availability: skuInfo.availableQuantity > 0 ? "available" : "unavailable",
merchantName: "sellerAccountName",
};
})
);
// CRITICAL: Must respond within 2.5 seconds or products show as unavailable
res.json({
items: responseItems,
postalCode: postalCode ?? "",
country: country ?? "",
});
};
async function getSkuFromLocalCatalog(skuId: string): Promise<{
price: number;
listPrice: number;
sellingPrice: number;
availableQuantity: number;
}> {
// Replace with your actual catalog/inventory lookup
return {
price: 9990,
listPrice: 12990,
sellingPrice: 9990,
availableQuantity: 15,
};
}{sellerSkuId}changenotification/{sellerId}/{sellerSkuId}async function notifyPriceChange(
client: AxiosInstance,
sellerId: string,
sellerSkuId: string
): Promise<void> {
await client.post(
`/notificator/${sellerId}/changenotification/${sellerSkuId}/price`
);
}
async function notifyInventoryChange(
client: AxiosInstance,
sellerId: string,
sellerSkuId: string
): Promise<void> {
await client.post(
`/notificator/${sellerId}/changenotification/${sellerSkuId}/inventory`
);
}
async function syncPriceAndInventory(
client: AxiosInstance,
sellerId: string,
sellerSkuIds: string[]
): Promise<void> {
for (const sellerSkuId of sellerSkuIds) {
await notifyPriceChange(client, sellerId, sellerSkuId);
await notifyInventoryChange(client, sellerId, sellerSkuId);
// Throttle to avoid rate limits
await new Promise((resolve) => setTimeout(resolve, 200));
}
}{sellerSkuId}changenotification/{sellerId}/{sellerSkuId}async function notifyPriceChange(
client: AxiosInstance,
sellerId: string,
sellerSkuId: string
): Promise<void> {
await client.post(
`/notificator/${sellerId}/changenotification/${sellerSkuId}/price`
);
}
async function notifyInventoryChange(
client: AxiosInstance,
sellerId: string,
sellerSkuId: string
): Promise<void> {
await client.post(
`/notificator/${sellerId}/changenotification/${sellerSkuId}/inventory`
);
}
async function syncPriceAndInventory(
client: AxiosInstance,
sellerId: string,
sellerSkuIds: string[]
): Promise<void> {
for (const sellerSkuId of sellerSkuIds) {
await notifyPriceChange(client, sellerId, sellerSkuId);
await notifyInventoryChange(client, sellerId, sellerSkuId);
// Throttle to avoid rate limits
await new Promise((resolve) => setTimeout(resolve, 200));
}
}import axios from "axios";
async function runCatalogSync(): Promise<void> {
const config: SellerConnectorConfig = {
marketplaceAccount: "mymarketplace",
sellerId: "externalseller01",
appKey: process.env.VTEX_APP_KEY!,
appToken: process.env.VTEX_APP_TOKEN!,
};
const client = createMarketplaceClient(config);
// Fetch SKUs that need syncing from your system
const skusToSync = await getLocalSkusNeedingSync();
for (const sku of skusToSync) {
const skuSuggestion: SkuSuggestion = {
ProductName: sku.productName,
SkuName: sku.skuName,
ImageUrl: sku.imageUrl,
ProductDescription: sku.description,
BrandName: sku.brand,
CategoryFullPath: sku.categoryPath,
EAN: sku.ean,
Height: sku.height,
Width: sku.width,
Length: sku.length,
WeightKg: sku.weight,
SkuSpecifications: sku.specifications,
};
const result = await notifyAndSync(
client,
config.marketplaceAccount,
config.sellerId,
sku.sellerSkuId,
skuSuggestion
);
console.log(`SKU ${sku.sellerSkuId}: ${result.action}`);
// Throttle between SKU operations
await new Promise((resolve) => setTimeout(resolve, 200));
}
// Sync prices and inventory for all active SKUs (seller SKU IDs)
const activeSellerSkuIds = skusToSync.map((s) => s.sellerSkuId);
await syncPriceAndInventory(client, config.sellerId, activeSellerSkuIds);
}
async function getLocalSkusNeedingSync(): Promise<
Array<{
sellerSkuId: string;
productName: string;
skuName: string;
imageUrl: string;
description: string;
brand: string;
categoryPath: string;
ean: string;
height: number;
width: number;
length: number;
weight: number;
specifications: Array<{ FieldName: string; FieldValues: string[] }>;
}>
> {
// Replace with your actual data source
return [];
}import axios from "axios";
async function runCatalogSync(): Promise<void> {
const config: SellerConnectorConfig = {
marketplaceAccount: "mymarketplace",
sellerId: "externalseller01",
appKey: process.env.VTEX_APP_KEY!,
appToken: process.env.VTEX_APP_TOKEN!,
};
const client = createMarketplaceClient(config);
// Fetch SKUs that need syncing from your system
const skusToSync = await getLocalSkusNeedingSync();
for (const sku of skusToSync) {
const skuSuggestion: SkuSuggestion = {
ProductName: sku.productName,
SkuName: sku.skuName,
ImageUrl: sku.imageUrl,
ProductDescription: sku.description,
BrandName: sku.brand,
CategoryFullPath: sku.categoryPath,
EAN: sku.ean,
Height: sku.height,
Width: sku.width,
Length: sku.length,
WeightKg: sku.weight,
SkuSpecifications: sku.specifications,
};
const result = await notifyAndSync(
client,
config.marketplaceAccount,
config.sellerId,
sku.sellerSkuId,
skuSuggestion
);
console.log(`SKU ${sku.sellerSkuId}: ${result.action}`);
// Throttle between SKU operations
await new Promise((resolve) => setTimeout(resolve, 200));
}
// Sync prices and inventory for all active SKUs (seller SKU IDs)
const activeSellerSkuIds = skusToSync.map((s) => s.sellerSkuId);
await syncPriceAndInventory(client, config.sellerId, activeSellerSkuIds);
}
async function getLocalSkusNeedingSync(): Promise<
Array<{
sellerSkuId: string;
productName: string;
skuName: string;
imageUrl: string;
description: string;
brand: string;
categoryPath: string;
ean: string;
height: number;
width: number;
length: number;
weight: number;
specifications: Array<{ FieldName: string; FieldValues: string[] }>;
}>
> {
// Replace with your actual data source
return [];
}POST .../changenotification/{skuId}POST .../changenotification/{sellerId}/{sellerSkuId}sellerIdimport { RequestHandler } from "express";
// Correct: Cache-first approach for fast fulfillment simulation
const cachedPriceInventory = new Map<string, {
price: number;
listPrice: number;
sellingPrice: number;
availableQuantity: number;
updatedAt: number;
}>();
const fastFulfillmentSimulation: RequestHandler = async (req, res) => {
const { items } = req.body;
const responseItems = items.map((item: SimulationItem, index: number) => {
const cached = cachedPriceInventory.get(item.id);
if (!cached) {
return {
id: item.id,
requestIndex: index,
quantity: 0,
availability: "unavailable",
};
}
return {
id: item.id,
requestIndex: index,
quantity: Math.min(item.quantity, cached.availableQuantity),
price: cached.price,
listPrice: cached.listPrice,
sellingPrice: cached.sellingPrice,
availability: cached.availableQuantity > 0 ? "available" : "unavailable",
};
});
// Responds in < 50ms from cache
res.json({ items: responseItems });
};POST .../changenotification/{skuId}POST .../changenotification/{sellerId}/{sellerSkuId}sellerIdimport { RequestHandler } from "express";
// Correct: Cache-first approach for fast fulfillment simulation
const cachedPriceInventory = new Map<string, {
price: number;
listPrice: number;
sellingPrice: number;
availableQuantity: number;
updatedAt: number;
}>();
const fastFulfillmentSimulation: RequestHandler = async (req, res) => {
const { items } = req.body;
const responseItems = items.map((item: SimulationItem, index: number) => {
const cached = cachedPriceInventory.get(item.id);
if (!cached) {
return {
id: item.id,
requestIndex: index,
quantity: 0,
availability: "unavailable",
};
}
return {
id: item.id,
requestIndex: index,
quantity: Math.min(item.quantity, cached.availableQuantity),
price: cached.price,
listPrice: cached.listPrice,
sellingPrice: cached.sellingPrice,
availability: cached.availableQuantity > 0 ? "available" : "unavailable",
};
});
// Responds in < 50ms from cache
res.json({ items: responseItems });
};.../changenotification/{sellerId}/{sellerSkuId}PUTGEThttps://api.vtex.com/{account}/suggestions/{sellerId}/{sellerSkuId}/notificator/{sellerId}/changenotification/{sellerSkuId}/price|inventory.../changenotification/{sellerId}/{sellerSkuId}PUTGEThttps://api.vtex.com/{account}/suggestions/{sellerId}/{sellerSkuId}/notificator/{sellerId}/changenotification/{sellerSkuId}/price|inventoryPOST .../changenotification/{skuId}POST .../changenotification/{sellerId}/{skuId}PUT /suggestions/{sellerId}/{sellerSkuId}https://api.vtex.com/{accountName}POST .../changenotification/{skuId}POST .../changenotification/{sellerId}/{skuId}PUT /suggestions/{sellerId}/{sellerSkuId}https://api.vtex.com/{accountName}