# Workflow: Manage orders — browse, create, and run the order lifecycle

- **Audience:** API integrator / LLM agent
- **Base URL:** https://api.shirtplatform.com/webservices/rest
- **Auth:** HTTP Basic on `/auth`, then `x-auth-token` header on every request
  (see [workflow-authenticate.md](workflow-authenticate.md))
- **Preconditions:**
  - You are authenticated; `{accountId}` and `{shopId}` are known.
  - Any catalog entities an order references — products, colors, sizes, the destination `country` —
    already exist. Creating an order does **not** create those; you supply their ids.
- **Outcome:** You can list and read orders, create an order (two ways), update its external id,
  commit it, send it to production, cancel it, and delete it — the full lifecycle an integrator
  drives through the public API.

This document covers the order lifecycle on the main **Order** resource
(`/accounts/{accountId}/shops/{shopId}/orders`, backed by the interface `OrderResource`), plus the
one CreatorSE deferred-create endpoint that also lives under the **Order** group
(`/orders/usingCreatorSE`). Both render on the same reference page,
[resource_Order.html](resource_Order.html).

No payment is ever taken through this API. An order carries a *financial status* you set yourself
(see [commit](#24-commit-an-order)); no transaction is performed and no card data is collected.

It is split like the generated reference: **Part 1 — read-only `GET` endpoints** (inspect orders, no
side effects, no ordering constraint), then **Part 2 — the lifecycle operations** in the order you
run them (create → update → commit → send to production → cancel / delete).

**Related, documented elsewhere (cross-referenced, not repeated here):**

- The **current session order** (bind one order to your HTTP session and build it up without
  tracking its id, and merge items between orders) — see
  [workflow-session-order.md](workflow-session-order.md).
- Adding **items** to an order — the OrderedProduct resource
  ([resource_OrderedProduct.html](resource_OrderedProduct.html)); the relevant create endpoints are
  pointed to from [Part 2.2](#22-create-an-order) but their bodies are documented on their own page.

---

## Part 1 — Browse orders (read-only)

Every endpoint here is a `GET` with no side effects and no ordering constraint. Use them to inspect
orders and to read back the `id` / `uniqueId` / `version` the write operations in Part 2 consume.
List endpoints return a paged wrapper ([PagedData](data-types/PagedData.html)) and accept `page`
(zero-based, default `0`), `size` (default `10`), and `filterId` (id of a saved order filter; empty
string = all) query parameters.

Two shapes exist for reads: the **basic** shape ([Order](data-types/Order.html)) and the
**expanded** shape ([OrderExpanded](data-types/OrderExpanded.html)), which inlines the order's items
and fulfilment detail in one payload (larger response).

### 1.1 List orders

- **List:** `GET /accounts/{accountId}/shops/{shopId}/orders`
  ([reference](resource_Order.html#resource_Order-get-accounts-accountId-shops-shopId-orders)) →
  `200` [PagedData](data-types/PagedData.html) of [Order](data-types/Order.html).
- **List expanded:** `GET .../orders/expanded`
  ([reference](resource_Order.html#resource_Order-get-accounts-accountId-shops-shopId-orders-expanded)) →
  `200` [PagedData](data-types/PagedData.html) of [OrderExpanded](data-types/OrderExpanded.html).

### 1.2 Get a single order

By platform id:

- **Basic:** `GET .../orders/{orderId}`
  ([reference](resource_Order.html#resource_Order-get-accounts-accountId-shops-shopId-orders-orderId)) →
  `200` [Order](data-types/Order.html); `404` if no order with that id exists in this shop.
- **Expanded:** `GET .../orders/expanded/{orderId}`
  ([reference](resource_Order.html#resource_Order-get-accounts-accountId-shops-shopId-orders-expanded-orderId)) →
  `200` [OrderExpanded](data-types/OrderExpanded.html); `404` if not found.

By your own external id (the `uniqueId` you set on create or via [update](#23-update-an-orders-uniqueid)):

- **Basic:** `GET .../orders/by-uniqueid/{uniqueId}`
  ([reference](resource_Order.html#resource_Order-get-accounts-accountId-shops-shopId-orders-by-uniqueid-uniqueId)) →
  `200` [Order](data-types/Order.html); `404` if no order with that `uniqueId` exists in this shop.
- **Expanded:** `GET .../orders/expanded/by-uniqueid/{uniqueId}`
  ([reference](resource_Order.html#resource_Order-get-accounts-accountId-shops-shopId-orders-expanded-by-uniqueid-uniqueId)) →
  `200` [OrderExpanded](data-types/OrderExpanded.html); `404` if not found.

Read the basic-by-id endpoint to obtain the current `version` you need before an
[update](#23-update-an-orders-uniqueid).

---

## Response shapes (orders)

The **API-wide** response conventions — pagination (`page`/`size`, `pagedData.totalElements`), the JSON
(Jettison) single-element-object-vs-array collapse, and the `*Expanded` root used by expanded reads (and
the create response, see §2.2) — are documented in the **Media Type** and **Linking & Expansion**
quick-start guides and are not repeated here. One order-specific note:

- **Fresh orders omit fields that are not set yet (sparse serialization).** A just-created order may
  come back without `financialStatus`, `fulfilmentStatus`, `paid`, or its items — null/empty values are
  simply not serialized rather than sent as `null`. Do not treat a missing field as a schema change or
  an error; test for presence before reading it, and re-read the order after
  [commit](#24-commit-an-order) / [send to production](#25-send-an-order-to-production) once those
  statuses exist.

---

## Part 2 — The order lifecycle

The stages below run in sequence for a typical order: **create** it, optionally **update** its
`uniqueId`, **commit** it (set its financial status), **send it to production**, and — if needed —
**cancel** or **delete** it. The two mutating verbs behave as expected: `POST` create is **not**
idempotent (each call makes a new order); `PUT`/`DELETE` lifecycle transitions are safe to replay
(replaying a delete/cancel that already happened just returns the terminal state).

### 2.1 Financial status vs. fulfilment status (read this first)

An order carries two independent status axes, and the lifecycle operations act on different ones:

- **Financial status** — what you set with [commit](#24-commit-an-order)
  ([OrderFinancialStatus](data-types/OrderFinancialStatus.html): `PENDING`, `PAID`, …). This API
  never takes a payment; the status is purely a label you assign.
- **Fulfilment status** — where the order is in production
  ([OrderFulfillmentStatus](data-types/OrderFulfillmentStatus.html)). It gates
  [delete](#27-delete-an-order): once an order is locked in production or has a cancellation pending,
  it cannot be hard-deleted and must be [cancelled](#26-cancel-an-order) instead.

### 2.2 Create an order

There are two ways to create an order through the API. **Prefer the CreatorSE deferred create
([2.2.1](#221-preferred-creatorse-deferred-create)) as the default.** It creates the order together
with its full design/composition in a single request, which is simpler and safer for an integrator
for three concrete reasons — all verified against the implementation:

1. **One call instead of many.** Plain `save` ([2.2.2](#222-alternative-plain-create-then-add-items))
   creates only the bare order header; you then make a **separate call per item** through the
   OrderedProduct resource (`saveUsingBaseProduct` / `saveUsingCustomDesign`) for every product, and
   commit afterward. `saveDeferred` submits the order **and** its full item/design composition in one
   request — the whole payload is stored in a single operation.
2. **Less orchestration risk.** The multi-call plain path can fail partway (say item 3 of 5),
   leaving the order in a partially-built state you have to detect and repair. `saveDeferred`
   persists the whole submission in **one transaction**: it either all lands or none of it does, so
   there is no half-built intermediate state to reconcile.
3. **Asynchronous processing.** `saveDeferred` returns `200` as soon as the payload is **validated
   and persisted for deferred processing**; the order-composition / design-rendering pipeline runs
   **out of band** afterward (the stored submission is picked up later by a separate processor). Your
   request does not block on that pipeline, which matters for your own request timeouts.

Use the plain `save` path when you only need a basic order and will add items yourself, or when you
are building an order incrementally (in which case also consider the current session order —
[workflow-session-order.md](workflow-session-order.md)).

#### 2.2.1 Preferred — CreatorSE deferred create

Create the order with its full design/composition in one asynchronous call.

- **Request:** `POST /accounts/{accountId}/shops/{shopId}/orders/usingCreatorSE`
  ([reference](resource_Order.html#resource_Order-post-accounts-accountId-shops-shopId-orders-usingCreatorSE))
- **Body:** [CreatorSE.OrderDeferred](data-types/CreatorSE.OrderDeferred.html) — the order data
  including its designs, customer info, and shipping addresses.
- **Response 200:** [CreatorSE.OrderSubmited](data-types/CreatorSE.OrderSubmited.html) — carries the
  generated order `id`. The order payload has been accepted and queued; fulfilment is processed
  asynchronously.
- **Errors:** `400` validation failed — e.g. missing country or customer address.
- **DDF filename caveat:** when the order contains `DesignElementDdf` elements, each element's
  `productionResource.filename` is **required and must include a file extension** (e.g.
  `design.pdf`). A DDF element submitted without a filename extension is rejected with `400`.
- **Idempotency:** NOT idempotent — each call creates a new order.

#### 2.2.2 Alternative — plain create, then add items

Create a bare order header, then add each item and commit separately.

- **Request:** `POST /accounts/{accountId}/shops/{shopId}/orders`
  ([reference](resource_Order.html#resource_Order-post-accounts-accountId-shops-shopId-orders))
- **Body:** [Order](data-types/Order.html) — basic order data (`uniqueId`, financial status,
  customer note, and the destination `country`). A minimal create still requires several blocks to be
  **present**: `country`, `customer` with both `customer.billingAddress` and
  `customer.shippingAddress`, and `orderShipping`. The service fills in the detail of these nested
  objects itself (via its defaulting step), so near-empty objects are accepted — but the keys must be
  there. Omitting any of them is rejected with `400`.

    ```json
    {
      "productionOrder" : {
        "uniqueId" : "ext-order-123",
        "country" : { "id" : 12345 },
        "customer" : {
          "billingAddress" : {},
          "shippingAddress" : {}
        },
        "orderShipping" : {}
      }
    }
    ```

  > **Order `country` is the account catalog country — a different id from an address country.**
  > The order-level `country` is a [Country](data-types/Country.html) (`CountryPrime`): the account's
  > catalog country that carries VAT rates, currency, and price precision, and whose `id` is an
  > account-catalog identifier. It is **not** the same identifier space as the country inside an
  > [Address](data-types/Address.html): an address carries its country as a free-text **name** plus a
  > two-letter `countryCode`, never a catalog `Country.id`. The **same physical country therefore has
  > a different `id` at order level than any identifier used in an address** — do not copy an
  > order-level `country.id` into an address, and do not expect the two to match.

  > **The two create paths validate differently (same body, looser rules on `session`).** This plain
  > `POST .../orders` requires `country` + `customer.billingAddress` + `customer.shippingAddress` +
  > `orderShipping` to be present (else `400`). The current-session create
  > (`POST .../orders/session`, [workflow-session-order.md](workflow-session-order.md#step-1--start-a-current-session-order))
  > takes the **same** [Order](data-types/Order.html) body but succeeds with only `country` (plus an
  > optional `uniqueId`) — it does not require the customer/address/shipping blocks. Pick the path by
  > how much you have at create time; do not assume the validation carries over between them.

- **Response 200:** [OrderExpanded](data-types/OrderExpanded.html) — the created order, returned in the
  **expanded** shape (its JSON root element is `productionOrderExpanded`, not `productionOrder`).
  Extract `productionOrderExpanded.id` → `{orderId}` for every later step; the created order's
  `version` is `0`. (The by-id / by-uniqueId reads in Part 1 return the basic `productionOrder` root —
  only this create response uses the expanded root.)
- **Errors:** `400` the submitted order data failed validation.
- **Idempotency:** NOT idempotent — each call creates a new order.

**Then add items** through the OrderedProduct resource (documented on its own page —
[resource_OrderedProduct.html](resource_OrderedProduct.html)), one call per product:

- **Add a base (catalog) product:** `POST .../orders/{orderId}/orderedProducts/usingBaseProduct/{productId}`
  ([reference](resource_OrderedProduct.html#resource_OrderedProduct-post-accounts-accountId-shops-shopId-orders-orderId-orderedProducts-usingBaseProduct-productId)).
- **Add a Creator-designed product:** `POST .../orders/{orderId}/orderedProducts/usingCreatorSE`
  ([reference](resource_OrderedProduct.html#resource_OrderedProduct-post-accounts-accountId-shops-shopId-orders-orderId-orderedProducts-usingCreatorSE)).

Both return `200` with the created [OrderedProduct](data-types/OrderedProduct.html); see the
OrderedProduct reference for their request bodies and full status codes. After the items are
in place, [commit](#24-commit-an-order) the order.

#### 2.2.3 Third option — the current session order

For a multi-step build flow where you would rather not thread `{orderId}` through every call, bind
the order to your HTTP session and look it up by session instead. See
[workflow-session-order.md](workflow-session-order.md). It creates and reads the same
[Order](data-types/Order.html) — it is a lookup convenience, not a separate cart model.

### 2.3 Update an order's uniqueId

The `PUT {orderId}` endpoint updates **only** the order's `uniqueId` (your external identifier). All
other fields in the body are ignored.

- **Request:** `PUT .../orders/{orderId}`
  ([reference](resource_Order.html#resource_Order-put-accounts-accountId-shops-shopId-orders-orderId))
- **Body:** [Order](data-types/Order.html) — must carry the order's current `version` (optimistic
  locking) and the `uniqueId` to set.

    ```json
    {
      "productionOrder" : {
        "id" : 456,
        "version" : 3,
        "uniqueId" : "ext-order-999"
      }
    }
    ```

- **Response 204:** no body.
- **Errors:** `404` no order with the given id exists in this shop. A **stale `version`** is rejected
  (the update is not applied and the order is left unchanged) — see *Error handling* for the
  read-modify-write pattern and why the code is `500`, not `409`.
- **Idempotency:** safe to replay **with the current `version`** — but a second identical replay
  fails the version check (the first call incremented the stored version), so re-read the order's
  `version` before retrying.

### 2.4 Commit an order

Committing an order sets its financial status. This does **not** take a payment — it stamps the
status you name (default `PENDING`).

- **Request:** `PUT .../orders/{orderId}/commitOrder`
  ([reference](resource_Order.html#resource_Order-put-accounts-accountId-shops-shopId-orders-orderId-commitOrder))
- **Query param:** `financialStatus` — one of the
  [OrderFinancialStatus](data-types/OrderFinancialStatus.html) values (e.g. `PENDING`, `PAID`);
  defaults to `PENDING` when omitted.
- **Body:** none.
- **Response 204:** no body.
- **Idempotency:** safe to replay (re-committing sets the same status).

### 2.5 Send an order to production

Hands a committed order to the production and fulfilment pipeline. No body is needed: the hand-off is
derived entirely from data already stored on the order — the `Express` production flag when the order
is marked express, the order's invoice group when one is set, and the order's customer comment (if
present) attached as a production comment. Address and shipping data are **not** submitted here; they
must already be on the order from creation.

- **Request:** `PUT .../orders/{orderId}/sendToProduction`
  ([reference](resource_Order.html#resource_Order-put-accounts-accountId-shops-shopId-orders-orderId-sendToProduction))
- **Body:** none.
- **Response 204:** no body — the order was handed to the production pipeline.
- **Idempotency:** re-sending an order already in production is rejected downstream by the production
  tool (a duplicate active production order is refused), so a replay does not create a second
  production order.

### 2.6 Cancel an order

Cancels an existing order. Unlike [delete](#27-delete-an-order), cancel is the correct path for an
order that is already in production or otherwise no longer freely editable — it routes through the
cancellation flow rather than removing the row.

- **Request:** `DELETE .../orders/{orderId}/cancelOrder`
  ([reference](resource_Order.html#resource_Order-delete-accounts-accountId-shops-shopId-orders-orderId-cancelOrder))
- **Body:** none.
- **Response 204:** no body.
- **Idempotency:** safe to replay (an order already cancelled / awaiting cancellation stays in that
  state).

### 2.7 Delete an order

Deletes an order that is still freely editable. **This is not a general-purpose cancel** — it is
guarded against production state.

- **Request:** `DELETE .../orders/{orderId}`
  ([reference](resource_Order.html#resource_Order-delete-accounts-accountId-shops-shopId-orders-orderId))
- **Body:** none.
- **Response 200:** the order was deleted; **the response body is empty**. (Note: this `DELETE`
  returns `200`, not the `204` you might expect from the other lifecycle transitions.)
- **Errors:**
  - `400` — the order is **locked in production** (`fulfilment status NOT_EDITABLE`) or has a
    **pending cancellation** (`CANCELLATION_WAITING`), and cannot be deleted directly. Use
    [cancelOrder](#26-cancel-an-order) instead.
  - `404` — no order with the given id exists in this shop.
- **Idempotency:** safe to replay (a second call returns `404`).

### 2.8 Merge items between orders

To move ordered products out of one order and into another (for example consolidating a customer's
earlier order into the one they are working on now), use the `transferItems` endpoint. It is
documented as Step 4 of [workflow-session-order.md](workflow-session-order.md#step-4-optional--merge-items-from-another-order-into-the-current-session-order)
(`POST .../orders/{destinationOrderId}/transferItems` → `204`; `409` if a referenced order is missing
or the source order already has a financial status) and is not repeated here.

---

## Error handling and retries

### Optimistic locking on update (version conflict)

[Update](#23-update-an-orders-uniqueid) uses optimistic locking: the request body must carry the
order's current `version`, and a stale one is rejected.

1. **Read the current version.** `GET .../orders/{orderId}` →
   `{ "productionOrder" : { "id" : 456, "version" : 3, "uniqueId" : "..." } }`
2. **Update with that version.** `PUT .../orders/{orderId}` with `"version" : 3` → `204`. The server
   increments the stored version to `4`.
3. **Replay the same body (now stale).** `PUT` again with `"version" : 3` → **HTTP `500`**, not
   `409`. The implementation throws `org.hibernate.StaleObjectStateException`, which maps to
   `500`. There is no `409` for this condition — do not code against one. **Recover:** re-`GET` the
   order to read the new `version` (`4`), then retry.

### Create is not idempotent

Both create paths ([2.2.1](#221-preferred-creatorse-deferred-create) and
[2.2.2](#222-alternative-plain-create-then-add-items)) make a new order on every call. On a timeout,
do not blindly retry: search by your `uniqueId` (`GET .../orders/by-uniqueid/{uniqueId}`, Part 1) to
check whether the order already landed, and create only if it did not.

### Delete vs. cancel

A `400` from [delete](#27-delete-an-order) means the order is already in production or has a
cancellation pending — this is a state guard, not a transient error. Do not retry the delete; use
[cancelOrder](#26-cancel-an-order) instead.

### Not found

A `404` from the by-id / by-uniqueId reads, `update`, or `delete` means no such order exists in this
shop. The id-scoped lifecycle transitions ([commit](#24-commit-an-order),
[cancel](#26-cancel-an-order), [send to production](#25-send-an-order-to-production)) do **not**
surface `404` for an unknown order — an order you cannot access is rejected at authorization time
with `401` before the method runs.

## Related data types

- [Order](data-types/Order.html) — the basic order shape (list/by-id reads; plain-create body;
  update body). Rendered from `ProductionOrderPrime`.
- [OrderExpanded](data-types/OrderExpanded.html) — the expanded order shape (expanded reads;
  plain-create response).
- [PagedData](data-types/PagedData.html) — the paged wrapper for the list reads.
- [CreatorSE.OrderDeferred](data-types/CreatorSE.OrderDeferred.html) /
  [CreatorSE.OrderSubmited](data-types/CreatorSE.OrderSubmited.html) — deferred-create request /
  response.
- [OrderFinancialStatus](data-types/OrderFinancialStatus.html) — values for `commitOrder`.
- [OrderFulfillmentStatus](data-types/OrderFulfillmentStatus.html) — the fulfilment states that gate
  delete.
- [OrderedProduct](data-types/OrderedProduct.html) — the item created when you add a product to an
  order (OrderedProduct resource; rendered from `DesignedOrderedProductPrime`).
</content>
</invoke>
