Ads

Product catalogs and catalog ads

Build and fill a Meta product catalog, group products into sets, and run catalog ads that show each person the products they are most likely to buy.

A catalog ad is one ad that covers many products. Instead of uploading an image per item, you point the ad at a product set and Meta renders a card for each product, then picks which ones each person sees based on what they browsed. Pozy manages the catalog behind it too: create a catalog, add products one at a time, in bulk or from a feed file, and define product sets.

Catalogs and catalog ads run on Meta (Facebook and Instagram). The same catalog can also power Facebook and Instagram Shops and a WhatsApp Business catalog.

#Run a catalog ad

You need a Meta ads connection, an ad account with a pixel, and a catalog with at least one product set. If the catalog already exists in Meta Commerce Manager, start at step 1. To build one from scratch, see Build a catalog first.

#Find the catalog

GET /v1/ads/catalogs lists the catalogs the ad account's business owns or has been shared.

cURL
curl "https://api.pozy.ai/v1/ads/catalogs?accountId=66b2e19d8c3f5a7e9d0b1c2e&adAccountId=act_1234567890" \
  -H "Authorization: Bearer $POZY_API_KEY"
Response
{
  "businessId": "1092837465019283",
  "catalogs": [
    { "id": "1003405825408877", "name": "Home store", "vertical": "commerce", "productCount": 412, "businessId": "1092837465019283" }
  ]
}

The business is resolved from the first of these you give: businessId, the owner of adAccountId, the owner of a WhatsApp Business Account (when accountId is a WhatsApp connection), or the only business the Meta login can see.

#Pick the product set

The ad promotes a product set, not the whole catalog. List the sets with GET /v1/ads/catalogs/{catalogId}/product-sets:

Response
{ "productSets": [{ "id": "1003260032095191", "name": "All products", "productCount": 412 }] }

#Create the ad

Send goal: "catalog_sales" with all three promotedObject fields. There is no imageUrl or video: Meta builds the visuals from each product, and your copy becomes a template filled per item.

curl -X POST https://api.pozy.ai/v1/ads/create \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: home-store-catalog-q1" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c2e",
    "adAccountId": "act_1234567890",
    "name": "Home store, catalog",
    "goal": "catalog_sales",
    "budgetAmount": 40,
    "budgetType": "daily",
    "headline": "{{product.name}}",
    "body": "Free shipping on orders over $50",
    "description": "{{product.price}}",
    "callToAction": "SHOP_NOW",
    "linkUrl": "https://shop.example.com",
    "countries": ["US"],
    "optimizationGoal": "OFFSITE_CONVERSIONS",
    "promotedObject": {
      "productSetId": "1003260032095191",
      "pixelId": "1729525464415281",
      "customEventType": "PURCHASE"
    }
  }'

The response is a sales campaign (platformObjective: "OUTCOME_SALES"), an ad set bound to the product set, and a catalog template ad, starting in pending_review.

Set the optimization goal

Without optimizationGoal: "OFFSITE_CONVERSIONS", the ad set optimizes for link clicks, which buys clicks for a campaign you meant to drive purchases. Send it, and the ad set optimizes toward the customEventType in promotedObject.

#Template tags

Copy fields accept Meta's catalog tags, replaced per product: {{product.name}}, {{product.price}}, and on a vehicle catalog {{vehicle.make}}, {{vehicle.model}}, {{vehicle.year}} and {{vehicle.price}}.

catalogFormat picks the layout: single_image, carousel_images_multi_items, carousel_images_single_item or carousel_slideshows.

#Rules for catalog ads

  • promotedObject.productSetId, pixelId and customEventType are all required. Meta's "Promoted Object is Required" error usually means the pixel is missing, not the set.
  • Pozy checks the product set before creating anything. Passing the catalog id where the set id belongs, or a set the login cannot read, returns 400 naming promotedObject.productSetId.
  • A catalog ad is one creative. It cannot be combined with creatives[], adSetId, dynamicCreative or placementAssets.
  • Targeting works as on any campaign. Broad targeting is fine here because Meta matches products to people.
  • Instagram placements need an Instagram identity. When the Page has no linked Instagram account, Pozy uses the Page-backed one.
  • Creating the ad from a known product set id does not need catalog permissions. Reading and editing the catalog does (below).

#Build a catalog

#Permission

Listing, creating and editing catalogs needs Meta's catalog_management permission on the Meta login. Not every login has it. When the connection you use cannot manage catalogs, Meta answers 403 (code 100). Pass catalogAccountId naming another connected facebook, instagram or metaads account whose Meta login does.

#Create the catalog

cURL
curl -X POST https://api.pozy.ai/v1/ads/catalogs \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "accountId": "66b2e19d8c3f5a7e9d0b1c2e", "adAccountId": "act_1234567890", "name": "Home store", "vertical": "commerce" }'

vertical defaults to commerce. Others include vehicles, hotels, flights, destinations, home_listings, local_service_businesses and services. Deleting a catalog (DELETE /v1/ads/catalogs/{catalogId}) removes every product and takes it off any ads and WhatsApp numbers that use it.

#Add products

Products are keyed by retailerId, your SKU. It is your handle for later lookups and updates, and it cannot change.

Way in Call Good for
One product POST /v1/ads/catalogs/{catalogId}/products Adding items as they go live
Bulk POST /v1/ads/catalogs/{catalogId}/products/batch Up to 5,000 creates, updates or deletes in one request
Feed file POST /v1/ads/catalogs/{catalogId}/feeds Keeping a large catalog in sync from a CSV, TSV or XML file
Bulk create and update
curl -X POST https://api.pozy.ai/v1/ads/catalogs/1003405825408877/products/batch \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c2e",
    "requests": [
      { "method": "CREATE", "product": {
          "retailerId": "sku-981", "name": "Linen throw", "url": "https://shop.example.com/p/linen-throw",
          "imageUrl": "https://cdn.example.com/linen-throw.jpg", "price": 64.99, "currency": "USD",
          "availability": "in stock", "condition": "new", "brand": "Acme Home" } },
      { "method": "UPDATE", "product": { "retailerId": "sku-772", "salePrice": 39.00, "inventory": 12 } },
      { "method": "DELETE", "product": { "retailerId": "sku-105" } }
    ]
  }'

The bulk call answers 202 with handles. Meta processes them in the background; poll GET /v1/ads/catalogs/{catalogId}/batches/{handle} for the status and per-product errors. A CREATE needs name, url, imageUrl, price and currency.

Product fields include description, additionalImageUrls, salePrice with salePriceStartDate and salePriceEndDate, availability, condition, brand, category, googleProductCategory, gtin, mpn, inventory, visibility (published or staging), color, size, gender, material, pattern and customLabel0 to customLabel4. GET .../products?retailerId=sku-981 looks one product up by SKU; prices come back formatted by Meta, such as "$64.99".

#Keep it in sync with a feed

A feed pulls a product file from a URL. With schedule, Meta fetches it on a cadence; without it, trigger each fetch yourself.

Create a feed
{
  "accountId": "66b2e19d8c3f5a7e9d0b1c2e",
  "name": "Nightly product export",
  "schedule": { "interval": "DAILY", "url": "https://shop.example.com/exports/products.csv", "hour": 3 }
}

interval is HOURLY, DAILY or WEEKLY (with dayOfWeek). POST .../feeds/{feedId}/uploads with a url fetches a file right away, and GET .../feeds/{feedId}/uploads shows each import with itemsDetected, itemsDeleted, errorCount and warningCount.

If your store is connected to Pozy, you can read its products from the commerce endpoints and push them in with the bulk call, or point a feed at the store's own product export.

#Group products into sets

A product set is a saved filter over the catalog. It uses Meta's filter syntax:

Create a product set
{
  "accountId": "66b2e19d8c3f5a7e9d0b1c2e",
  "name": "Acme brand, in stock",
  "filter": { "and": [ { "brand": { "i_contains": "acme" } }, { "availability": { "eq": "in stock" } } ] }
}

A filter on SKUs looks like { "retailer_id": { "is_any": ["sku-981", "sku-772"] } }. Update a set's name or filter with PUT .../product-sets/{productSetId} and remove it with DELETE. Pass the set's id, never the catalog's, as promotedObject.productSetId.

#Sell in WhatsApp too

The same catalog can back a WhatsApp Business number. Link it with POST /v1/whatsapp/catalogs; the catalog must belong to the same business as the WhatsApp Business Account, and catalogAccountId must name a Meta login with catalog_management. See WhatsApp.

#Errors you will see

Status and code Cause Fix
403 (Meta code 100) The Meta login lacks catalog_management Pass catalogAccountId for a login that has it.
400 naming promotedObject.productSetId The catalog id was sent as the set id, or the set is unreadable Re-read ids from the product sets list.
400 naming promotedObject.productCatalogId The catalog does not contain that set Send the catalog that owns the set, or leave it out.
400 naming creatives creatives[] with goal: "catalog_sales" Send one creative.
Meta "Promoted Object is Required" The pixel is missing Add promotedObject.pixelId.
Errors in a batch status Products Meta refused, listed by retailerId Fix those products and resend them.

#Endpoints