> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apostra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Add products

> Add discovered products to a discovery session's selection

`POST /api/v2/buyer/discovery/{discoveryId}/products`

Adds products to the session selection. Pass the `productId` from your [Discover products](/v2/buyer/discovery/tasks/discover-products) or [Browse products](/v2/buyer/discovery/tasks/browse-products) results. When a result includes `inventorySourceId`, preserve it in the selection so identical product IDs from different inventory sources remain unambiguous. `salesAgentId`, `groupId`, and `groupName` are optional and resolved server-side from those results, so pass them only to disambiguate a product that appears in more than one group. Optionally pin a budget, pricing option, bid, or per-line-item targeting. By default products merge into the existing selection; set `replace: true` to swap the whole set.

When you pass `pricingOptionId`, Apostra validates it against the exact pricing
in the session's current discovery result and carries that option into campaign
staging. A separate catalog refresh cannot invalidate an option that result
just returned. Re-run discovery when the option is no longer present in the
current result or its quoted price has expired. Older sessions whose results
predate pricing evidence recording use current catalog validation and may also
require a new discovery run.

To select the same product more than once, omit `selectionId` for its single base selection and give each repeat a distinct `selectionId`. Each selection becomes an independent media-buy line item. Re-sending an existing `selectionId` updates that instance instead of adding another copy.

## Request

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.apostra.com/api/v2/buyer/discovery/disc_01HZX3YQ7K9R6V3M2P1E0F8B2T/products \
    -H "Authorization: Bearer $SCOPE3_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "products": [
        {
          "productId": "prod_acme_ctv_sports",
          "salesAgentId": "agent_acme_media",
          "groupId": "ctx_disc_01HZX3-group-0",
          "groupName": "Acme Media Connected TV",
          "pricingOptionId": "po_fixed_30",
          "budget": 150000
        }
      ]
    }'
  ```

  ```json Non-fixed pricing with a bid theme={null}
  {
    "products": [
      {
        "productId": "prod_example_olv_open",
        "salesAgentId": "agent_example_media",
        "groupId": "ctx_disc_01HZX3-group-1",
        "groupName": "Example Media CTV",
        "bidPrice": 12.5,
        "budget": 50000
      }
    ]
  }
  ```

  ```json Sponsored products theme={null}
  {
    "products": [
      {
        "productId": "prod_retailer_sponsored_products",
        "salesAgentId": "agent_retailer_media",
        "groupId": "ctx_disc_01HZX3-group-2",
        "groupName": "Retailer Sponsored Products",
        "pricingOptionId": "po_cpc_auction",
        "bidPrice": 1.25,
        "budget": 10000,
        "targetingOverlay": {
          "product_codes": ["sku-coffee-250g", "sku-coffee-1kg"],
          "keyword_targets": [
            { "keyword": "ground coffee", "match_type": "exact" },
            { "keyword": "arabica coffee", "match_type": "phrase" }
          ],
          "negative_keywords": [
            { "keyword": "instant coffee", "match_type": "exact" }
          ]
        }
      }
    ]
  }
  ```

  ```json Repeat the same product theme={null}
  {
    "products": [
      {
        "productId": "prod_acme_ctv_sports",
        "pricingOptionId": "po_fixed_30",
        "budget": 90000,
        "targetingOverlay": { "geo_countries": ["US"] }
      },
      {
        "productId": "prod_acme_ctv_sports",
        "selectionId": "prod_acme_ctv_sports::uk-flight",
        "pricingOptionId": "po_fixed_30",
        "budget": 60000,
        "targetingOverlay": { "geo_countries": ["GB"] }
      }
    ]
  }
  ```
</CodeGroup>

## Parameters

| Field                             | Type    | Required      | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| --------------------------------- | ------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `products`                        | array   | Yes           | Products to add (min 1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `products[].productId`            | string  | Yes           | Product ID from the discovery results                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `products[].inventorySourceId`    | string  | When returned | Typed inventory-source ID from the discovery result. Preserve it when present to distinguish identical product IDs from different sources.                                                                                                                                                                                                                                                                                                                                                                                                |
| `products[].selectionId`          | string  | No            | Per-instance line-item identity. Omit for the product's base selection. Use a distinct value to add the same product again; re-send the same value to update that instance.                                                                                                                                                                                                                                                                                                                                                               |
| `products[].salesAgentId`         | string  | No            | Sales agent the product came from. Resolved server-side from your discovery results when omitted; pass to disambiguate.                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `products[].groupId`              | string  | No            | Group the product was discovered in. Resolved server-side from your discovery results when omitted.                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `products[].groupName`            | string  | No            | Name of that group. Resolved server-side from your discovery results when omitted.                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `products[].pricingOptionId`      | string  | No            | Pricing option ID from the product's `pricingOptions`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `products[].bidPrice`             | number  | No            | Bid for non-fixed options (`isFixed: false`); read from `pricingOptions[].rate` or `floorPrice`                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `products[].budget`               | number  | No            | Budget allocation for this product                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `products[].targetingOverlay`     | object  | No            | Per-line-item AdCP targeting. For sponsored products, use source-supported fields such as promoted product codes and keyword targets.                                                                                                                                                                                                                                                                                                                                                                                                     |
| `products[].pageId`               | string  | No            | Meta Facebook Page ID. Required for Meta products when the connected ad account has more than one authorized Page — pass one of the page IDs listed in the error message. Omit when the account has exactly one authorized page (the server selects it automatically). Fixed at ad creation; cannot change after execution.                                                                                                                                                                                                               |
| `products[].pixelId`              | string  | No            | Meta Pixel / Dataset ID for conversion tracking. Required for Meta Sales (OUTCOME\_SALES) products — pass the pixel ID listed in the error message. There is no auto-select: every Meta Sales buy must explicitly specify a pixel. Fixed at ad creation; cannot change after execution.                                                                                                                                                                                                                                                   |
| `products[].instagramActorId`     | string  | No            | Meta Instagram account ID. Required for Instagram Direct destinations (Instagram Direct Conversations, Instagram-based Lead Generation) when the connected ad account has more than one authorized Instagram account. Omit when the account has exactly one (the server selects it automatically). Fixed at ad creation; cannot change after execution.                                                                                                                                                                                   |
| `products[].welcomeMessageFlowId` | string  | No            | Meta welcome message flow ID. Required for lead-to-message destinations (Messenger, Instagram Direct, and WhatsApp Lead Generation) — pass one of the welcome-message-flow candidates returned by account resource readiness in this response. Candidates only appear on a catalog-style `discover_products` call with no `brief` (a `buying_mode: "wholesale"` request that still includes a `brief` does not surface them). There is no auto-select, even with a single candidate. Fixed at ad creation; cannot change after execution. |
| `replace`                         | boolean | No            | When `true`, replaces all existing products instead of merging                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |

## Response

```json theme={null}
{
  "discoveryId": "disc_01HZX3YQ7K9R6V3M2P1E0F8B2T",
  "products": [
    {
      "productId": "prod_acme_ctv_sports",
      "salesAgentId": "agent_acme_media",
      "bidPrice": 32.5,
      "budget": 150000,
      "groupId": "ctx_disc_01HZX3-group-0",
      "groupName": "Acme Media Connected TV",
      "selectedAt": "2026-06-07T15:04:00Z"
    }
  ],
  "totalProducts": 1,
  "budgetContext": {
    "sessionBudget": 250000,
    "allocatedBudget": 150000,
    "remainingBudget": 100000
  }
}
```

Returns the full selection after the add, so you do not need a separate [Get products](/v2/buyer/discovery/tasks/get-products) call to confirm.

Repeat instances return their `selectionId`; the base instance omits it. Keep that value when updating or removing one repeat. Reconciliation preserves repeat instances as distinct line items, so their budget and targeting do not collapse into the base selection.

## Errors

* `400 VALIDATION_ERROR` — empty `products`, missing a required selection field, or a `productId` not present in the session's discovery results.
* `404 NOT_FOUND` — `discoveryId` does not exist or is not visible to the authenticated account.

See [Errors](/v2/reference/errors) for the full error contract.

## Related

<CardGroup cols={2}>
  <Card title="Discovery overview" href="/v2/guides/discovery" icon="rocket">
    Selection model and budget allocation
  </Card>

  <Card title="Get products" href="/v2/buyer/discovery/tasks/get-products" icon="list">
    List the current selection
  </Card>

  <Card title="Remove products" href="/v2/buyer/discovery/tasks/remove-products" icon="trash">
    Drop products from the selection
  </Card>

  <Card title="Apply proposal" href="/v2/buyer/discovery/tasks/apply-proposal" icon="wand-magic-sparkles">
    Add a whole proposal at once
  </Card>
</CardGroup>
