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

# Draft Returns (HAL Forms)

> Build a return-creation flow by managing draft returns through a state machine.

## Draft Returns API

The Draft Returns API lets you build a complete return-creation flow by managing draft returns
through a state machine. Endpoints allow you to initialize a draft, add or remove items, set
customer and address details, finalize selections, and submit the completed return.

> [!NOTE]
>
> In `2026-10`, the Draft Returns API uses HAL Forms for all responses and ignores the `Accept`
> header on every endpoint, including `GET /draft-returns` (List). On `2026-07` and earlier versions,
> the response format depends on your `Accept` header, and List requires
> `Accept: application/prs.hal-forms+json`.

### Response structure

Each successful response includes:

* `draft_return` — the draft return and shopper details
* `context` — data needed to fill action payloads
* `_links` — available actions, keyed by relation name, each with an `href`
* `_templates` — action descriptors keyed by name, with `method`, `title`, `contentType`, and `properties`
* `client_application` — how the client should identify itself on later requests
* `errors` — any errors from processing

```json theme={null}
{
  "_links": {
    "self": { "href": "https://api.loopreturns.com/api/2026-10/draft-returns/123" },
    "add-returning-item": {
      "href": "https://api.loopreturns.com/api/2026-10/draft-returns/123/returning-items"
    }
  },
  "_templates": {
    "add-returning-item": {
      "method": "POST",
      "title": "Add a line item to the return",
      "contentType": "application/json",
      "properties": [
        { "name": "order_line_item_id", "type": "number", "required": true }
      ]
    }
  },
  "draft_return": { "..." },
  "context": { "..." },
  "client_application": { "..." },
  "errors": []
}
```

Responses use `Content-Type: application/prs.hal-forms+json`. Request bodies use
`Content-Type: application/json`. Authenticate with the `X-Authorization` API key header.

Shopper-portal draft responses include `shopper_token`. Send that value as
`X-Loop-Shopper-Token` on later shopper-portal requests that include a draft return id.
Merchant public API responses omit this field and do not require the header.
A missing, empty, or non-matching token on those draft-id requests returns `404`.
Create, initialize, and list do not require the header.

### State machine

```
created → finalize-items → items-finalized → submit → submitted
   ↓                              ↓
   ↓← ← unfinalize-items ← ← ← ← ↓
   ↓                              ↓
   └→ cancel → cancelled     expired
```

* **created** — add items and set customer information
* **items-finalized** — choose credit type, processing type, and return method
* **submitted** / **cancelled** / **expired** — terminal; `_templates` and `_links` are empty

### Return type

Each returning item has its own `set-returning-item-return-type` template. The values you can
send depend on the item, so send only a value from that template's `options`. A return type the
item does not allow is rejected with `action-validation-failed`.

* `exchange` is not offered when the item's return policy turns exchanges off, even if Shop Now
  is on.
* `inline-exchange` covers a same-product exchange, to another variant or to the purchased
  variant. Show a same-product choice only when `inline-exchange` is offered. Loop saves it as
  `exchange`. It is not offered when a workflow excludes inline exchanges, unless the order is
  allowlisted. `exchange` alone still covers Advanced Exchange. See [Set Returning Item Return Type](https://docs.loopreturns.com/api-reference/2026-10/draft-returns/set-returning-item-return-type.md)
  for when Loop offers `inline-exchange`.
* `replace` is offered for warranty replacements. Loop saves it as `exchange`. Like
  `inline-exchange`, it is not offered when a workflow excludes inline exchanges, unless the
  order is allowlisted.
* On a warranty claim, only the outcomes the warranty allows are offered, even when the item is
  still within its return window.
* On a warranty claim, `exchange` and `replace` are not offered for an item with no catalog
  product or variant, such as a deleted product or a custom line item. If that leaves the
  warranty with no outcome, the item is not eligible for the warranty claim.

See [Set Returning Item Return Type](https://docs.loopreturns.com/api-reference/2026-10/draft-returns/set-returning-item-return-type.md).

### User input

Each unanswered merchant question on a returning item has its own `add-returning-item-user-input`
template. Its `title` is the question.

A yes/no template can include `systemMessage`, the merchant's "Response if yes" message in the
draft return's language, or the merchant's first configured message when that language has no
translation. It may contain HTML. It is only present when the merchant turned that response on.

```json theme={null}
{
  "_templates": {
    "add-returning-item-user-input:1:10": {
      "method": "POST",
      "title": "Have the seals been broken?",
      "systemMessage": "<p>Used cartridges cannot be returned.</p>"
    }
  }
}
```

Show `systemMessage` when the shopper answers `Yes`. If the merchant's rule rejects the item on
`Yes`, the item is removed from the draft return and the next response does not include the
message. Keep it from the template before you submit the answer, show it, then return the shopper
to item selection.

See [Add Returning Item User Input](https://docs.loopreturns.com/api-reference/2026-10/draft-returns/add-returning-item-user-input.md).

### Processing type

`processing_type` is `regular` or `instant`. Every draft return starts as `regular`.

In the `items-finalized` state, `_embedded.processing_type_options` shows which processing types
you can use:

```json theme={null}
{
  "_embedded": {
    "processing_type_options": {
      "required": false,
      "options": [
        {
          "type": "regular",
          "available": true,
          "unavailability_reason": null,
          "authorization_amount": null,
          "charge_amount": null,
          "window_days": null,
          "hold_window_days": null
        },
        {
          "type": "instant",
          "available": true,
          "unavailability_reason": null,
          "authorization_amount": { "amount": 5000, "currency_code": "USD" },
          "charge_amount": { "amount": 5000, "currency_code": "USD" },
          "window_days": 14,
          "hold_window_days": null
        }
      ]
    }
  }
}
```

| Field | Meaning |
| - | - |
| `required` | `true` when the return policy requires Instant. `regular` is then unavailable. |
| `options[].type` | `regular` or `instant`. |
| `options[].available` | Whether you can set this type now. |
| `options[].unavailability_reason` | Why the type is unavailable: `not-enabled`, `not-applicable`, `excluded`, or `instant-required`. `null` when available. |
| `options[].authorization_amount` | The amount Loop holds (authorizes) for Instant, in minor units. |
| `options[].charge_amount` | The amount Loop charges for Instant, in minor units. |
| `options[].window_days` | The Instant window, in days, from the return policy. |
| `options[].hold_window_days` | The hold window, in days. Set only when Instant applies to a refund rather than an exchange. |

The amounts and day counts are set only on an available `instant` option. They are `null`
otherwise.

When the draft has an exchange item, Instant runs as Instant Exchange. It stays available even
when the credit type is `refund`.

To change the processing type, use the `set-processing-type` template and send only a value from
its `options`. See [Set Processing Type](https://docs.loopreturns.com/api-reference/2026-10/draft-returns/set-processing-type.md).

#### Instant reset

While the draft is in `items-finalized`, Loop checks on every request (including `GET` and
submit) whether Instant is still available. If it is not, Loop:

* sets `processing_type` to `regular`
* removes the payment intent, so `payment_intent` is `null`

Before you submit, read `processing_type` and `payment_intent` in the latest response. If you
still want Instant and it is available again, set it again and attach a new payment intent.

### Payment

A draft return can require a payment, for example when the shopper owes money after exchanging
into a pricier item, or when `processing_type` is `instant`. Loop collects it through the shop's
Stripe connection.

* If the shop has a usable Stripe connection, the   `add-payment-intent` template is offered and
  marked required. Call [Create Payment Intent](https://docs.loopreturns.com/api-reference/2026-10/draft-returns/create-payment-intent.md)
  before you submit.
* If the shop has no usable Stripe connection, Loop cannot collect the payment. The
  `add-payment-intent` template is not offered and `requires_uncollectible_payment` is `true`.
  The draft return cannot be submitted. To continue, change the draft so no payment is needed,
  for example by changing items or the return method.

If Instant stops being available, Loop resets `processing_type` to `regular` and removes the
payment intent, so `payment_intent` comes back `null`. Check `processing_type` and
`payment_intent` before you submit. If a payment is still needed, `add-payment-intent` is offered
again.


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