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

# Create proposal

> Freeze a discovery offer against a buyer and mint a shareable proposal code

`POST /api/v2/storefront/proposals`

Saves a discover-products response against a buyer (operator) binding and mints a shareable `proposalCode`. The server reads the cached snapshot for the named discovery session, freezes the chosen proposal as the offer, and binds it to the operator. The buyer redeems the code on `discover_products` to receive the exact frozen products and proposals. The proposal is created in `active` status.

## Request

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.interchange.io/api/v2/storefront/proposals \
    -H "Authorization: Bearer $SCOPE3_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "operatorId": { "domain": "acme.com" },
      "label": "Acme Q3 RFP — CTV",
      "notes": "Offline RFP follow-up; honors the upfront rate.",
      "expiresAt": "2026-09-30T23:59:59Z",
      "discoveryId": "disc_a1b2c3",
      "proposalId": "proposal_abc123"
    }'
  ```

  ```json body theme={null}
  {
    "operatorId": "acme.com",
    "operatorRef": { "domain": "acme.com" },
    "label": "Acme Q3 RFP — CTV",
    "notes": "Offline RFP follow-up; honors the upfront rate.",
    "expiresAt": "2026-09-30T23:59:59Z",
    "discoveryId": "disc_a1b2c3",
    "proposalId": "proposal_abc123"
  }
  ```
</CodeGroup>

## Parameters

| Field         | Type             | Required | Notes                                                                                                                                                                          |
| ------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `operatorId`  | string or object | Yes      | Buyer binding for redemption. Prefer `{ "domain": "brand.example" }`; legacy strings are treated as domain bindings, and `customer:<id>` is decoded as `{ "customerId": id }`. |
| `label`       | string           | Yes      | Seller's internal label for the proposal (1..255 chars)                                                                                                                        |
| `expiresAt`   | string           | Yes      | When the code expires (ISO 8601). Must be no later than the underlying `proposal.expiresAt`                                                                                    |
| `discoveryId` | string           | Yes      | Discovery session ID from a recent `discover_products` call. Must be scoped to this seller and restricted to its storefront sales agents (1..255 chars)                        |
| `proposalId`  | string           | Yes      | Which proposal from the discovery snapshot to freeze and bind to the code (1..255 chars)                                                                                       |
| `notes`       | string           | No       | Free-form notes about the offline RFP (max 4000 chars)                                                                                                                         |

## Response

`201 Created` with the new proposal:

```json theme={null}
{
  "id": 318,
  "proposalCode": "PRP-XK4A29",
  "operatorId": "acme.com",
  "operatorRef": { "domain": "acme.com" },
  "label": "Acme Q3 RFP — CTV",
  "notes": "Offline RFP follow-up; honors the upfront rate.",
  "status": "active",
  "expiresAt": "2026-09-30T23:59:59Z",
  "proposalId": "proposal_abc123",
  "discoverySessionId": "disc_a1b2c3",
  "snapshot": { "products": [], "proposals": [] },
  "createdAt": "2026-06-07T15:00:00Z",
  "createdBy": "seller@acme-media.example.com",
  "firstViewedAt": null,
  "lastViewedAt": null,
  "redeemedAt": null,
  "redeemedInMediaBuyId": null
}
```

`proposalCode` is the short handle you hand to the buyer; they pass it to `discover_products` to redeem. `id` is the numeric row ID you pass to every sibling operation. `snapshot` holds the frozen `products[]` and `proposals[]` returned to the buyer on redemption.

## Errors

* `400 VALIDATION_ERROR` — missing required field, `expiresAt` later than the underlying `proposal.expiresAt`, or a `discoveryId` not scoped to this seller's storefront sales agents.

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

## Related

<CardGroup cols={2}>
  <Card title="Proposal tasks" href="/v2/storefront/proposals/tasks" icon="list-check">
    All proposal operations
  </Card>

  <Card title="Proposals overview" href="/v2/storefront/proposals/overview" icon="file-signature">
    Lifecycle and concepts
  </Card>

  <Card title="Get proposal" href="/v2/storefront/proposals/tasks/get-proposal" icon="magnifying-glass">
    Read back the saved snapshot
  </Card>

  <Card title="Revoke proposal" href="/v2/storefront/proposals/tasks/revoke-proposal" icon="ban">
    Withdraw a code before redemption
  </Card>
</CardGroup>
