> For the complete documentation index, see [llms.txt](https://docs.commercelayer.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.commercelayer.io/core-api-reference/2026-05/orders.md).

# Orders

The order object and the allowed CRUD operations on the related resource endpoint

Orders get created in `draft` status and become `pending` when they have a customer and some line items.

{% hint style="info" %}
Draft orders act as **shopping carts** — see our [how-to](/how-tos/placing-orders/shopping-cart.md) on how to manage shopping carts. Draft orders that aren't associated with a customer are automatically deleted **2 months** after their latest update.
{% endhint %}

{% hint style="danger" %}
For performance reasons (and to make sure not to hit the API [rate limits](/core/rate-limits.md)), we strongly advise against dealing with empty carts — just create a new order right before adding the first line item to it.
{% endhint %}

Pending orders can be recovered or cancelled when abandoned. The status of an order is closely linked to the related [payment](#payment-status) (dynamically computed from the associated payment sessions) and [fulfillment](#fulfillment-status) statuses. Orders are placed synchronously by default, if you need to place them [asynchronously](#asynchronous-order-placement) and transit through the `placing` status use the `place_async` attribute. When an order is `placed`, it can either get `approved` or `cancelled`.

{% hint style="warning" %}
Placed orders cannot be deleted. To archive them, you can patch them by passing the `_archive` trigger attribute. You can [filter](/core/filtering-data.md) them using the query `filter[q][archived_at_null]=false`, [search](/metrics/getting-started/use-cases/latest-archived-orders.md) for them using the Metrics API, or see them in the related section of the Dashboard Orders app.
{% endhint %}

### Fulfillment status

An approved order becomes `fulfilled` when paid and shipped. When an order is cancelled, any associated [payment void](#voiding) or [payment refund](#refunding) must be created manually against the related payment session.

<table><thead><tr><th>Action</th><th width="140">Order status</th><th width="240">Fulfillment status</th></tr></thead><tbody><tr><td>An empty order is created.</td><td><strong><code>draft</code></strong></td><td><strong><code>unfulfilled</code></strong></td></tr><tr><td>A customer is associated with the order, and a purchasable item is added.</td><td><strong><code>pending</code></strong></td><td><strong><code>unfulfilled</code></strong></td></tr><tr><td>The order is placed asynchronously, and errors occur.</td><td><strong><code>placing</code></strong></td><td><strong><code>unfulfilled</code></strong></td></tr><tr><td>The order is placed.</td><td><strong><code>placed</code></strong></td><td><strong><code>unfulfilled</code></strong></td></tr><tr><td>The order is being edited.</td><td><strong><code>editing</code></strong></td><td><strong><code>unfulfilled</code></strong></td></tr><tr><td>The order is cancelled.</td><td><strong><code>cancelled</code></strong></td><td><strong><code>unfulfilled</code></strong></td></tr><tr><td>The order is approved.</td><td><strong><code>approved</code></strong></td><td><strong><code>unfulfilled</code></strong></td></tr><tr><td>The payment is captured.</td><td><strong><code>approved</code></strong></td><td><strong><code>in_progress</code></strong></td></tr><tr><td>The order is fully refunded.</td><td><strong><code>cancelled</code></strong></td><td><strong><code>unfulfilled</code></strong> if <em>in progress</em>, otherwise <strong><code>fulfilled</code></strong>.</td></tr><tr><td>All shipments are shipped.</td><td><strong><code>approved</code></strong></td><td><strong><code>fulfilled</code></strong></td></tr></tbody></table>

If the order contains items flagged as `do_not_ship` only, its fulfillment status is automatically `not_required`.

{% hint style="warning" %}
The table above describes the most common order lifecycle scenario. Some specific use cases (e.g. the ones involving manual actions on orders and shipments) might slightly differ.
{% endhint %}

### Payment status

The payment status of an order is computed dynamically by aggregating the statuses of its associated [payment sessions](#payment-sessions). The remaining amount not yet covered by active sessions is tracked via the `session_amount_cents` attribute — when it reaches zero, the full order total is covered.

<table><thead><tr><th width="220">Payment status</th><th>Condition</th></tr></thead><tbody><tr><td><strong><code>free</code></strong></td><td>The order total is zero.</td></tr><tr><td><strong><code>unpaid</code></strong></td><td>No sessions beyond <code>unpaid</code> or <code>invalidated</code> — an invalidated session never counts, the same as an unpaid one.</td></tr><tr><td><strong><code>authorized</code></strong></td><td>At least one session is <code>authorized</code> and <code>session_amount_cents</code> is zero or negative (e.g. if the order total was reduced after the session was authorized) — the order total is fully covered.</td></tr><tr><td><strong><code>partially_authorized</code></strong></td><td>At least one session is <code>authorized</code> but <code>session_amount_cents</code> is still positive — part of the order total isn't covered by any session yet.</td></tr><tr><td><strong><code>partially_paid</code></strong></td><td>At least one session is <code>paid</code> or <code>partially_paid</code>, while the amount actually captured — across every session that ever captured funds (<code>paid</code>, <code>partially_paid</code>, <code>partially_refunded</code>, or <code>refunded</code>), net of any refunds — doesn't yet cover the order total.</td></tr><tr><td><strong><code>partially_refunded</code></strong></td><td>At least one session is <code>partially_refunded</code>.</td></tr><tr><td><strong><code>paid</code></strong></td><td>At least one session is <code>paid</code> or <code>partially_paid</code>, and the captured amount covers the order total.</td></tr><tr><td><strong><code>refunded</code></strong></td><td>At least one session is <code>refunded</code>.</td></tr><tr><td><strong><code>voided</code></strong></td><td>None of the above match — all remaining sessions are <code>voided</code>.</td></tr></tbody></table>

{% hint style="info" %}
Since a single payment status can't fully represent the combined state of multiple sessions, the conditions above are evaluated in order, top to bottom, and the first match wins — prioritizing statuses that still need action over settled ones (for example, `authorized` always takes precedence over `voided`, and `paid` always takes precedence over `refunded`).
{% endhint %}

{% hint style="warning" %}
Coverage for the `partially_paid` and `paid` checks is measured on money actually captured and still held, not on the sessions' declared amounts — a refund gives money back, so it stops counting toward the order total. A refund can therefore reopen an order: if two sessions cover the total and one of them is then partially refunded, the order goes back to `partially_paid`, since money is genuinely owed again. A session in `partially_refunded` status only makes the order's own payment status read the same way while the order stays fully covered (e.g. when its sessions had over-covered the total).
{% endhint %}

{% hint style="info" %}
The payment status alone doesn't reveal whether an order was ever over-collected — a session whose authorization pushed the order past its total still authorizes and captures normally, counting toward `payment_status` like any other (more details [here](/core-api-reference/2026-05/payment_sessions.md#exceeding-the-order-balance)).
{% endhint %}

## Payment sessions

The payment flow for an order is driven by [payment sessions](/core-api-reference/2026-05/payment_sessions.md).

{% hint style="success" %}
An order can have **multiple payment sessions** — each covering a portion of the order total.
{% endhint %}

{% hint style="warning" %}
An order can't mix payment sessions with a legacy ([deprecated](/core-api-reference/2026-05/readme.md#whats-deprecated) after version `2017-08`) payment source and/or payment method — whichever one you associate with the order first ties it to that version's payments workflow, and associating the other one afterward returns an error.
{% endhint %}

### Selecting a payment setting

Before creating a payment session, fetch the list of [payment settings](/core-api-reference/2026-05/payment_settings.md) available for the order via the `available_payment_settings` relationship. This list is derived from the payment settings associated with the market, optionally filtered by any [payment rules](/core-api-reference/2026-05/payment_rules.md) configured for the order.

### Creating a payment session

Create a payment session by associating it with the order and a chosen payment setting. When the session is created, a gateway interaction may be initiated automatically (e.g. to create a payment intent on Stripe or a session on Adyen). The session starts in `unpaid` status.

{% hint style="success" %}
Multiple sessions can be created to cover the full order total across different payment settings — **split payment**.
{% endhint %}

### Authorizing

Authorization can be initiated in two ways:

**Explicit** — Create a [payment authorization](/core-api-reference/2026-05/payment_authorizations.md) against the session. The authorization amount always equals the full session amount (partial authorization is not supported). The gateway call is processed asynchronously: the transaction starts in `pending` and transitions to `succeeded`, `declined`, or `failed` once the gateway responds. For gateways that require additional client-side steps (e.g. 3DS), the transaction may land in `requires_action`. In this case, the client must then update the authorization and send the `_payment_details` trigger attribute to resume the flow.

**Event-driven** — A webhook event from the payment provider signals a successful authorization linked to the session. The transaction is created (or updated) automatically, and no API call is required. This is the natural outcome of a pure client-side flow, where the customer completes payment through the provider's SDK and the gateway notifies Commerce Layer directly.

In both cases, a successful authorization transitions the session to `authorized` and triggers a recomputation of the order's payment status.

{% hint style="info" %}
If `auto_place` is enabled on the payment setting, the order is placed automatically when the session transitions to `authorized`, without requiring a separate `_place` call on the order.
{% endhint %}

{% hint style="warning" %}
A session can have any number of authorization attempts, but only one can ever succeed. A failed attempt (e.g. a declined 3DS challenge) can be retried with a new authorization against the same session — but once one has succeeded, no further attempt can succeed (more details [here](/core-api-reference/2026-05/payment_authorizations.md#retrying-a-failed-attempt)).
{% endhint %}

### Capturing

Capture the authorized funds by creating a [payment capture](/core-api-reference/2026-05/payment_captures.md) against the session, passing the related `payment_authorization`. The `amount_cents` is optional and defaults to the full remaining `capture_balance_cents` on the authorization.

{% hint style="success" %}
Partial captures are supported — multiple captures can be created against the same authorization.
{% endhint %}

{% hint style="info" %}
If `auto_capture` is enabled on the payment setting, a capture is created automatically in `succeeded` state as soon as the authorization succeeds, without requiring a separate API call.
{% endhint %}

### Voiding

Cancel an authorization before it is captured by creating a [payment void](/core-api-reference/2026-05/payment_voids.md) against the session, passing the related `payment_authorization`. The void amount must equal the full authorization amount.

{% hint style="info" %}
Only one succeeded void per authorization is allowed — a failed attempt can be retried with a new void, but only one can ever succeed (more details [here](/core-api-reference/2026-05/payment_voids.md#retrying-a-failed-attempt)).
{% endhint %}

### Refunding

Refund a captured amount by creating a [payment refund](/core-api-reference/2026-05/payment_refunds.md) against the session, passing the related `payment_capture`. The `amount_cents` is optional and defaults to the full remaining `refund_balance_cents` on the capture.

{% hint style="success" %}
Partial refunds are supported — multiple refunds can be created against the same capture.
{% endhint %}

{% hint style="warning" %}
Payment voids and refunds are always created explicitly via their respective endpoints. They are never triggered automatically, including when an order is cancelled.
{% endhint %}

### Wallets

Customers can store vaulted payment instruments for reuse via [payment wallets](/core-api-reference/2026-05/payment_wallets.md). A wallet can be linked to a payment session at creation time to enable faster checkout. Wallet support depends on the payment setting being configured as *vaultable* (e.g. Adyen, Braintree, Stripe, and external settings with a `token_url` configured).

## Asynchronous order placement

To place an order asynchronously, you just need to set the `place_async` flag to `true` (default is `false`). In this scenario, only a subset of the standard synchronous validations (needed to check the order integrity) is performed when passing the `_place` trigger attribute:

* **Customer** — the correct association with an existing customer is checked.
* **Billing** — the correct association with an existing billing address is checked.
* **Items** — the presence of at least one SKU, bundle, positive gift card (i.e. purchasing a gift card), or positive adjustment is checked.
* **Shipping** — the correct association with an existing shipping method and shipping address is checked.

If some [errors](#order-errors) occur at this stage, the order stays `pending` and cannot be placed. If those validations are successful, the order is moved to an intermediate status (`placing`) where the remaining validations are added:

* **Coupons** — the validity of the associated coupon code (if any) is checked.
* **Stock** — the availability of the necessary stock to fulfill the order is checked.
* **Payment rules** — any enabled payment rules configured on the market are evaluated. Order-subject rules can block placement if the payment coverage doesn't meet the configured threshold. Payment-setting-subject rules verify that the sessions associated with the order use only the allowed payment settings.
* **External validation** — if any (i.e. if an `external_order_validation_url` is set at the [market](/core-api-reference/2026-05/markets.md#external-urls) level).

If those validations are also successful, the order is safely placed and moved through the next steps of its lifecycle. If some of the additional validations throw an [error](#order-errors), you need to fix it (e.g. adding the missing stock) and manually re-trigger the `_place` (which will be again performed asynchronously unless the `place_async` flag is set back to `false`).

{% hint style="info" %}
The orders that are in `placing` status can be fetched but not edited by a sales channel. To patch them, you need to use [integration](/core/api-credentials.md#integration) API credentials. If you need the customer to fix the error that's keeping the order in `placing`, you can pass the `_pending` trigger attribute and move it back to the `pending` status.
{% endhint %}

{% hint style="warning" %}
Order placement does not trigger any payment processing. Authorization is always initiated separately — either explicitly by creating a payment authorization against the session, or automatically when the payment provider sends a webhook event signaling that an authorization linked to the session has succeeded.
{% endhint %}

## Auto-refresh

By default, orders that are still editable (i.e. in `draft` or `pending` status) are automatically refreshed each time they get updated and/or one of the related line items gets created, updated, or destroyed. This automatically triggers several actions:

* If the order is already associated with a shipping address, the related shipments are rebuilt based on the related [inventory model's strategy](/how-tos/inventory/strategies.md).
* Any active promotions are applied and the related discounts are recalculated.
* Any negative adjustment is redistributed on the [taxable items](#taxable-items).
* If the market associated with the order has a tax calculator, taxes get updated.
* All of the internal amounts and counters are refreshed.

{% hint style="info" %}
Removing both the shipping and billing addresses from an order with an associated tax calculator resets all previously computed tax amounts to zero, since no address means no taxable jurisdiction.
{% endhint %}

All of the above gets calculated in sequence for each of the order's line items, resulting in a fairly intensive computation, which can considerably slow down in case one or more [external resources](/core/external-resources.md) are involved. This may lead to concurrent request issues and/or timeout errors, making the automatic refresh not ideal, especially when you have to deal with a cart containing a large number of line items (e.g. B2B).

To better handle these specific scenarios, you can leverage the order's `autorefresh` attribute and set it to `false` (default is `true`) to prevent the automatic triggering of the above-mentioned actions. This choice will result in stale order data, but will ensure much faster order editing (the performance gain will build up exponentially with an increasing number of line items — we estimate an average boost of 500%). Once the editing is completed, the attribute can be updated back to `true`, which automatically triggers a new refresh of the order (in any case, remember that there is always the option to force a refresh manually even when the order auto-refresh is disabled by passing the `_refresh` trigger attribute).

{% hint style="warning" %}
If your top priority is pure performance and you're fine with getting a refreshed snapshot of the order just before placing it rather than in real time, we strongly recommend disabling the order auto-refresh option when needed.
{% endhint %}

## Order editing

Draft and pending orders are always editable by a sales channel before placement. Once an order is placed but still not approved, it can be moved to the `editing` status by passing the `_start_editing` trigger attribute — regardless of its current payment status. As soon as the editing operations are finished, the order must be moved back to the `placed` status by passing the `_stop_editing` trigger attribute.

{% hint style="warning" %}
For security reasons, only [integrations](/core/api-credentials.md#integration) are allowed to change the `_start_editing` and `_stop_editing` attributes of an order. When an order is in the `editing` status, it can be updated by the customer that placed it.
{% endhint %}

When an order is in editing, you can make almost any change you need (e.g. adding or removing line items, coupons, adjustments, etc.). There is no amount cap enforced at `_stop_editing` — the order total can be freely increased or decreased. Any payment adjustment required as a result of the edit must be handled manually via the order's payment sessions: a new authorization for an increased total, or an explicit void or refund for a reduced one. In the former case, a [payment link](/core-api-reference/2026-05/payment_links.md) can also be created for just the outstanding delta, rather than reattaching the whole order.

### Promotions and coupons

The discounts due to the active promotions applied at placement time are refreshed according to the changes applied to the order, even if the promotions should have expired in the meantime. New promotions (if any) triggered by the changes due to the order editing are not applied. Coupons applied at placement time are still valid (even single-use ones). New coupons can be applied, as long as one wasn't already present at the time of placement.

{% hint style="info" %}
Gift cards aren't applied at the order level. They are managed as payment instruments via [payment setting gift cards](/core-api-reference/2026-05/payment_setting_gift_cards.md) and payment sessions.
{% endhint %}

### Shipments and shipping methods

Shipments are rebuilt so, after any editing operation and before exiting the editing status, you need to check the [available shipping methods](https://docs.commercelayer.io/core-api-reference/2026-05/spaces/-Lk-ezuDClaMavTqnRi0/pages/-LyDmO63mY7E0Z-rsf5x#1.-get-the-available-shipping-methods) again (which may be different) and choose a new one to be [re-associated](https://docs.commercelayer.io/core-api-reference/2026-05/spaces/-Lk-ezuDClaMavTqnRi0/pages/-LyDmO63mY7E0Z-rsf5x#3.-select-a-shipping-method) with the edited order, otherwise the API will return an error on the `_stop_editing`.

{% hint style="info" %}
Orders can be edited multiple times before approval. Please note that once an editing operation is started, it can't be reverted. It can always be aborted by cancelling the order.
{% endhint %}

### Non-editable attributes

[Integrations](/core/api-credentials.md#integration) can edit almost every order attribute or relationship even if the order is in a non-editable status (i.e. a different one from `draft`, `pending`, or `editing` — more information [here](/core/roles-and-permissions.md)), with a few exceptions, listed below. If the order is no longer editable, the following attributes and relationships cannot be edited using any API credentials:

* `market`
* `customer`
* `shipping_address`
* `coupon_code`

{% hint style="info" %}
You can still edit the attributes above if the order is in the `placed` status by entering [order editing](#order-editing), otherwise they are considered frozen and not editable anymore.
{% endhint %}

{% hint style="danger" %}
Altering the non-editable attributes during the order's placement process is not permitted, since they can trigger collateral effects after the order has been placed (e.g. promotions application, shipments rebuild, etc.) and cause an inconsistent order status. If you patch the order by passing the `_place` trigger attribute together with some attribute or relationship that would alter any of the non-editable ones, no error is raised (to guarantee the order's placement), but the changes are silently ignored.
{% endhint %}

## Changing the order number

The default order numeric identifier can be changed according to your needs as long as the passed value is unique within the specific organization environment.

{% hint style="info" %}
This feature is available **for enterprise plans only** and can be activated by [environment](/core/api-specification.md#environments) (meaning you can request to enable it in test mode, in live mode, or both — inspect the related [organization](/core-api-reference/2026-05/organization.md)'s flags `order_number_editable_test` and `order_number_editable_live` to check your configuration).
{% endhint %}

## Stock reservation

When an order is placed, the [stock reservations](/core-api-reference/2026-05/stock_reservations.md) needed to block the whole order's stock are automatically created and the associated stock is reserved without decrementing the [stock item](/core-api-reference/2026-05/stock_items.md) quantities. Once the order is approved, the stock item quantities are decremented. If the order is cancelled, the reserved stock is released, becoming available again. You can temporarily reserve the stock associated with a line item before the order placement by sending a specific [trigger attribute](/core-api-reference/2026-05/line_items.md#reserving-the-stock) when adding the line item to the order.

## Automatic subscriptions generation

If a [subscription model](/core-api-reference/2026-05/subscription_models.md) is associated with the same market as an order, subscriptions can be automatically generated for all the line items that have a frequency, based on the strategy set at the [subscription model](/core-api-reference/2026-05/subscription_models.md#subscription-strategies) level. To [trigger the automatic order subscription generation](/how-tos/placing-orders/subscriptions/generating-the-subscriptions.md), you just need to update the source order after placement and pass the `_create_subscriptions` trigger attribute.

## Order validation

Automatic order validation is performed at the time of the order placement. Sometimes, you may need to implement custom validation (e.g. you want to support more complex validation rules specific to your business logic) on some orders, as an additional step before the order placement. To do that, you can leverage Commerce Layer's [external order validation](/core/external-resources/external-order-validation.md) feature — just make sure to correctly set up the URL of your external service (that will be in charge of computing the validation logic) at the [market](/core-api-reference/2026-05/markets.md) level and update the order(s) in question by setting the `_validate` trigger attribute to `true`.

<details>

<summary>How-to</summary>

Check the related [guide](/core/external-resources/external-order-validation.md) to learn how to validate orders via external services.

</details>

## Timed placement

You can add an expiration date for the placement of an order by setting the order's `expires_at` attribute (you need to use [integration](/core/api-credentials.md#integration) API credentials), after which the order placement attempt will fail. If the `expires_at` timestamp is set, you can also fill the `expiration_info` JSON object with any key/value pair you need to add useful information (e.g. messages to be shown on the frontend, a return URL for orders not placed due to expired time, etc.).

{% hint style="warning" %}
The value of the expiration timestamp will be checked against the current time only at the first placement attempt of the order (i.e. it will be ignored for the subsequent [order editing](#order-editing) updates, if any).
{% endhint %}

{% hint style="info" %}
This option can come in handy when dealing with quite common use cases such as ticketing processes, time-limited inventory, flash sales, and more.
{% endhint %}

## Order errors

The latest **10** errors occurring during the attempt to place an order (both synchronously and [asynchronously](#asynchronous-order-placement)), including the ones coming from [external validations](#order-validation) or [subscription](#automatic-subscriptions-generation) processes, are stored in the related [resource errors](/core-api-reference/2026-05/resource_errors.md) array until order approval. To inspect them, you just need to fetch the error including the resource errors association. You can also leverage the `errors_count` attribute (which is reset to **0** once the order is approved) at the order level.

## Taxable items

The taxable items of an order are used for tax and discount distributions. Line items of type `skus` and `bundles` are taxable by default. As for the other item types, you need to set this property by specifying the related attribute at the order level:

* Set `freight_taxable` to `true` if you want to add the line items of type `shipments` (i.e. shipping costs) to the taxable items list.
* Set `adjustment_taxable` to `true` if you want to add the line items of type `adjustments` (i.e. positive adjustments) to the taxable items list.
* Set `gift_card_taxable` to `true` if you want to add the line items of type `gift_cards` (i.e. purchased gift cards) to the taxable items list.

## Idempotency

{% hint style="info" %}
Order status changes are **idempotent**. Triggering `_place`, `_cancel`, or other status transitions multiple times is safe — no duplicate side effects (stock reservations, webhook events, fulfillment actions, etc.) will be produced.
{% endhint %}

Since the payment status is computed dynamically from the statuses of the associated payment sessions, it is always kept consistent automatically. If it appears out of sync — for example due to a gateway timeout during event processing — re-triggering the order status transition will force a recomputation without risk of creating duplicate transactions.

[Webhooks](/core-api-reference/2026-05/webhooks.md) events and stock item updates are likewise guaranteed to execute only once.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.commercelayer.io/core-api-reference/2026-05/orders.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
