---
title: "Update a shipping on an offer"
description: "Update any subset of the shipping settings on a preorder offer: the shipping timeline text, delivery timing, fulfillment handling (hold, split or separate orders) and hold release."
url: https://docs.stoqapp.com/v2-reference/preorders-offers/update-a-shipping-on-an-offer/
api: "API v2 reference"
method: PATCH
path: "/preorders/offers/{id}/shipping"
---

# Update a shipping on an offer

`PATCH https://app.stoqapp.com/api/v2/external/preorders/offers/{id}/shipping`

Update any subset of the shipping settings on a preorder offer: the shipping timeline text, delivery timing, fulfillment handling (hold, split or separate orders) and hold release.

**Note:** Deep partial: absent fields are unchanged, null clears. Settings the save changes on its own (the purchase option and payments when separate orders turns on, split fulfillments when delivery leaves asap) are listed in also_changed. reschedule_existing_orders: true is the editor's &quot;Reschedule existing orders to this date&quot; checkbox: after the save, every unfulfilled, uncancelled preorder on the offer has its scheduled fulfillments moved to the offer's ship date (ships_at), or to its own order date plus days_after_checkout, in the background. It needs delivery_timing exact_time after the save, can be sent alone to re-apply the current date, and does not email customers (send a shipping update from the orders endpoints for that). When sent, the response has rescheduled_orders_queued.

## Path parameters

- `id` (string, required)

## Headers

- `Idempotency-Key` (string): A fresh unique key for every write (a new UUID, say); reuse it only to retry that write. Keys are remembered for 1 hour: the same key and the same request within that hour returns the first result, with `Idempotent-Replayed: true`, without running again; the same key with a different method, path or body is refused (422 `idempotency_key_reused`); a retry while the first is still running gets 409 `idempotency_key_in_progress`. Keys are per store. Server errors and rate limits are not kept, so their retry runs.

## Request body

- `timeline` (object)
  - `scope` (string): same shows one shipping text for every product; per_variant uses each variant's own shipping text (set per variant). One of: `same`, `per_variant`.
  - `show_shipping_text` (boolean): Show shipping timeline on the widget. Also turns the cart line-item property (advanced.cart_attributes.show_shipping) on or off when it changes, as the editor does; sending the current value changes nothing.
  - `shipping_text` (string): The line under the preorder button. Required while show_shipping_text is true.
- `delivery_timing` (string): Fulfillment status of preorder items: asap is Unfulfilled (ship now), exact_time is Scheduled (needs ships_at or days_after_checkout and the Shopify purchase option), unknown is On hold (no date promised). asap and unknown clear ships_at and days_after_checkout. One of: `asap`, `exact_time`, `unknown`.
- `ships_at` (string): Ship date: a date (2026-11-05), read as midnight in the shop time zone, or an ISO 8601 datetime (kept as that exact moment). Returned as the UTC datetime. Sending it alone makes delivery_timing exact_time on that date. Also sets timeline.variant_shipping_text (the {{ date }} value) to the date in the shop time zone, as the editor does: &quot;05 Nov 2026&quot;.
- `days_after_checkout` (integer): Ship N days after checkout. Sending it alone makes delivery_timing exact_time with that delay; ships_at is then null. Also sets timeline.variant_shipping_text to &quot;N days after checkout&quot;, as the editor does.
- `fulfillment` (object)
  - `handling` (string): none; hold_all holds every fulfillment; split_fulfillments ships in-stock items first (needs delivery_timing asap); separate_orders moves preorder items to their own order, which switches the Shopify purchase option off and resets partial payment, the discount and a Scheduled status (listed in also_changed). One of: `none`, `hold_all`, `split_fulfillments`, `separate_orders`.
  - `hold_reason` (string): The hold reason shown in Shopify.
  - `separate_orders` (object)
    - `grouping` (string): offer creates one separate order per offer; variant one per variant; ship_date one per ship date (batch or shipping text), which needs timeline.scope per_variant or batches on. One of: `variant`, `offer`, `ship_date`.
    - `number_format` (string): sequential gives each separate order its own order number; suffix appends suffix to the original order name. One of: `sequential`, `suffix`.
    - `suffix` (string): Suffix appended to the separate order name.
    - `hold_fulfillments` (boolean): Hold fulfillments on the separate order until they're released.
    - `tag` (string): Tag applied to the separate preorder order.
    - `transaction_gateway` (string): Payment gateway recorded on the separate order transaction.
    - `original_order_hold_tag` (string): Tag on the original order while it is being split. Removed once the split finishes.
    - `original_order_split_tag` (string): Tag on the original order after the preorder items move to a separate order.
- `on_hold` (object)
  - `release_schedule` (string): scheduled releases held fulfillments on release_at; manual leaves them held. One of: `scheduled`, `manual`.
  - `release_at` (string): ISO 8601. Required while release_schedule is scheduled.
  - `require_payment` (boolean): Only release once the balance is paid.
- `reschedule_existing_orders` (boolean): true also moves the scheduled fulfillments of existing unfulfilled preorders on this offer to the ship date (ships_at, or each order date plus days_after_checkout), as the editor's &quot;Reschedule existing orders to this date&quot; checkbox does. Needs delivery_timing exact_time. No customer email.
- `locale` (string): Language the translated fields are written in, such as 'fr' or 'pt-BR'. Omitted: the shop's default language. Untranslated fields ignore it.

## Returns

- `timeline` (object)
  - `scope` (string): e.g. `same`
  - `show_shipping_text` (boolean): e.g. `true`
  - `shipping_text` (string): e.g. `Shipping: {{ date }}`
  - `variant_shipping_text` (string): e.g. `ASAP`
- `delivery_timing` (string): e.g. `exact_time`
- `ships_at` (any)
- `days_after_checkout` (integer): e.g. `30`
- `fulfillment` (object)
  - `handling` (string): e.g. `hold_all`
  - `hold_reason` (string): e.g. `Waiting on stock`
  - `separate_orders` (object)
    - `grouping` (string): e.g. `offer`
    - `number_format` (string): e.g. `suffix`
    - `suffix` (string): e.g. `SPLIT`
    - `hold_fulfillments` (boolean): e.g. `false`
    - `tag` (string): e.g. `STOQ-split-order`
    - `transaction_gateway` (string): e.g. `Prepaid`
    - `original_order_hold_tag` (any)
    - `original_order_split_tag` (any)
- `on_hold` (object)
  - `release_schedule` (string): e.g. `manual`
  - `release_at` (any)
  - `require_payment` (boolean): e.g. `false`

Rate limit: this call costs 2 points ([rate limits](https://docs.stoqapp.com/docs/rate-limits/)).

## Example request

```sh
curl -X PATCH "https://app.stoqapp.com/api/v2/external/preorders/offers/73a74948-2b57-4cc3-971f-2b441828c977/shipping" \
  -H "X-Auth-Token: $STOQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "days_after_checkout": 30,
    "fulfillment": {
      "handling": "hold_all",
      "hold_reason": "Waiting on stock"
    }
  }'
```

## 200

Success: example response shown.

```json
{
  "timeline": {
    "scope": "same",
    "show_shipping_text": true,
    "shipping_text": "Shipping: {{ date }}",
    "variant_shipping_text": "ASAP"
  },
  "delivery_timing": "exact_time",
  "ships_at": null,
  "days_after_checkout": 30,
  "fulfillment": {
    "handling": "hold_all",
    "hold_reason": "Waiting on stock",
    "separate_orders": {
      "grouping": "offer",
      "number_format": "suffix",
      "suffix": "SPLIT",
      "hold_fulfillments": false,
      "tag": "STOQ-split-order",
      "transaction_gateway": "Prepaid",
      "original_order_hold_tag": null,
      "original_order_split_tag": null
    }
  },
  "on_hold": {
    "release_schedule": "manual",
    "release_at": null,
    "require_payment": false
  }
}
```

## 202

Saved. Returns the updated section, with also_changed for cascades.

## 401

Missing or invalid `X-Auth-Token`.

```json
{
  "errors": [
    "Invalid API key"
  ],
  "code": "auth_invalid",
  "details": [
    {
      "code": "auth_invalid",
      "message": "Invalid API key",
      "path": null,
      "value": null,
      "expected": null
    }
  ]
}
```

## 404

The resource does not exist for this shop, or the action path is not registered.

```json
{
  "errors": [
    "Offer not found"
  ],
  "code": "not_found",
  "details": [
    {
      "code": "not_found",
      "message": "Offer not found",
      "path": null,
      "value": null,
      "expected": null
    }
  ]
}
```

## 409

An earlier request with the same Idempotency-Key is still running. Wait, then resend the same request with the same key.

```json
{
  "errors": [
    "A request with this Idempotency-Key is still running"
  ],
  "code": "idempotency_key_in_progress",
  "details": [
    {
      "code": "idempotency_key_in_progress",
      "message": "A request with this Idempotency-Key is still running",
      "path": null,
      "value": null,
      "expected": null
    }
  ]
}
```

## 422

Invalid fields: one `details` entry per field error, its `path` the canonical section path. Nothing was saved.

```json
{
  "errors": [
    "widget.button.text_color must be a hex color"
  ],
  "code": "invalid_params",
  "details": [
    {
      "code": "invalid_params",
      "message": "must be a hex color",
      "path": "widget.button.text_color",
      "value": "blue",
      "expected": {
        "type": "string"
      }
    }
  ]
}
```

## 429

Rate limited: cost-weighted per token (read 1, write 2; 360 points / 60s). Check `Retry-After`.

```json
{
  "errors": [
    "Rate limit exceeded"
  ],
  "code": "rate_limited",
  "details": [
    {
      "code": "rate_limited",
      "message": "Rate limit exceeded",
      "path": null,
      "value": null,
      "expected": null
    }
  ]
}
```

