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

# Partner endpoints

> Partner-callable endpoints under /v2 for managing partner contexts, cohort pricing, cohort widget configs, quotes, and merchant-scoped resources.

This page lists every endpoint your app can call with an OAuth access token. Each entry includes the route, the scopes it requires, the request shape, and the response shape.

<Note>
  Routes documented here are versioned under `/v2`. The `/v1` quote endpoint remains available for the public widget and is documented at the end of this page. The `/v1` OAuth and install routes partners also call (`/v1/oauth/token`, `/v1/oauth/authorize/approve`, `/v1/auth/login`) are documented in [Installing Your App](/developer/installing-your-app) and the [API Reference](/developer/api-reference); the rest of `/v1` is dashboard-only. The **public, unauthenticated** cohort endpoints the checkout extension calls (`/v2/quote/insurance/partner/:applicationId` and `/v2/widget/config/partner/:applicationId`) are documented in the [Checkout Extension guide](/developer/checkout-extension#what-the-extension-fetches).
</Note>

All requests must include a Bearer access token from the [OAuth flow](/developer/authentication):

```bash theme={null}
Authorization: Bearer op_at_...
```

<Note>
  Authenticated `/v2` endpoints wrap every success response in a standard envelope:

  ```json theme={null}
  { "data": { "...": "payload" }, "status": 200 }
  ```

  The response examples on this page show the **`data` payload only**. The public reads (`/v1/quote/insurance` and the `/v2/.../partner/:applicationId` endpoints) are not wrapped.
</Note>

## Base URL

```
https://api.production.orderprotection.com
```

## At a glance

| Resource | Routes | Required scopes |
| - | - | - |
| [Partner contexts](#partner-contexts) | `POST/GET/PATCH/DELETE /v2/settings/context` | `store:read`, `partner-context:update` |
| [Cohort pricing](#cohort-pricing) | `POST/GET/PUT/DELETE /v2/settings/context/:contextId/pricing` | `partner-context:update` + per ruleset type — see below |
| [Cohort widget configs](#cohort-widget-configs) | `POST/GET/PUT/DELETE /v2/widget/context/:contextId/config` | `partner-context:update` + per widget type — see below |
| [Merchant settings](#merchant-settings) | `GET /v2/settings` | `store:read` |
| [Pricing rules (merchant)](#pricing-rules-merchant) | `GET /v2/pricing/rules`, `PUT /v2/pricing/rules/:id` | per ruleset type — see below |
| [Quote](#quote) | `POST /v2/quote` | `quote:read` |
| [Widget config (merchant)](#widget-config-merchant) | `GET /v2/widget/config`, `PUT /v2/widget/config/:id` | per widget type — see below |
| [Public widget read](#public-widget-read-with-cohort) | `GET /v1/quote/insurance` | none (public; uses cohort headers) |

***

## Partner contexts

A **partner context** is your app's installation container on a store — **one per (store, app) pair**. It holds optional sparse overrides on the merchant's default settings (fields you don't override are inherited), and your A/B cohorts hang off it as child resources keyed by a `variantKey` you choose: [cohort price sets](#cohort-pricing) and [cohort widget configs](#cohort-widget-configs).

This is the primitive that powers parallel partner A/B testing without disturbing the merchant's defaults.

### Create a context

```
POST /v2/settings/context
```

Required scope: `partner-context:update`

**Request body:**

| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Human label, 1–128 chars. Shown in dashboards. |

**Example:**

```bash theme={null}
curl -X POST https://api.production.orderprotection.com/v2/settings/context \
  -H "Authorization: Bearer op_at_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "My Platform"}'
```

**Response `201 Created`:**

```json theme={null}
{
  "id": "cmokjb8bq000663gkhjp5ppoj",
  "applicationId": "cmpn5in6p000484flmfk9ns4d",
  "name": "My Platform",
  "createdAt": "2026-04-28T18:22:11.000Z",
  "updatedAt": null
}
```

Creating a second context on the same store returns `409 Conflict` — there is one context per (store, app). Fetch the existing one with the list endpoint below.

***

### List your contexts

```
GET /v2/settings/context
```

Required scope: `store:read`

Returns only the contexts owned by the calling OAuth app for this store.

**Response `200 OK`:**

```json theme={null}
[
  {
    "id": "cmokjb8bq000663gkhjp5ppoj",
    "applicationId": "cmpn5in6p000484flmfk9ns4d",
    "name": "My Platform",
    "createdAt": "2026-04-28T18:22:11.000Z",
    "updatedAt": null
  }
]
```

***

### Get a context (with merged settings for a cohort)

```
GET /v2/settings/context/:id?variantKey=cohort-a
```

Required scope: `store:read`

Returns the context metadata plus the **resolved** settings and pricing rules for the supplied cohort — the same view a shopper assigned to that `variantKey` would see. Merchant defaults are layered first; partner overrides for non-null fields are layered on top. If the cohort has its own price sets they're returned; otherwise the merchant's rule sets are.

| Query param | Required | Description |
| - | - | - |
| `variantKey` | Yes | Which cohort's pricing to resolve. Must match `^[a-zA-Z0-9_.:-]{1,64}$`. |

**Response `200 OK`:**

```json theme={null}
{
  "id": "cmokjb8bq000663gkhjp5ppoj",
  "applicationId": "cmpn5in6p000484flmfk9ns4d",
  "variantKey": "cohort-a",
  "name": "My Platform",
  "createdAt": "2026-04-28T18:22:11.000Z",
  "updatedAt": "2026-04-28T18:24:02.000Z",
  "resolvedSettings": {
    "enableCancelAfterPurchase": true,
    "cancelAfterPurchaseDurationSeconds": 86400,
    "sendConfirmationEmail": true,
    "offerStoreCreditOnOpPurchase": false,
    "storeCreditType": null,
    "storeCreditBonus": null,
    "storeCreditBonusType": null,
    "storeCreditMaxPercentage": null,
    "..." : "all merchant GeneralSetting fields"
  },
  "resolvedPricingRules": [
    {
      "id": "rs_xxx",
      "type": "SHIPPING",
      "automaticallyAddToCart": true,
      "autoAddStateBlockList": [],
      "autoAddCountryBlockList": [],
      "rules": [
        { "id": "pr_a", "minValue": 0, "maxValue": 100, "...": "..." }
      ]
    }
  ]
}
```

<Note>
  This endpoint does **not** consult the OAuth-app installation off-switch. The OAuth gate already authorises the calling app to read its own context. The off-switch only fires on the public widget read path.
</Note>

***

### Update a context

```
PATCH /v2/settings/context/:id?variantKey=cohort-a
```

Required scope: `partner-context:update` (+ `store-credit:update` if any store-credit field is present)

Every field is optional. **Sparse semantics:** fields you omit stay inherited from the merchant; fields you send become partner overrides. Overrides are **installation-wide** (one context row per store + app) — the `variantKey` query param only selects which cohort's pricing the response renders.

**Request body:**

| Field | Type | Description |
| - | - | - |
| `name` | string | New label (1–128 chars). |
| `enableCancelAfterPurchase` | boolean | Override the cancel-after-purchase toggle. |
| `cancelAfterPurchaseDurationSeconds` | number | Override the cancel-after-purchase window (seconds, ≥ 0). |
| `sendConfirmationEmail` | boolean | Override the confirmation-email toggle. |
| `sendSmsUpdates` | boolean | Override the SMS-updates toggle. |
| `offerStoreCreditOnOpPurchase` | boolean | Toggle store credit. **Requires** `store-credit:update`. |
| `storeCreditType` | enum | One of the `StoreCreditType` values. **Requires** `store-credit:update`. |
| `storeCreditBonus` | number | Bonus amount (≥ 0). **Requires** `store-credit:update`. |
| `storeCreditBonusType` | enum | One of the `CalcMethod` values (`fixed`, `percentage`). **Requires** `store-credit:update`. |
| `storeCreditMaxPercentage` | number | Cap (0–100). **Requires** `store-credit:update`. |

**Example:**

```bash theme={null}
curl -X PATCH "https://api.production.orderprotection.com/v2/settings/context/cmokjb8bq000663gkhjp5ppoj?variantKey=cohort-a" \
  -H "Authorization: Bearer op_at_..." \
  -H "Content-Type: application/json" \
  -d '{"enableCancelAfterPurchase": true, "cancelAfterPurchaseDurationSeconds": 86400}'
```

**Response `200 OK`:** Same shape as `GET /v2/settings/context/:id`.

***

### Delete a context

```
DELETE /v2/settings/context/:id
```

Required scope: `partner-context:update`

Soft-deletes the context. Future shopper traffic resolving against any `variantKey` for this (store, app) falls back to merchant defaults at the resolver.

**Response `200 OK`:**

```json theme={null}
{ "ok": true }
```

***

## Cohort pricing

Per-cohort price rule sets, attached to your partner context and keyed by `variantKey`. On the [header-based v1 read path](#public-widget-read-with-cohort), a cohort with no partner price set falls back to the merchant's default pricing; the path-based `/v2/.../partner/...` endpoints return `404` instead (the checkout extension treats that as "no cohort" and renders the store default).

All writes require the base scope `partner-context:update` **plus** the per-type scope below. Reads require the per-type read scope.

| Type | Read scope | Write scope |
| - | - | - |
| `SHIPPING` | `pricing:shipping-insurance:read` | `pricing:shipping-insurance:update` |
| `EXTENDED_WARRANTY` | `pricing:extended-warranty:read` | `pricing:extended-warranty:update` |

<Note>
  All pricing scopes are **admin-gated** — see [Scopes → Price Settings](/developer/scopes#price-settings-admin-gated).
</Note>

### Create a cohort price set

```
POST /v2/settings/context/:contextId/pricing
```

**Request body:**

| Field | Type | Required | Description |
| - | - | - | - |
| `type` | enum | Yes | `SHIPPING` or `EXTENDED_WARRANTY`. **Immutable** after create. |
| `variantKey` | string | Yes | Your cohort key. Must match `^[a-zA-Z0-9_.:-]{1,64}$`. **Immutable** after create. |
| `rules` | array | Yes | Price rules — see shape below. |
| `automaticallyAddToCart` | boolean | No | Auto-select protection for this cohort. |
| `noOp` | boolean | No | Cohort still resolves, but the flag tells the renderer to stand down — the checkout extension hides the offer and removes any protection line. Use as a control arm or to pause a cohort without deleting it. |
| `autoAddStateBlockList` | string\[] | No | ISO 3166-2 region codes (2 letters). Cap 100. |
| `autoAddCountryBlockList` | string\[] | No | ISO 3166-1 alpha-2 codes (2 letters). Cap 100. |

**Each rule:**

```json theme={null}
{
  "min": 0,
  "max": 100,
  "terms": {
    "paidBy": "customer",   // optional
    "operator": "AND",      // optional, "AND" | "OR"
    "customer": { "amount": 0.99, "type": "fixed" },
    "brand":    { "amount": 0,    "type": "fixed" }
  }
}
```

`min`/`max` are cart-subtotal bounds. `type` is `fixed` or `percentage`. Both `customer` and `brand` are optional but at least one is typically present.

**Example:**

```bash theme={null}
curl -X POST https://api.production.orderprotection.com/v2/settings/context/cmokjb8bq000663gkhjp5ppoj/pricing \
  -H "Authorization: Bearer op_at_..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "SHIPPING",
    "variantKey": "cohort-a",
    "automaticallyAddToCart": true,
    "rules": [
      { "min": 0,   "max": 100,     "terms": { "customer": { "amount": 0.99, "type": "fixed" } } },
      { "min": 101, "max": 1000000, "terms": { "customer": { "amount": 3,    "type": "percentage" } } }
    ]
  }'
```

**Response `201 Created`:**

```json theme={null}
{
  "id": "rs_abc",
  "type": "SHIPPING",
  "automaticallyAddToCart": true,
  "noOp": false,
  "autoAddStateBlockList": [],
  "autoAddCountryBlockList": [],
  "rules": [
    { "id": "pr_x", "min": 0, "max": 100, "terms": { "paidBy": "customer", "customer": { "amount": 0.99, "type": "fixed" } } }
  ]
}
```

One rule set per `(variantKey, type)` — creating a duplicate returns `409 Conflict`. To repoint a cohort at different pricing, delete the rule set and create a new one.

***

### List cohort price sets

```
GET /v2/settings/context/:contextId/pricing
```

Returns the context's rule sets across all cohorts. The response includes only the types you hold a matching read scope for (others are silently filtered). A token with no matching read scope at all gets `403`.

**Response `200 OK`:** Array of rule sets, same shape as the create response.

***

### Get a cohort price set

```
GET /v2/settings/context/:contextId/pricing/:ruleSetId
```

Requires the read scope matching the rule set's type. An `id` whose type you can't read returns `404` (existence is hidden).

***

### Update a cohort price set

```
PUT /v2/settings/context/:contextId/pricing/:ruleSetId
```

Same body as create, minus `variantKey` (immutable). `type` is accepted for round-trip ergonomics but must match the existing value — changing it returns `400`. Omitted fields preserve their existing values.

**Response `200 OK`:** The updated rule set.

***

### Delete a cohort price set

```
DELETE /v2/settings/context/:contextId/pricing/:ruleSetId
```

Hard-deletes the rule set and its rules. The path-based partner endpoints then return `404` for that cohort (the checkout extension falls back to the store default); the v1 header read path falls back to merchant pricing.

**Response `200 OK`:**

```json theme={null}
{ "ok": true }
```

***

## Cohort widget configs

Per-cohort widget configuration, attached to your partner context and keyed by `variantKey`. This is what the [checkout extension](/developer/checkout-extension) renders when a checkout's block settings point at your cohort.

All writes require the base scope `partner-context:update` **plus** the per-type scope below. Reads require the per-type read scope.

| Type | Read scope | Write scope |
| - | - | - |
| `CHECKOUT` | `widget:checkout:read` | `widget:checkout:update` |
| `CART` | `widget:cart:read` | `widget:cart:update` |
| `SUBSCRIPTION` | `widget:cart:read` | `widget:cart:update` |
| `INFO_MODAL` | `widget:info-modal:read` | `widget:info-modal:update` |

### Create a cohort widget config

```
POST /v2/widget/context/:contextId/config
```

**Request body:**

| Field | Type | Required | Description |
| - | - | - | - |
| `variantKey` | string | Yes | Your cohort key. Must match `^[a-zA-Z0-9_.:-]{1,64}$`. **Immutable** after create. |
| `type` | enum | Yes | `CHECKOUT`, `CART`, `SUBSCRIPTION`, or `INFO_MODAL`. **Immutable** after create. |
| `status` | enum | No | `ACTIVE` or `DRAFT`. Defaults to `DRAFT`. |
| `variant` | string | No | Free-form label, ≤ 256 chars. |
| `name` | string | No | Display name, ≤ 256 chars. Defaults to `config.widgetName` when omitted. |
| `config` | object | No | The widget configuration — validated against a strict schema; **unknown fields are rejected with `400`**. |

**Example:**

```bash theme={null}
curl -X POST https://api.production.orderprotection.com/v2/widget/context/cmokjb8bq000663gkhjp5ppoj/config \
  -H "Authorization: Bearer op_at_..." \
  -H "Content-Type: application/json" \
  -d '{
    "variantKey": "cohort-a",
    "type": "CHECKOUT",
    "status": "ACTIVE",
    "name": "Cohort A",
    "config": {
      "isV2": true,
      "titleText": "Package Protection",
      "infoText": "Protect your order against loss, theft, or damage in transit.",
      "showDisclaimerText": true,
      "disclaimerTextOn": "Your order is protected.",
      "disclaimerTextOff": "Your order will not be protected."
    }
  }'
```

**Response `201 Created`:**

```json theme={null}
{
  "id": "cmokr9v3t000284gkq2m8xw1e",
  "type": "CHECKOUT",
  "status": "ACTIVE",
  "variant": null,
  "name": "Cohort A",
  "config": { "isV2": true, "titleText": "Package Protection", "...": "..." },
  "customizationValues": { "...": "read-only mirror of config" },
  "updatedAt": "2026-05-26T18:46:25.987Z"
}
```

One config per `(variantKey, type)` — a duplicate returns `409 Conflict`. `CHECKOUT`, `CART`, and `INFO_MODAL` configs can coexist under the same `variantKey` because they differ on `type`.

<Note>
  Write your configuration to `config` only. `customizationValues` is a legacy read-only mirror kept in lockstep server-side — sending it in a request body is rejected. For checkout rendering, use the field vocabulary shown in the [Quickstart, Step 5](/developer/partner-integration-quickstart#step-5-create-a-widget-config-per-cohort) — the checkout extension renders a defined subset of the schema (copy, colors, borders, disclaimer, and layout fields).
</Note>

***

### List cohort widget configs

```
GET /v2/widget/context/:contextId/config
```

Returns the context's widget configs across all cohorts, filtered to the types you hold a read scope for. A token with no matching read scope at all gets `403`.

***

### Get / update / delete a cohort widget config

```
GET    /v2/widget/context/:contextId/config/:id
PUT    /v2/widget/context/:contextId/config/:id
DELETE /v2/widget/context/:contextId/config/:id
```

`PUT` accepts `status`, `variant`, `name`, and `config` (same strict schema; `variantKey` and `type` are immutable — delete and recreate to repoint a cohort). An `id` whose type you can't access returns `404`. `DELETE` hard-deletes.

***

## Merchant settings

The merchant's default `GeneralSetting`, read-only for partners.

```
GET /v2/settings
```

Required scope: `store:read`

**Response `200 OK`:**

```json theme={null}
{
  "enableCancelAfterPurchase": true,
  "cancelAfterPurchaseDurationSeconds": 86400,
  "sendConfirmationEmail": true,
  "storeCreditType": null,
  "storeCreditBonus": null,
  "storeCreditBonusType": null,
  "storeCreditMaxPercentage": null,
  "offerStoreCreditOnOpPurchase": false,
  "updatedByApplicationId": "cmpn5in6p000484flmfk9ns4d",
  "updatedAt": "2026-04-28T18:24:02.000Z"
}
```

`updatedByApplicationId` reflects the OAuth application that last wrote these settings, or `null` if the most recent write was made through the OP dashboard.

<Note>
  There is no partner write path for merchant defaults — the former `PUT /v2/settings` endpoint has been removed. Partner setting overrides are written per-installation via [`PATCH /v2/settings/context/:id`](#update-a-context), leaving the merchant's defaults untouched.
</Note>

***

## Pricing rules (merchant)

The merchant's default rule sets, readable and writable with per-ruleset-type scopes. A token with only `pricing:shipping-insurance:read` will see only `SHIPPING` rulesets; the others are silently filtered out (not 403'd) so a single token can be used for multiple resource types without surfacing access errors.

| Type | Read scope | Write scope |
| - | - | - |
| `SHIPPING` | `pricing:shipping-insurance:read` | `pricing:shipping-insurance:update` |
| `EXTENDED_WARRANTY` | `pricing:extended-warranty:read` | `pricing:extended-warranty:update` |

<Note>
  All pricing scopes are **admin-gated** — see [Scopes → Price Settings](/developer/scopes#price-settings-admin-gated). For A/B testing, prefer [cohort price sets](#cohort-pricing) — writing here changes pricing for **every** shopper outside a partner cohort.
</Note>

### List pricing rules

```
GET /v2/pricing/rules
```

Required scope: any of the per-type read scopes above. The response includes only the rulesets you have a matching read scope for.

**Response `200 OK`:**

```json theme={null}
[
  {
    "id": "rs_abc",
    "type": "SHIPPING",
    "automaticallyAddToCart": true,
    "autoAddStateBlockList": ["CA", "NY"],
    "autoAddCountryBlockList": [],
    "rules": [
      { "id": "pr_x", "min": 0, "max": 100, "...": "..." }
    ],
    "updatedByApplicationId": "cmpn5in6p000484flmfk9ns4d"
  }
]
```

***

### Update a ruleset

```
PUT /v2/pricing/rules/:id
```

Required scope: matching write scope for the ruleset's type. Passing an `id` whose type you don't have a write scope for returns `404` (existence is hidden).

**Request body:**

| Field | Type | Description |
| - | - | - |
| `automaticallyAddToCart` | boolean | Toggle auto-add for this ruleset. |
| `rules` | array | Replacement list of price rules. Omit to leave existing rules unchanged. |
| `autoAddStateBlockList` | string\[] | ISO 3166-2 region codes (2 letters). Cap 100. Empty array = no blocking; omitted = preserve existing. |
| `autoAddCountryBlockList` | string\[] | ISO 3166-1 alpha-2 codes (2 letters). Cap 100. Same null/empty semantics as above. |

**Each rule:**

```json theme={null}
{
  "id": "pr_x",            // optional — omit to create a new rule
  "min": 0,
  "max": 100,
  "terms": {
    "paidBy": "customer",   // optional
    "operator": "AND",      // optional, "AND" | "OR"
    "customer": { "amount": 2.99, "type": "fixed" },
    "brand":    { "amount": 0,    "type": "fixed" }
  }
}
```

`type` is one of `fixed` or `percentage`. Both `customer` and `brand` are optional but at least one is typically present.

**Example:**

```bash theme={null}
curl -X PUT https://api.production.orderprotection.com/v2/pricing/rules/rs_abc \
  -H "Authorization: Bearer op_at_..." \
  -H "Content-Type: application/json" \
  -d '{
    "automaticallyAddToCart": true,
    "autoAddCountryBlockList": ["CA"]
  }'
```

**Response `200 OK`:** Array containing the updated ruleset, same shape as `GET /v2/pricing/rules`.

<Note>
  ISO codes are normalised to upper case server-side, so `"ca"` and `"CA"` are equivalent on writes. Reads always return uppercase.
</Note>

***

## Quote

A stateless protection quote computation. Use this to preview the customer/brand split for a given subtotal before writing it into a cart.

### Generate a quote

```
POST /v2/quote
```

Required scope: `quote:read`

**Request body:**

| Field | Type | Required | Description |
| - | - | - | - |
| `orderTotal` | number | One of these | Pre-computed subtotal (no need to send line items). |
| `lineItems` | array | One of these | Line items; the server computes the subtotal, stripping non-shippable items for `SHIPPING` quotes. Cap 500. |
| `policyType` | enum | No | `SHIPPING` (default) or `EXTENDED_WARRANTY`. |
| `currency` | string | No | ISO currency code; informational only. |
| `duration` | string | If `EXTENDED_WARRANTY` | ISO 8601 year duration, e.g. `"P3Y"`. Required for warranty quotes; ignored on shipping quotes. |

Each line item:

```json theme={null}
{
  "price": 19.99,
  "quantity": 1,
  "isShippable": true,
  "id": "li_x",          // optional
  "productType": "shoes" // optional
}
```

At least one of `orderTotal` or `lineItems` must be present. If both are sent, `lineItems` wins and `orderTotal` is ignored.

**Example:**

```bash theme={null}
curl -X POST https://api.production.orderprotection.com/v2/quote \
  -H "Authorization: Bearer op_at_..." \
  -H "Content-Type: application/json" \
  -d '{"orderTotal": 49.99, "policyType": "SHIPPING"}'
```

**Response `201 Created`:**

```json theme={null}
{
  "customer": 1.99,
  "brand": 0,
  "fundedBy": "customer",
  "operator": null,
  "pricingModel": "FLAT"
}
```

***

## Widget config (merchant)

The merchant's default widget configs, with per-widget-type scopes. As with pricing, a narrow token only sees the widget types it can read; the rest are silently filtered.

| Type | Read scope | Write scope |
| - | - | - |
| `CHECKOUT` | `widget:checkout:read` | `widget:checkout:update` |
| `CART` | `widget:cart:read` | `widget:cart:update` |
| `INFO_MODAL` | `widget:info-modal:read` | `widget:info-modal:update` |

<Note>
  For A/B testing, prefer [cohort widget configs](#cohort-widget-configs) — writing here changes the widget for **every** shopper outside a partner cohort.
</Note>

### List widget configs

```
GET /v2/widget/config
```

Required scope: any of the per-type read scopes.

**Response `200 OK`:**

```json theme={null}
[
  {
    "id": "wc_abc",
    "type": "CART",
    "status": "ACTIVE",
    "variant": null,
    "config": { "...": "schemaless — defined by the renderer" },
    "customizationValues": { "...": "schemaless" },
    "updatedAt": "2026-04-28T18:24:02.000Z",
    "updatedByApplicationId": "cmpn5in6p000484flmfk9ns4d"
  }
]
```

***

### Update a widget config

```
PUT /v2/widget/config/:id
```

Required scope: matching write scope for the widget's type. As with pricing, an `id` whose type you can't write returns `404` — existence and scope are not distinguishable.

**Request body:**

| Field | Type | Required | Description |
| - | - | - | - |
| `config` | object | Yes | The widget configuration object. Schemaless — the renderer validates on read. |
| `status` | enum | No | One of the `WidgetStatus` values. |
| `customizations` | object | No | Schemaless customization values. |

**Response `200 OK`:** Same shape as a single entry in `GET /v2/widget/config`.

***

## Public widget read (with cohort)

The shopper-facing widget read path (`GET /v1/quote/insurance`) is unauthenticated, but it accepts two optional headers that route the read to a partner context. This is useful for **verifying cohort resolution** and for surfaces where OrderProtection's rendering reads through the v1 path.

| Header | Description |
| - | - |
| `x-op-partner-app-id` | Your OAuth application ID (`Application.id`, a cuid — not your `op_app_...` client\_id). |
| `x-op-partner-variant` | The `variantKey` of the cohort to apply. |

Both headers must match `^[a-zA-Z0-9_.:-]{1,64}$`. If either is missing, malformed, or the indicated context doesn't exist for this store, the response silently falls back to merchant defaults — public widget reads must never crash a checkout.

The off-switch fires when the OAuth app has been uninstalled (or never installed) on the store: in that case the cohort headers are ignored and the merchant's defaults are returned. Install / uninstall events propagate within seconds via Kafka and bust the install-status cache, but **already-cached cohort responses** (the public reads are cached for up to an hour) can keep serving until their TTL expires.

```bash theme={null}
curl -G https://api.production.orderprotection.com/v1/quote/insurance \
  --data-urlencode "store_url=example.myshopify.com" \
  --data-urlencode "country=US" \
  -H "x-op-partner-app-id: cmpn5in6p000484flmfk9ns4d" \
  -H "x-op-partner-variant: cohort-a"
```

The response follows the existing `/v1/quote/insurance` documentation, with the resolved settings + pricing swapped for the cohort's. When a cohort resolves, the response additionally carries `source`, `partnerApplicationId`, and `partnerVariantKey` so you can confirm which cohort was applied.

<Note>
  The Shopify checkout extension uses the **path-based** public endpoints instead (`/v2/quote/insurance/partner/:applicationId` and `/v2/widget/config/partner/:applicationId`) — those throw `400`/`404` rather than silently falling back, and are documented in the [Checkout Extension guide](/developer/checkout-extension#what-the-extension-fetches).
</Note>

***

## Error responses

Standard JSON error envelope (same as the rest of the OrderProtection API):

```json theme={null}
{
  "statusCode": 403,
  "message": "Insufficient OAuth scopes",
  "error": "Forbidden"
}
```

| Code | Common cause on these endpoints |
| - | - |
| `400` | Malformed body, validation failure, unknown widget-config field, or an immutable field change (`type`). |
| `403` | Missing OAuth scope for the route or resource type, or admin-gated scope not granted. |
| `404` | Resource doesn't exist for your app on this store — or exists with a type your token can't access (existence is hidden). |
| `409` | Duplicate: context already exists for this store, or a rule set / widget config already exists for that `(variantKey, type)`. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.