> ## 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 settings, pricing, widgets, quotes, and partner-owned A/B-test contexts.

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; everything else under `/v1` is dashboard-only and not part of the partner surface.
</Note>

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

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

## 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`         | `read_store`, `write_settings`                                                           |
| [Merchant settings](#merchant-settings)               | `GET/PUT /v2/settings`                               | `read_store`, `write_settings` (+ `write_store_credit_settings` for store-credit fields) |
| [Pricing rules](#pricing-rules)                       | `GET /v2/pricing/rules`, `PUT /v2/pricing/rules/:id` | per ruleset type — see below                                                             |
| [Quote](#quote)                                       | `POST /v2/quote`                                     | `read_quotes`                                                                            |
| [Widget config](#widget-config)                       | `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 a partner-owned cohort of settings overrides. The OP backend stores sparse overrides keyed by `(storeId, applicationId, variantKey)`. At read time, partner overrides are layered over the merchant's default settings — fields you don't override are inherited from the merchant. Pricing rules are layered the same way: if the partner has no pricing rules attached, the merchant's rules are used.

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: `write_settings`

**Request body:**

| Field        | Type   | Required | Description                                                                                                                        |
| ------------ | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `variantKey` | string | Yes      | Stable identifier for the cohort. Must match `^[a-zA-Z0-9_.:-]{1,64}$`. Use the same value when reading on the public widget path. |
| `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 '{"variantKey": "experiment_a", "name": "Variant A — auto-add on"}'
```

**Response `201 Created`:**

```json theme={null}
{
  "id": "cmokjb8bq000663gkhjp5ppoj",
  "applicationId": "app_ghi012",
  "variantKey": "experiment_a",
  "name": "Variant A — auto-add on",
  "createdAt": "2026-04-28T18:22:11.000Z",
  "updatedAt": null
}
```

***

### List your contexts

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

Required scope: `read_store`

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

**Response `200 OK`:**

```json theme={null}
[
  {
    "id": "cmokjb8bq000663gkhjp5ppoj",
    "applicationId": "app_ghi012",
    "variantKey": "experiment_a",
    "name": "Variant A — auto-add on",
    "createdAt": "2026-04-28T18:22:11.000Z",
    "updatedAt": null
  }
]
```

***

### Get a context (with merged settings)

```
GET /v2/settings/context/:id
```

Required scope: `read_store`

Returns the context metadata plus the **resolved** settings and pricing rules — the same view a shopper assigned to this cohort would see. Merchant defaults are layered first; partner overrides for non-null fields are layered on top.

**Response `200 OK`:**

```json theme={null}
{
  "id": "cmokjb8bq000663gkhjp5ppoj",
  "applicationId": "app_ghi012",
  "variantKey": "experiment_a",
  "name": "Variant A — auto-add on",
  "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": 50, "...": "..." }
      ]
    }
  ]
}
```

<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
```

Required scope: `write_settings` (+ `write_store_credit_settings` 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 for this cohort. Pass an explicit value to override; pass `null` is **not** supported on this endpoint — use `DELETE` to drop the cohort entirely.

**Request body:**

| Field                                | Type    | Description                                                                                        |
| ------------------------------------ | ------- | -------------------------------------------------------------------------------------------------- |
| `name`                               | string  | New cohort label (1–128 chars).                                                                    |
| `enableCancelAfterPurchase`          | boolean | Override the cancel-after-purchase toggle for this cohort.                                         |
| `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** `write_store_credit_settings`.                                   |
| `storeCreditType`                    | enum    | One of the `StoreCreditType` values. **Requires** `write_store_credit_settings`.                   |
| `storeCreditBonus`                   | number  | Bonus amount (≥ 0). **Requires** `write_store_credit_settings`.                                    |
| `storeCreditBonusType`               | enum    | One of the `CalcMethod` values (`flat`, `percentage`). **Requires** `write_store_credit_settings`. |
| `storeCreditMaxPercentage`           | number  | Cap (0–100). **Requires** `write_store_credit_settings`.                                           |

**Example:**

```bash theme={null}
curl -X PATCH https://api.production.orderprotection.com/v2/settings/context/cmokjb8bq000663gkhjp5ppoj \
  -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: `write_settings`

Soft-deletes the context. Future shopper traffic with the same `variantKey` falls back to merchant defaults at the resolver.

**Response `200 OK`:**

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

***

## Merchant settings

The merchant's default `GeneralSetting`. Writes attribute the change to your OAuth application so dashboards can surface "last edited by app X" attribution.

<Warning>
  Writing to `/v2/settings` modifies the **merchant's defaults** — it affects every shopper that doesn't fall into a partner cohort. Most parallel-A/B-test workflows should write to `PATCH /v2/settings/context/:id` instead, leaving the merchant's defaults untouched.
</Warning>

### Get merchant settings

```
GET /v2/settings
```

Required scope: `read_store`

**Response `200 OK`:**

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

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

***

### Update merchant settings

```
PUT /v2/settings
```

Required scope: `write_settings` (+ `write_store_credit_settings` for store-credit fields)

**Request body:**

| Field                                | Type    | Description                                                         |
| ------------------------------------ | ------- | ------------------------------------------------------------------- |
| `enableCancelAfterPurchase`          | boolean |                                                                     |
| `cancelAfterPurchaseDurationSeconds` | number  |                                                                     |
| `sendConfirmationEmail`              | boolean |                                                                     |
| `offerStoreCreditOnOpPurchase`       | boolean | **Requires** `write_store_credit_settings`.                         |
| `storeCreditType`                    | enum    | **Requires** `write_store_credit_settings`.                         |
| `storeCreditBonus`                   | number  | (≥ 0) **Requires** `write_store_credit_settings`.                   |
| `storeCreditBonusType`               | enum    | `flat` or `percentage`. **Requires** `write_store_credit_settings`. |
| `storeCreditMaxPercentage`           | number  | 0–100. **Requires** `write_store_credit_settings`.                  |

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

***

## Pricing rules

Per-ruleset-type scopes. A token with only `read_shipping_insurance_pricing` 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`          | `read_shipping_insurance_pricing` | `write_shipping_insurance_pricing` |
| `EXTENDED_WARRANTY` | `read_extended_warranty_pricing`  | `write_extended_warranty_pricing`  |

<Note>
  All pricing scopes are **admin-gated** — see [Scopes → Price Settings](/developer/scopes#price-settings-admin-gated). Private apps cannot use them; public apps must be reviewed and approved by OrderProtection.
</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", "minValue": 0, "maxValue": 50, "...": "..." }
    ],
    "updatedByApplicationId": "app_ghi012"
  }
]
```

***

### 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": 50,
  "terms": {
    "paidBy": "customer",   // optional
    "operator": "AND",      // optional, "AND" | "OR"
    "customer": { "amount": 2.99, "type": "flat" },
    "brand":    { "amount": 0,    "type": "flat" }
  }
}
```

`type` is one of `flat` 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: `read_quotes`

**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; rejected on shipping quotes.            |

Each line item:

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

Either `orderTotal` **or** `lineItems` must be present (not both).

**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 `200 OK`:**

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

***

## Widget config

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`   | `read_widget_checkout`   | `write_widget_checkout`   |
| `CART`       | `read_widget_cart`       | `write_widget_cart`       |
| `INFO_MODAL` | `read_widget_info_modal` | `write_widget_info_modal` |

### 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": "app_ghi012"
  }
]
```

***

### 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 how a partner-controlled cart-attribute or shopper-segmenter on the merchant's storefront can serve different settings + pricing per shopper.

| Header                 | Description                                       |
| ---------------------- | ------------------------------------------------- |
| `x-op-partner-app-id`  | Your OAuth application ID (`Application.id`).     |
| `x-op-partner-variant` | The `variantKey` of the partner context 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. App install / uninstall events propagate within seconds via Kafka, so deliberate revocations take effect immediately rather than waiting for cache TTL.

```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: app_ghi012" \
  -H "x-op-partner-variant: experiment_a"
```

The response shape is unchanged from the existing `/v1/quote/insurance` documentation; only the resolved settings + pricing differ when a valid cohort is supplied.

***

## 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, or admin-gated scope missing on a store-credit field.                                      |
| `401` | Missing, expired, or revoked access token.                                                                                     |
| `403` | Token missing the required scope. On endpoints where existence is hidden (pricing, widget), this surfaces as `404` instead.    |
| `404` | Resource doesn't exist, isn't owned by your app (partner contexts), or you lack the matching per-type scope (pricing, widget). |
| `429` | Rate limit exceeded.                                                                                                           |

***

<Note>
  *Last verified against monolog `d32a10bba`.*
</Note>
