---
name: create-order-reference
scope: platform
description: "Schema-validated createOrder reference covering FulfilmentChoice linkage, ORDER::MULTI patterns, and readback query shapes."
load: on-demand
priority: high
relevant-skills: [fluent-test-data, fluent-e2e-test, fluent-trace, fluent-event-api, fluent-workflow-builder, fluent-feature-plan, fluent-feature-explain]
relevant-entities: [ORDER, FULFILMENT_CHOICE, FULFILMENT, CUSTOMER, PRODUCT, PAYMENT]
version: 1.1
last-updated: 2026-03-23
author: schema-introspection
source: live-schema + https://docs.fluentcommerce.com/by-type/create-order-using-graphql-mutation + https://docs.fluentcommerce.com/essential-knowledge/fulfilment-choice-entity
---

# createOrder Mutation Reference

Complete reference for the `createOrder` GraphQL mutation. All input and readback examples were re-validated against the live Fluent Commerce schema on 2026-03-23, with official Fluent docs used as the behavioral cross-check for mixed-basket (`ORDER::MULTI`) patterns.

## Mutation Signature

```graphql
mutation($input: CreateOrderInput!) {
  createOrder(input: $input) {
    id ref status type
  }
}
```

---

## Type Tree

### CreateOrderInput (root)

| Field | Type | Required | Notes |
|-------|------|:--------:|-------|
| `ref` | `String!` | YES | Must be unique across the account |
| `type` | `String!` | YES | Workflow routing key (e.g., `HD`, `CC`, `MULTI`) |
| `retailer` | `RetailerId!` | YES | **Wrapper object** `{ id: <ID> }` — NOT a bare ID or ref |
| `customer` | `CustomerId!` | YES | **Wrapper object** `{ id: <ID> }` — NOT a bare ID or ref |
| `items` | `[CreateOrderItemWithOrderInput]!` | YES | At least one item required |
| `fulfilmentChoice` | `CreateFulfilmentChoiceWithOrderInput` | no | **Singular** — use for single-FC orders |
| `fulfilmentChoices` | `[CreateFulfilmentChoiceWithOrderInput]` | no | **Plural** — use for split-shipment / mixed-FC orders |
| `totalPrice` | `Float` | no | Order total (excl. tax) |
| `totalTaxPrice` | `Float` | no | Tax total |
| `billingAddress` | `CreateOrderBillingAddressInput` | no | Billing address (all fields optional) |
| `payment` | `PaymentKey` | no | `{ ref: "PAY-REF" }` — links to existing Payment entity |
| `financialTransactions` | `[CreateFinancialTransactionWithOrderInput]` | no | Inline payment records |
| `attributes` | `[AttributeInput]` | no | Custom metadata |
| `ref2` | `String` | no | Secondary reference (must be unique if provided) |
| `tag1` / `tag2` / `tag3` | `String` | no | Searchable tags |

### RetailerId / CustomerId (wrapper objects)

```json
"retailer": { "id": "123" },
"customer": { "id": "456" }
```

These are **wrapper input types** with a single `id: ID!` field. Reuse the exact `id` value returned by discovery and do **not** substitute `ref`. GraphQL `ID` accepts scalar ID values, and Fluent queries commonly return them as strings. Discover via:
- Retailer: `retailers(first:1) { edges { node { id ref tradingName } } }`
- Customer: `customers(first:1, username: {...}) { edges { node { id firstName lastName } } }`

### CreateOrderItemWithOrderInput

| Field | Type | Required | Notes |
|-------|------|:--------:|-------|
| `ref` | `String!` | YES | Unique within the order |
| `productRef` | `String!` | YES | Must exist in the catalogue |
| `quantity` | `Int!` | YES | Must be >= 1 |
| `productCatalogueRef` | `String` | no | Which catalogue contains the product. Omit only if using the compatibility catalogue |
| `fulfilmentChoiceRef` | `String` | no | **Links this item to a specific FulfilmentChoice by ref**. Required for split-shipment orders |
| `paidPrice` | `Float` | no | Price paid (excl. tax) |
| `price` | `Float` | no | Unit price |
| `totalPrice` | `Float` | no | Line total |
| `taxPrice` | `Float` | no | Tax per unit |
| `totalTaxPrice` | `Float` | no | Line tax total |
| `taxType` | `String` | no | `GST`, `VAT`, or `EXCLTAX` |
| `currency` | `String` | no | ISO 4217 (e.g., `AUD`, `USD`, `EUR`) |
| `attributes` | `[AttributeInput]` | no | Item-level metadata |

### CreateFulfilmentChoiceWithOrderInput

| Field | Type | Required | Notes |
|-------|------|:--------:|-------|
| `deliveryType` | `String!` | YES | `STANDARD`, `EXPRESS`, `OVERNIGHT`, `3HOURS`, `NONE` (CC) |
| `ref` | `String` | no | Recommended — items link via `fulfilmentChoiceRef` |
| `type` | `String` | no | `HD`, `CC`, `SFS` (ship-from-store), or custom |
| `fulfilmentType` | `String` | no | Additional classification |
| `pickupLocationRef` | `String` | no | **Required for CC** — ref of the pickup store/location |
| `deliveryAddress` | `CreateCustomerAddressInput` | no | **Required for HD** — where to ship |
| `deliveryFirstName` | `String` | no | Recipient first name |
| `deliveryLastName` | `String` | no | Recipient last name |
| `deliveryContact` | `String` | no | Recipient phone |
| `deliveryEmail` | `String` | no | Recipient email |
| `deliverAfter` | `DateTime` | no | Earliest delivery window |
| `deliverBefore` | `DateTime` | no | Latest delivery window |
| `dispatchOn` | `DateTime` | no | Planned dispatch date |
| `currency` | `String` | no | ISO 4217 |
| `fulfilmentPrice` | `Float` | no | Shipping/collection fee |
| `fulfilmentTaxPrice` | `Float` | no | Tax on shipping fee |
| `deliveryInstruction` | `String` | no | Max 250 chars |
| `attributes` | `[AttributeInput]` | no | FC-level metadata (sourcing hints, consignment prefix, etc.) |

### CreateCustomerAddressInput (delivery address)

| Field | Type | Required | Notes |
|-------|------|:--------:|-------|
| `ref` | `String!` | YES | Unique address reference |
| `name` | `String` | no | Recipient name |
| `companyName` | `String` | no | Max 255 chars |
| `street` | `String` | no | Max 255 chars |
| `street2` | `String` | no | Max 255 chars |
| `city` | `String` | no | Max 255 chars |
| `state` | `String` | no | Max 200 chars |
| `postcode` | `String` | no | Max 100 chars |
| `region` | `String` | no | Max 250 chars |
| `country` | `String` | no | Max 100 chars (ISO 3166) |
| `latitude` | `Float` | no | |
| `longitude` | `Float` | no | |
| `timeZone` | `String` | no | Max 32 chars |
| `email` | `String` | no | Max 255 chars |

### CreateFinancialTransactionWithOrderInput

| Field | Type | Required | Notes |
|-------|------|:--------:|-------|
| `ref` | `String!` | YES | Unique transaction ref |
| `type` | `String!` | YES | Max 25 chars. e.g., `PAYMENT`, `REFUND` |
| `amount` | `Float!` | YES | Transaction amount |
| `currency` | `String!` | YES | ISO 4217 (3 chars) |
| `paymentMethod` | `String!` | YES | e.g., `CREDIT_CARD`, `PAYPAL`, `CASH` |
| `scale` | `Int` | no | Decimal places for unscaled value |
| `unscaledValue` | `Int` | no | Amount without decimals (for precision) |
| `externalTransactionCode` | `String` | no | External payment gateway code |
| `externalTransactionId` | `String` | no | External payment gateway ID |
| `cardType` | `String` | no | e.g., `VISA`, `MASTERCARD` |
| `paymentProvider` | `String` | no | e.g., `STRIPE`, `ADYEN` |
| `attributes` | `[AttributeInput]` | no | |

### AttributeInput

| Field | Type | Required | Notes |
|-------|------|:--------:|-------|
| `name` | `String!` | YES | Attribute key |
| `type` | `String!` | YES | `STRING`, `INTEGER`, `FLOAT`, `BOOLEAN`, `JSON` |
| `value` | `Json!` | YES | **Any valid JSON value** — see gotchas below |

---

## The Item-to-FulfilmentChoice Link

This is the most misunderstood relationship in the createOrder mutation.

```
Order
  |
  +-- fulfilmentChoice (singular)     OR    fulfilmentChoices[] (plural)
  |     ref: "FC-HD-001"                      [{ ref: "FC-HD-001" }, { ref: "FC-CC-001" }]
  |
  +-- items[]
        item[0].fulfilmentChoiceRef: "FC-HD-001"   --> links to FC-HD-001
        item[1].fulfilmentChoiceRef: "FC-CC-001"   --> links to FC-CC-001
```

**Rules:**
1. Use `fulfilmentChoice` (singular) when ALL items share one fulfilment choice
2. Use `fulfilmentChoices` (plural) when items have DIFFERENT fulfilment choices
3. Each item's `fulfilmentChoiceRef` must match a `fulfilmentChoice.ref` — this is the join key
4. If using singular `fulfilmentChoice`, items can omit `fulfilmentChoiceRef` (auto-linked)
5. If using plural `fulfilmentChoices`, every item MUST have `fulfilmentChoiceRef`

---

## Input Contract vs Query Contract

The most common `ORDER::MULTI` mistake is mixing up the **mutation input join key** with the **query readback shape**.

### Input-side linkage (`createOrder`)

- Use `items[].fulfilmentChoiceRef` to link an order item to a fulfilment choice by `ref`
- Use `fulfilmentChoice` for one-FC orders and `fulfilmentChoices[]` for mixed or split orders
- Use `items[].productRef` and `items[].productCatalogueRef` on input

### Readback-side linkage (`orders` / `orderById`)

- Query `order.fulfilmentChoice { ... }` for the primary/singular FC
- Query `order.fulfilmentChoices { edges { node { ... } } }` for all FCs on mixed baskets
- Query `order.items { edges { node { fulfilmentChoice { ... } } } }` to verify which FC an item is linked to
- Query `order.items { edges { node { product { ... } } } }` to read the resolved product
- `Fulfilment.fulfilmentChoiceRef` remains queryable and is useful when tracing downstream fulfilment creation

**Important:** `fulfilmentChoiceRef` is a valid field on `CreateOrderItemWithOrderInput`, but on the validated readback schema `OrderItem` exposes `fulfilmentChoice { ... }`, not an `OrderItem.fulfilmentChoiceRef` scalar.

### Validated linkage query

```graphql
query orderLinkage($ref: [String!]) {
  orders(ref: $ref, first: 1) {
    edges {
      node {
        id
        ref
        type
        status
        fulfilmentChoice {
          id
          ref
          type
          status
          deliveryType
        }
        fulfilmentChoices {
          edges {
            node {
              id
              ref
              type
              status
              deliveryType
              items {
                edges {
                  node {
                    id
                    ref
                    quantity
                    order {
                      id
                      ref
                    }
                    fulfilmentChoice {
                      id
                      ref
                      type
                      status
                    }
                  }
                }
              }
              fulfilments {
                edges {
                  node {
                    id
                    ref
                    status
                    fulfilmentChoiceRef
                  }
                }
              }
            }
          }
        }
        items {
          edges {
            node {
              id
              ref
              quantity
              status
              fulfilmentChoice {
                id
                ref
                type
                status
                deliveryType
              }
              product {
                id
                ref
              }
            }
          }
        }
      }
    }
  }
}
```

Use this readback query after `createOrder` to prove the item-to-FC mapping actually materialized the way the mutation input intended.

---

## Pattern 1: Simple Home Delivery (HD)

Single fulfilment choice, all items shipped to one address.

```graphql
mutation createHDOrder($input: CreateOrderInput!) {
  createOrder(input: $input) {
    id ref status type
  }
}
```

```json
{
  "input": {
    "ref": "ORD-HD-20260323-001",
    "type": "HD",
    "retailer": { "id": 123 },
    "customer": { "id": 456 },
    "items": [
      {
        "ref": "ORD-HD-20260323-001-ITEM-1",
        "productRef": "PROD-SKU-001",
        "productCatalogueRef": "MASTER_CATALOGUE",
        "quantity": 2,
        "paidPrice": 29.99,
        "currency": "AUD",
        "taxType": "GST",
        "taxPrice": 2.73
      },
      {
        "ref": "ORD-HD-20260323-001-ITEM-2",
        "productRef": "PROD-SKU-002",
        "productCatalogueRef": "MASTER_CATALOGUE",
        "quantity": 1,
        "paidPrice": 49.99,
        "currency": "AUD"
      }
    ],
    "fulfilmentChoice": {
      "ref": "ORD-HD-20260323-001-FC",
      "type": "HD",
      "deliveryType": "STANDARD",
      "deliveryAddress": {
        "ref": "ORD-HD-20260323-001-ADDR",
        "name": "Jane Smith",
        "street": "42 Wallaby Way",
        "city": "Sydney",
        "state": "NSW",
        "postcode": "2000",
        "country": "AU"
      },
      "attributes": [
        { "name": "sourcingLocationRef", "type": "STRING", "value": "LOC-WH-001" },
        { "name": "consignmentPrefix", "type": "STRING", "value": "A_" }
      ]
    },
    "totalPrice": 109.97,
    "totalTaxPrice": 10.00
  }
}
```

**When to use:** Standard ecommerce order. One delivery address, one or more items, all shipped together.

---

## Pattern 2: Click & Collect (CC)

Customer picks up from a store. No delivery address — `pickupLocationRef` instead.

```json
{
  "input": {
    "ref": "ORD-CC-20260323-001",
    "type": "CC",
    "retailer": { "id": 123 },
    "customer": { "id": 456 },
    "items": [
      {
        "ref": "ORD-CC-20260323-001-ITEM-1",
        "productRef": "PROD-SKU-001",
        "productCatalogueRef": "MASTER_CATALOGUE",
        "quantity": 1,
        "paidPrice": 29.99
      }
    ],
    "fulfilmentChoice": {
      "ref": "ORD-CC-20260323-001-FC",
      "type": "CC",
      "deliveryType": "NONE",
      "pickupLocationRef": "LOC-STORE-001"
    }
  }
}
```

**Key differences from HD:**
- `deliveryType`: `"NONE"` (customer picks up, no shipping)
- `pickupLocationRef`: ref of the store/location (must exist)
- No `deliveryAddress`

---

## Pattern 3: Split Shipment (MULTI with multiple FCs)

Items fulfilled from different sources. Uses `fulfilmentChoices` (plural) and each item links via `fulfilmentChoiceRef`.

```json
{
  "input": {
    "ref": "ORD-SPLIT-20260323-001",
    "type": "MULTI",
    "retailer": { "id": 123 },
    "customer": { "id": 456 },
    "items": [
      {
        "ref": "ORD-SPLIT-20260323-001-ITEM-1",
        "productRef": "PROD-SKU-001",
        "productCatalogueRef": "MASTER_CATALOGUE",
        "quantity": 1,
        "fulfilmentChoiceRef": "ORD-SPLIT-20260323-001-FC-HD"
      },
      {
        "ref": "ORD-SPLIT-20260323-001-ITEM-2",
        "productRef": "PROD-SKU-002",
        "productCatalogueRef": "MASTER_CATALOGUE",
        "quantity": 1,
        "fulfilmentChoiceRef": "ORD-SPLIT-20260323-001-FC-HD"
      },
      {
        "ref": "ORD-SPLIT-20260323-001-ITEM-3",
        "productRef": "PROD-SKU-003",
        "productCatalogueRef": "MASTER_CATALOGUE",
        "quantity": 1,
        "fulfilmentChoiceRef": "ORD-SPLIT-20260323-001-FC-CC"
      }
    ],
    "fulfilmentChoices": [
      {
        "ref": "ORD-SPLIT-20260323-001-FC-HD",
        "type": "HD",
        "deliveryType": "STANDARD",
        "deliveryAddress": {
          "ref": "ORD-SPLIT-20260323-001-ADDR",
          "name": "Jane Smith",
          "street": "42 Wallaby Way",
          "city": "Sydney",
          "postcode": "2000",
          "country": "AU"
        }
      },
      {
        "ref": "ORD-SPLIT-20260323-001-FC-CC",
        "type": "CC",
        "deliveryType": "NONE",
        "pickupLocationRef": "LOC-STORE-001"
      }
    ]
  }
}
```

**Critical rules for split shipment:**
- Use `fulfilmentChoices` (plural array), NOT `fulfilmentChoice` (singular)
- Every item MUST have `fulfilmentChoiceRef` matching one of the FC refs
- Items 1 & 2 ship to home (HD), item 3 goes to store pickup (CC)

### Variant: Multi-location HD split within ORDER::MULTI

`ORDER::MULTI` does **not** mean each fulfilment choice must have a different FC `type`. A common pattern is one mixed-basket order with multiple `HD` fulfilment choices, each routed to a different sourcing location.

```json
{
  "input": {
    "ref": "ORD-MULTI-HD-20260323-001",
    "type": "MULTI",
    "retailer": { "id": "123" },
    "customer": { "id": "456" },
    "items": [
      {
        "ref": "ORD-MULTI-HD-20260323-001-ITEM-1",
        "productRef": "PROD-SKU-001",
        "productCatalogueRef": "MASTER_CATALOGUE",
        "quantity": 1,
        "fulfilmentChoiceRef": "ORD-MULTI-HD-20260323-001-FC-HD-WH1"
      },
      {
        "ref": "ORD-MULTI-HD-20260323-001-ITEM-2",
        "productRef": "PROD-SKU-002",
        "productCatalogueRef": "MASTER_CATALOGUE",
        "quantity": 1,
        "fulfilmentChoiceRef": "ORD-MULTI-HD-20260323-001-FC-HD-WH2"
      }
    ],
    "fulfilmentChoices": [
      {
        "ref": "ORD-MULTI-HD-20260323-001-FC-HD-WH1",
        "type": "HD",
        "deliveryType": "STANDARD",
        "deliveryAddress": {
          "ref": "ORD-MULTI-HD-20260323-001-ADDR-1",
          "name": "Jane Smith",
          "street": "42 Wallaby Way",
          "city": "Sydney",
          "postcode": "2000",
          "country": "AU"
        },
        "attributes": [
          { "name": "sourcingLocationRef", "type": "STRING", "value": "LOC-WH-001" }
        ]
      },
      {
        "ref": "ORD-MULTI-HD-20260323-001-FC-HD-WH2",
        "type": "HD",
        "deliveryType": "STANDARD",
        "deliveryAddress": {
          "ref": "ORD-MULTI-HD-20260323-001-ADDR-2",
          "name": "Jane Smith",
          "street": "42 Wallaby Way",
          "city": "Sydney",
          "postcode": "2000",
          "country": "AU"
        },
        "attributes": [
          { "name": "sourcingLocationRef", "type": "STRING", "value": "LOC-WH-002" }
        ]
      }
    ]
  }
}
```

**Why this matters:**
- The order `type` stays `MULTI`
- Each FC can still be `HD`, `CC`, `SFS`, or another implementation-specific type
- The split is expressed by multiple FC refs plus item-level `fulfilmentChoiceRef`
- Source-specific routing belongs on the FC, commonly via attributes such as `sourcingLocationRef`

---

## Pattern 4: Order with Financial Transactions

Include payment records inline with the order.

```json
{
  "input": {
    "ref": "ORD-PAY-20260323-001",
    "type": "HD",
    "retailer": { "id": 123 },
    "customer": { "id": 456 },
    "items": [
      {
        "ref": "ORD-PAY-20260323-001-ITEM-1",
        "productRef": "PROD-SKU-001",
        "productCatalogueRef": "MASTER_CATALOGUE",
        "quantity": 1,
        "paidPrice": 99.99
      }
    ],
    "fulfilmentChoice": {
      "ref": "ORD-PAY-20260323-001-FC",
      "type": "HD",
      "deliveryType": "EXPRESS",
      "fulfilmentPrice": 12.00,
      "deliveryAddress": {
        "ref": "ORD-PAY-20260323-001-ADDR",
        "street": "100 George St",
        "city": "Sydney",
        "postcode": "2000",
        "country": "AU"
      }
    },
    "totalPrice": 111.99,
    "financialTransactions": [
      {
        "ref": "ORD-PAY-20260323-001-FT-1",
        "type": "PAYMENT",
        "amount": 111.99,
        "currency": "AUD",
        "paymentMethod": "CREDIT_CARD",
        "cardType": "VISA",
        "paymentProvider": "STRIPE",
        "externalTransactionId": "pi_3abc123"
      }
    ],
    "billingAddress": {
      "name": "Jane Smith",
      "street": "100 George St",
      "city": "Sydney",
      "postcode": "2000",
      "country": "AU"
    }
  }
}
```

---

## Pattern 5: Minimal Order (fewest possible fields)

Absolute minimum required fields. Useful for smoke tests.

```json
{
  "input": {
    "ref": "ORD-MIN-20260323-001",
    "type": "HD",
    "retailer": { "id": 123 },
    "customer": { "id": 456 },
    "items": [
      {
        "ref": "ORD-MIN-20260323-001-ITEM-1",
        "productRef": "PROD-SKU-001",
        "quantity": 1
      }
    ],
    "fulfilmentChoice": {
      "deliveryType": "STANDARD"
    }
  }
}
```

Note: `fulfilmentChoice.ref`, `productCatalogueRef`, and `deliveryAddress` are all optional. This creates a valid order but with minimal data — workflows may require attributes that aren't present.

---

## Return Fields

**Input field names differ from return field names.** The mutation input uses `amount` but the return type uses `total`. The input uses `productRef` but the return type uses `product { ... on VariantProduct { ref } }`.

### Validated return field selection

```graphql
mutation createOrder($input: CreateOrderInput!) {
  createOrder(input: $input) {
    # Order fields
    id ref status type totalPrice totalTaxPrice

    # Items — product is a UNION type, needs inline fragment
    items {
      edges {
        node {
          id ref quantity status
          product { ... on VariantProduct { ref name } }
          fulfilmentChoice { id ref type }
        }
      }
    }

    # Singular FC return
    fulfilmentChoice {
      id ref type deliveryType
      deliveryAddress { ref name street city postcode country }
    }

    # Plural FC return (for split orders)
    fulfilmentChoices {
      edges {
        node { id ref type deliveryType pickupLocationRef }
      }
    }

    # Financial
    financialTransactions {
      edges {
        node { id ref type total currency paymentMethod status }
      }
    }

    # Billing
    billingAddress { name street city postcode country }

    # Related entities
    customer { id firstName lastName primaryEmail }
    retailer { id tradingName }
  }
}
```

### Input vs Return field name differences

| Concept | Input field name | Return field name |
|---------|-----------------|-------------------|
| Product reference | `items[].productRef` (string) | `items[].product { ... on VariantProduct { ref } }` (union) |
| Transaction amount | `financialTransactions[].amount` (Float) | `financialTransactions[].total` (Float) |
| Fulfilment choices | `fulfilmentChoices[]` (flat array input) | `fulfilmentChoices { edges { node { ... } } }` (Relay connection) |
| Items | `items[]` (flat array input) | `items { edges { node { ... } } }` (Relay connection) |

---

## Gotchas

### 1. RetailerId and CustomerId are wrapper objects

```json
// WRONG — bare integer
"retailer": 123,
"customer": 456

// WRONG — string ref
"retailer": "RET-001",
"customer": "CUST-001"

// CORRECT — wrapper object with numeric ID
"retailer": { "id": 123 },
"customer": { "id": 456 }
```

### 2. AttributeInput.value is `Json!` scalar

The `value` field accepts **any valid JSON value** — string, number, boolean, object, array. When passing via GraphQL variables (JSON), just use the native JSON type:

```json
{ "name": "colour",    "type": "STRING",  "value": "red" }
{ "name": "weight",    "type": "FLOAT",   "value": 2.5 }
{ "name": "express",   "type": "BOOLEAN", "value": true }
{ "name": "tags",      "type": "JSON",    "value": ["fragile", "oversized"] }
{ "name": "metadata",  "type": "JSON",    "value": { "source": "web", "channel": "AU" } }
```

### 3. fulfilmentChoice (singular) vs fulfilmentChoices (plural)

| Scenario | Use | fulfilmentChoiceRef on items? |
|----------|-----|:----------------------------:|
| All items → one delivery | `fulfilmentChoice` (singular) | Optional (auto-linked) |
| Items → different deliveries | `fulfilmentChoices` (plural array) | **Required on every item** |
| Mixed HD + CC | `fulfilmentChoices` (plural array) | **Required on every item** |

Never use both `fulfilmentChoice` and `fulfilmentChoices` on the same order.

### 4. Item refs must be unique within the order

```json
// WRONG — duplicate refs
{ "ref": "ITEM-1", "productRef": "SKU-A", "quantity": 1 },
{ "ref": "ITEM-1", "productRef": "SKU-B", "quantity": 1 }

// CORRECT — unique refs
{ "ref": "ORD-001-ITEM-1", "productRef": "SKU-A", "quantity": 1 },
{ "ref": "ORD-001-ITEM-2", "productRef": "SKU-B", "quantity": 1 }
```

**Best practice:** Prefix item refs with the order ref to guarantee uniqueness.

### 5. deliveryAddress.ref is required when providing a delivery address

```json
// WRONG — ref missing
"deliveryAddress": { "street": "42 Wallaby Way", "city": "Sydney" }

// CORRECT
"deliveryAddress": { "ref": "ORD-001-ADDR", "street": "42 Wallaby Way", "city": "Sydney" }
```

### 6. productCatalogueRef should always be provided

While technically optional (falls back to compatibility catalogue), omitting it is error-prone. Always pair `productRef` with `productCatalogueRef` to ensure the product is found in the correct catalogue.

### 7. The order type drives workflow routing

The `type` field (e.g., `HD`, `CC`, `MULTI`) determines which workflow processes the order. The Orchestration Engine matches `type` against workflow trigger conditions. Using an unrecognized type means no workflow picks it up.

Common types:
- `HD` — Home Delivery
- `CC` — Click & Collect
- `MULTI` — Mixed fulfilment (split shipment)
- Custom types defined by the implementation

### 8. DateTime format

`deliverAfter`, `deliverBefore`, `dispatchOn` use ISO 8601: `"2026-03-23T10:00:00.000Z"`

### 9. Relay connections on return but flat arrays on input

Input takes flat JSON arrays (`items: [...]`). Return fields use Relay connections (`items { edges { node { ... } } }`). Don't confuse the two.

---

## Discovery Queries (prerequisites)

Before calling `createOrder`, discover the required IDs and refs:

```graphql
# Retailer ID
query { retailers(first: 1) { edges { node { id ref tradingName } } } }

# Customer ID
query { customers(first: 1) { edges { node { id firstName lastName primaryEmail } } } }

# Product refs + catalogue
query { variantProducts(first: 5, catalogue: { ref: "MASTER_CATALOGUE" }) {
  edges { node { ref name status catalogue { ref } } }
} }

# Location refs (for CC pickup or sourcing)
query { locations(first: 10, type: "STORE", status: "ACTIVE") {
  edges { node { id ref name type status } }
} }
```

---

## Workflow Side Effects

Calling `createOrder` automatically fires a `CREATE` event on the new Order entity. The Orchestration Engine then:

1. Matches the event to an ORDER workflow (by `type` or catch-all)
2. Executes the `CREATED` status ruleset
3. Typically triggers sourcing, fulfilment creation, inventory reservation

No manual event dispatch is needed after `createOrder` — the workflow starts automatically.
