---
title: Place an order
description: Create a draft order, add subscription line items, validate and price the order, then submit it for fulfillment using the API
---

Orders are the API's shopping cart. You create a draft order, configure it step by step with line items and customer details, price it, and submit it for fulfillment. Until submission, everything is editable — nothing is provisioned and nothing is charged.

This guide takes you through one complete integration. A US consumer, Jane Smith, orders a new
mobile subscription with a new phone number. The subscription is delivered as an eSIM to her
iPhone.

On the way, the guide covers every decision that you meet. An existing customer or a new one.
The pre-order validation tools. The price calculation, the submission requirements, and
cancellation.

## Prerequisites

You need all of these before you start:

- **API credentials**: Every request carries both an `Authorization: Bearer` access token and an `X-API-Key` header
- **Product offerings**: At least one `AVAILABLE` product offering to sell — see [Product Management](/developer-guide/use-cases/product-management.md)
- **Payment integration**: If your orders require payment, a way to run payment sessions — see [Payment Processing](/developer-guide/use-cases/payment-processing.md)

## Overview

**1. Choose a product offering**

List product offerings and pick the plan the customer is buying.

**2. Verify customer input with the order tools**

Validate the address, read the network coverage, and make sure that the device supports an eSIM.
Do all three before you build the order.

**3. Create a draft order**

Start an order for an existing customer or create the customer together with the order.

**4. Add a subscription line item**

Attach the product offering, subscriber details, and SIM configuration.

**5. Review the validation state**

Fetch the order and resolve any missing fields or validation errors.

**6. Read the price**

Read the exact total, including jurisdiction-level US taxes, before asking the customer to pay.

**7. Meet the requirements and submit**

Complete payment, payment profile, or signing requirements, then submit the order.

**8. Track the order to completion**

Watch the order state until the subscription is created and activated.

## The order lifecycle

An order's `state` tells you exactly what you can do with it:

| State              | Meaning                                                                                                 |
| ------------------ | ------------------------------------------------------------------------------------------------------- |
| `PENDING`          | Draft (cart) state. The order can be modified, priced, and submitted.                                   |
| `PENDING_PAYMENT`  | The order is locked and awaiting payment completion.                                                    |
| `SUBMITTED`        | You submitted the order for processing.                                                                 |
| `PENDING_APPROVAL` | The order needs admin or manager approval through `POST /orders/{orderId}/approve` before it continues. |
| `PROCESSING`       | The order is in fulfillment.                                                                            |
| `COMPLETED`        | The order was successfully fulfilled.                                                                   |
| `CANCELLED`        | The order was canceled before completion.                                                               |
| `EXPIRED`          | The order expired due to inactivity.                                                                    |
| `FAILED`           | Order fulfillment failed.                                                                               |

> **Note**
>
> A draft order expires. Every order carries an `expiresAt` timestamp, and each update moves it
> forward. A cart that nobody touches goes to `EXPIRED`.

## Step-by-step implementation

> **Info**
>
> Example responses in this guide are trimmed to the fields relevant to each step. The API always
> returns the complete object.

### Step 1: Choose a product offering

List the product offerings available to your customer type. The `productOfferingId` you pick here is what you attach to the order's line item. Filter by `types=SUBSCRIPTION` to only see plans that create a mobile subscription.

```bash
curl -X GET "{BASE_URL}/product-offerings?customerType=CONSUMER&types=SUBSCRIPTION" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY"
```

Jane picks the 10 GB plan:
```json
{
  "items": [
    {
      "productOfferingId": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
      "status": "AVAILABLE",
      "name": "Seamless 10GB",
      "description": "10GB of high-speed data with unlimited calls and texts",
      "customerType": "CONSUMER",
      "product": {
        "productId": "9b2f80c4-6a1d-4e3b-8c5f-7d9e0a1b2c3d",
        "internalName": "seamless_cell_10gb_us",
        "type": "SUBSCRIPTION",
        "category": "PRODUCT_CATEGORY_SUBSCRIPTION_CELL",
        "networkProviderId": "tmobile-us",
        "features": {
          "dataMb": 10240,
          "includedCallSeconds": 3600,
          "includedSms": 500
        }
      },
      "price": {
        "netPriceMinor": 2999,
        "currency": "USD",
        "priceType": "RECURRING",
        "billingCycle": {
          "period": "MONTHLY",
          "interval": 1
        },
        "standardDiscount": { "amountMinor": 500 },
        "bindingContract": {
          "duration": {
            "unit": "MONTHS",
            "value": 12
          },
          "discount": {
            "amountMinor": 200
          }
        },
        "customUpfrontPayment": {
          "billingCycles": 3,
          "discount": {
            "amountMinor": 300,
            "duration": { "unit": "MONTHS", "value": 3 }
          }
        },
        "currencyOptionsMinor": {
          "USD": 2999,
          "SEK": 29900
        }
      }
    }
  ],
  "pagination": {
    "nextCursor": null
  }
}
```

### Step 2: Verify customer input with the order tools

With the order tools you validate customer input at form time, before it becomes a validation error on the order. All four are stateless `POST` endpoints — call them as often as you like.

#### Validate the service address

In the US, the subscriber's address doubles as the E911 emergency address, so it must be precise. Validate it as soon as the customer types it.

```bash
curl -X POST "{BASE_URL}/tools/validate-address" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "address": {
      "street1": "826 Valencia St",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94110",
      "country": "US"
    }
  }'
```

```json
{
  "valid": true
}
```

> **Note**
>
> A response can carry a `suggestedAddress` for a valid input, when the network registry holds a
> more exact form of the address. Take that form. The formatting of the network prevents a
> provisioning fault later.

#### Check network coverage

Make sure that the customer gets service at their address. Show them the quality to expect on each technology.

```bash
curl -X POST "{BASE_URL}/tools/check-network-coverage" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "address": {
      "street1": "826 Valencia St",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94110",
      "country": "US"
    }
  }'
```

The `coverageLevel` is one of `EXCELLENT`, `GOOD`, `FAIR`, `POOR`, or `NO_COVERAGE`:
```json
{
  "address": {
    "street1": "826 Valencia St",
    "city": "San Francisco",
    "state": "CA",
    "zip": "94110",
    "country": "US"
  },
  "coverageLevel": "EXCELLENT",
  "networkProviderId": "tmobile-us"
}
```

#### Check device eSIM support

Jane wants an eSIM, so look up the IMEI of her phone. The response says whether the device supports an eSIM. Some networks also need the IMEI later, to activate the eSIM, so collect it now.

```bash
curl -X POST "{BASE_URL}/tools/get-device-info" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "imei": "356938035643809"
  }'
```

```json
{
  "imei": "356938035643809",
  "tac": "35693803",
  "esim": true,
  "manufacturer": "Apple",
  "model": "A2653",
  "marketingName": "iPhone 15 Pro"
}
```

#### Check porting eligibility (port-ins only)

Jane takes a new number, so this step does not apply to her. If your customer wants to bring
their own number, make sure that the number is portable before you collect the porting
details.

```bash
curl -X POST "{BASE_URL}/tools/check-porting-eligibility" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "msisdn": "+14155550188"
  }'
```

```json
{
  "msisdn": "+14155550188",
  "eligible": true,
  "networkProviderId": "att-us"
}
```

### Step 3: Create a draft order

Every order needs a `customerType` (`CONSUMER` or `BUSINESS`). Everything else can be added later, but the `customer` field is where you make your first real decision:

- **Existing customer** — pass `"customer": { "customerId": "..." }`. The `customerId` accepts
  the internal UUID and your own external reference ID. An external reference ID needs the
  `rid_` prefix, as in `rid_crm-customer-12345`, so that the API can tell it from a UUID.
- **New customer** — pass the details of the customer. `name` and `customerType` are required.
  The API creates the customer as part of order fulfillment. If you also pass a `referenceId`
  that a customer already carries, the API takes that customer and creates no duplicate. You
  can call this from a flow that does not know whether the customer exists.

The `user` field names the person who uses the services. It follows the same pattern. Pass
`userId` for a returning user, or `name` and `email` to create one.

Jane is new, so we create both the customer and the user with the order:

```bash
curl -X POST "{BASE_URL}/orders" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "CONSUMER",
    "customer": {
      "name": "Jane Smith",
      "customerType": "CONSUMER",
      "referenceId": "crm-cust-84321",
      "contact": {
        "email": "jane.smith@example.com",
        "msisdn": "+14155550123"
      },
      "billing": {
        "method": "EMAIL_INVOICE",
        "email": "jane.smith@example.com",
        "currency": "USD",
        "address": {
          "street1": "826 Valencia St",
          "city": "San Francisco",
          "state": "CA",
          "zip": "94110",
          "country": "US"
        }
      }
    },
    "user": {
      "name": "Jane Smith",
      "email": "jane.smith@example.com",
      "msisdn": "+14155550123"
    },
    "billing": {
      "name": "Jane Smith",
      "email": "jane.smith@example.com",
      "address": {
        "street1": "826 Valencia St",
        "city": "San Francisco",
        "state": "CA",
        "zip": "94110",
        "country": "US"
      }
    }
  }'
```

The response is a draft order in `PENDING` state. Note `newCustomer: true` — the customer record itself is created during fulfillment, so it has no `customerId` yet:
```json
{
  "orderId": "296af5b6-f3b3-4128-b307-5ddc9190502f",
  "state": "PENDING",
  "customer": {
    "customerType": "CONSUMER",
    "name": "Jane Smith",
    "newCustomer": true
  },
  "user": {
    "userId": "c47ac10b-58cc-4372-a567-0e02b2c3d479",
    "name": "Jane Smith",
    "newUser": true
  },
  "lineItems": [],
  "validation": {
    "isValid": false,
    "missingFields": ["lineItems"]
  },
  "requirements": {
    "requiresPayment": "REQUIRED",
    "requiresPaymentProfile": "NOT_REQUIRED",
    "requiresSigning": "NOT_REQUIRED"
  },
  "createdAt": "2026-07-12T17:00:00Z",
  "updatedAt": "2026-07-12T17:00:00Z",
  "expiresAt": "2026-07-19T17:00:00Z"
}
```

For an existing customer, the request collapses to:
```json
{
  "customerType": "CONSUMER",
  "customer": {
    "customerId": "b47ac10b-58cc-4372-a567-0e02b2c3d479"
  }
}
```

> **Note**
>
> You can also pass initial `lineItems` and a `promoCode` directly in the create request. This guide
> adds line items separately to show the progressive flow, but a single create call with everything
> inline is equally valid.

### Step 4: Add a subscription line item

Add the plan to the order with `POST /orders/{orderId}/line-items`. A `SUBSCRIPTION` line item requires `type`, a `lineItemId` you choose (unique within the order), and the `productOfferingId`. The `subscriber` and `sim` objects are required eventually — provide them here or fill them in later with an update.

For the phone number, you have three options:

- **Leave `msisdn` empty** to have a number assigned automatically (what Jane does).
- **Pick a number from the number pool** and pass both the `msisdn` and the `leaseToken` you received when leasing it.
- **Port in an existing number** by setting the `msisdn`, `portingRequested: true`, and `porting.details`.

```bash
curl -X POST "{BASE_URL}/orders/296af5b6-f3b3-4128-b307-5ddc9190502f/line-items" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "lineItem": {
      "type": "SUBSCRIPTION",
      "lineItemId": "line-item-1",
      "productOfferingId": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
      "subscriber": {
        "name": "Jane Smith",
        "email": "jane.smith@example.com",
        "address": {
          "street1": "826 Valencia St",
          "city": "San Francisco",
          "state": "CA",
          "zip": "94110",
          "country": "US"
        }
      },
      "sim": {
        "esim": true,
        "imei": "356938035643809"
      }
    }
  }'
```

The response echoes the line item:
```json
{
  "type": "SUBSCRIPTION",
  "lineItemId": "line-item-1",
  "productOfferingId": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
  "subscriber": {
    "name": "Jane Smith",
    "email": "jane.smith@example.com",
    "address": {
      "street1": "826 Valencia St",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94110",
      "country": "US"
    }
  },
  "sim": {
    "esim": true,
    "imei": "356938035643809"
  }
}
```

A line item for a port-in looks like this instead. US porting details require `firstName`,
`lastName`, and `address`. With `tempNumber: true` the customer gets a temporary number to use
until the port completes.
```json
{
  "type": "SUBSCRIPTION",
  "lineItemId": "line-item-1",
  "productOfferingId": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
  "msisdn": "+14155550188",
  "portingRequested": true,
  "tempNumber": true,
  "porting": {
    "details": {
      "firstName": "Jane",
      "lastName": "Smith",
      "accountNumber": "7724318842",
      "passcode": "4821",
      "address": {
        "street1": "826 Valencia St",
        "city": "San Francisco",
        "state": "CA",
        "zip": "94110",
        "country": "US"
      }
    }
  },
  "subscriber": {
    "name": "Jane Smith",
    "email": "jane.smith@example.com"
  },
  "sim": {
    "esim": true,
    "imei": "356938035643809"
  }
}
```

To change a line item while the order is still `PENDING`, use `PUT
/orders/{orderId}/line-items/{lineItemId}`. To remove one, use `DELETE
/orders/{orderId}/line-items/{lineItemId}`.

> **Warning**
>
> If the order carries anything to ship, add a `shipping` object with a recipient `name` and
> `address`. A physical SIM (`"esim": false`) and hardware both ship. Jane takes an eSIM, so this
> order needs no `shipping` object.

### Step 5: Review the order's validation state

Line items are returned as part of the order, so `GET /orders/{orderId}` is your single read for everything: line items, validation, requirements, and pricing.

```bash
curl -X GET "{BASE_URL}/orders/296af5b6-f3b3-4128-b307-5ddc9190502f" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY"
```

Jane's order is now complete and ready to submit:
```json
{
  "orderId": "296af5b6-f3b3-4128-b307-5ddc9190502f",
  "state": "PENDING",
  "customer": {
    "customerType": "CONSUMER",
    "name": "Jane Smith",
    "newCustomer": true
  },
  "lineItems": [
    {
      "type": "SUBSCRIPTION",
      "lineItemId": "line-item-1",
      "productOfferingId": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
      "sim": {
        "esim": true,
        "imei": "356938035643809"
      }
    }
  ],
  "validation": {
    "isValid": true
  },
  "requirements": {
    "requiresPayment": "REQUIRED",
    "requiresPaymentProfile": "NOT_REQUIRED",
    "requiresSigning": "NOT_REQUIRED"
  },
  "createdAt": "2026-07-12T17:00:00Z",
  "updatedAt": "2026-07-12T17:04:00Z",
  "expiresAt": "2026-07-19T17:04:00Z"
}
```

When something is missing, `validation` tells you exactly what, at both the order level and per line item:
```json
{
  "isValid": false,
  "missingFields": ["billing.address"],
  "lineItemValidation": [
    {
      "lineItemId": "line-item-1",
      "isValid": false,
      "missingFields": ["subscriber.name", "sim.iccid"]
    }
  ]
}
```

Fix missing fields with `PUT /orders/{orderId}` (order details) and `PUT /orders/{orderId}/line-items/{lineItemId}` (line item details), then re-fetch.

### Step 6: Read the price

The platform calculates the price again each time the order changes, and returns it as `pricing`
on the order. Read the order to get the exact amount due before you collect the payment. On a US
order the tax is calculated per jurisdiction, from the addresses on the order. The address fields
in Step 5 must be correct before this number means anything.

```bash
curl "{BASE_URL}/orders/296af5b6-f3b3-4128-b307-5ddc9190502f" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY"
```

```json
{
  "pricing": {
    "subtotalMinor": 2999,
    "taxAmountMinor": 450,
    "totalMinor": 2749,
    "taxIncluded": false,
    "currency": "USD",
    "recurringCosts": {
      "subtotalMinor": 2299,
      "totalMinor": 2299,
      "taxIncluded": false,
      "billingCycle": {
        "period": "MONTHLY",
        "interval": 1
      }
    },
    "lineItems": [
      {
        "lineItemId": "line-item-1",
        "description": "Seamless 10GB",
        "subtotalMinor": 2999,
        "discounts": [
          { "name": "Standard discount", "amountMinor": 500 },
          { "name": "12-month commitment", "amountMinor": 200 }
        ],
        "totalDiscountsMinor": 700,
        "taxAmountMinor": 450,
        "taxIncluded": false,
        "totalMinor": 2749,
        "recurringAmountMinor": 2299
      }
    ],
    "calculatedAt": "2026-07-12T17:05:00Z"
  }
}
```

This is where the discounts of the offering turn into money. The catalog listed Seamless 10GB
at `2999` in Step 1, and it still does. Here that `2999` is the `subtotalMinor`. The
`standardDiscount` and the `bindingContract.discount` of the offering come off as
`totalDiscountsMinor`. The platform calculates the tax on what is left, and `totalMinor` is the
amount to charge.

Three more discounts apply at this same point. A promo code on the order. A price list
assigned to the customer. A discount on the subscription.

> **Note**
>
> The platform cannot price an invalid order. If this operation returns an error, get the order and
> correct the `validation` problems first.

Amounts are integers in the minor units of `currency`, so `2749` is $27.49. The `subtotalMinor`
field gives the amount before discounts and tax. The platform reports the discounts of each line
item, so the total is `2999`, less the `700` of `totalDiscountsMinor`, plus the `450` of tax.

In the US, `recurringCosts` does not include `taxAmountMinor`. The platform calculates the tax on
a recurring charge when it makes the invoice. It does not estimate that tax here.

### Step 7: Meet the submission requirements and submit

The `requirements` object of the order tells you what must happen before you submit it. Each
requirement is `NOT_REQUIRED`, `OPTIONAL`, or `REQUIRED`. What you get depends on the platform
configuration and on the contents of the order. A prepaid order of free items alone can need
nothing. A postpaid order normally requires a card capture or a signature.

| Requirement              | When `REQUIRED`                                      | Provide on submit                                                                        |
| ------------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `requiresPayment`        | The order total must be paid before fulfillment      | an `externalPayment` reference, or pay through a payment session — no submit call needed |
| `requiresPaymentProfile` | A stored payment method is needed for future billing | `paymentProfileSessionId` from a completed profile session                               |
| `requiresSigning`        | The customer must digitally sign the order           | `signingSessionId` from a completed signing session                                      |

[Payment processing](/developer-guide/use-cases/payment-processing.md) covers how to create and
complete a payment session and a payment profile session. The [API reference](/api-reference.md)
documents a signing session.

An order that pays through a payment session or a payment link moves to `PENDING_PAYMENT`. The
platform submits it as soon as the payment succeeds. If the customer paid outside the platform,
pass an `externalPayment` object on submit, with a `reference` in it. The order then counts as
paid.

Jane's order has `requiresPayment: "REQUIRED"` and she pays through a hosted payment session, so there is no submit call to make. Once her payment succeeds, the platform submits the order — poll it until it leaves `PENDING_PAYMENT`:

```bash
curl "{BASE_URL}/orders/296af5b6-f3b3-4128-b307-5ddc9190502f" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY"
```

```json
{
  "orderId": "296af5b6-f3b3-4128-b307-5ddc9190502f",
  "state": "SUBMITTED",
  "paymentSessionId": "a1b2c3d4-e5f6-7890-1234-56789abcdef0",
  "submittedAt": "2026-07-12T17:08:00Z"
}
```

The submit endpoint accepts an order in the `PENDING` state only, and a payment can start on a
complete order only. A payment session and a payment link run the same validation as a submit,
so an order in `PENDING_PAYMENT` is known to be submittable already. Call submit yourself when
the requirements are met outside a payment session: an `externalPayment` reference, a payment
profile session, or a signing session.

### Step 8: Track the order to completion

After the submit, the order moves through `SUBMITTED` → `PROCESSING` → `COMPLETED`. It can
also go to `PENDING_APPROVAL` or `FAILED` on the way. Poll `GET /orders/{orderId}` for the
state. To poll nothing, subscribe to the `order.statusChanged` webhook event.

```bash
curl -X GET "{BASE_URL}/orders/296af5b6-f3b3-4128-b307-5ddc9190502f" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY"
```

On completion, `createdEntities` maps each line item to what it produced — Jane's subscription, with her newly assigned number:
```json
{
  "orderId": "296af5b6-f3b3-4128-b307-5ddc9190502f",
  "state": "COMPLETED",
  "customer": {
    "customerId": "b47ac10b-58cc-4372-a567-0e02b2c3d479",
    "customerType": "CONSUMER",
    "name": "Jane Smith",
    "newCustomer": true
  },
  "lineItems": [
    {
      "type": "SUBSCRIPTION",
      "lineItemId": "line-item-1",
      "productOfferingId": "3f2504e0-4f89-41d3-9a0c-0305e82c3301"
    }
  ],
  "createdEntities": {
    "subscriptions": [
      {
        "subscriptionId": "d5f7a2b1-3c4e-4f5a-8b9c-0d1e2f3a4b5c",
        "status": "ACTIVATED",
        "msisdn": "+14155550111",
        "display": "(415) 555-0111",
        "createdByLineItem": "line-item-1"
      }
    ]
  },
  "submittedAt": "2026-07-12T17:08:00Z",
  "completedAt": "2026-07-12T17:11:00Z"
}
```

> **Warning**
>
> Read `state` on the order before you tell the customer that the service is live. An order fulfills
> all its line items or none of them.

### Canceling a draft order

If the customer abandons the purchase, cancel the order. The cancel releases the resources that
the order reserved. Only an order in the `PENDING` state can be canceled. The optional body
accepts `metadata` for your own bookkeeping. An abandoned order that nobody cancels expires by
itself at its `expiresAt`.

```bash
curl -X POST "{BASE_URL}/orders/296af5b6-f3b3-4128-b307-5ddc9190502f/cancel" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "metadata": {
      "reason": "customer abandoned checkout"
    }
  }'
```

```json
{
  "orderId": "296af5b6-f3b3-4128-b307-5ddc9190502f",
  "state": "CANCELLED"
}
```

## Error handling

All order endpoints return a consistent error body with a human-readable `message`, a machine-readable `code`, optional per-field `details`, and a `hint` for resolution:
```json
{
  "message": "Order cannot be submitted",
  "code": "failed_precondition",
  "details": [
    {
      "message": "A completed payment session is required to submit this order",
      "code": "missing_payment_session",
      "property": "paymentSessionId"
    }
  ],
  "hint": "Fetch the order to review its validation state and requirements, then retry."
}
```

Your order flow must handle these statuses:

| Status | When it happens                                                                                                                                                         |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Malformed request — inspect `details` for the offending property.                                                                                                       |
| `401`  | Missing or expired access token.                                                                                                                                        |
| `403`  | The API key or token does not grant access to this resource.                                                                                                            |
| `404`  | Unknown `orderId` or `lineItemId`.                                                                                                                                      |
| `409`  | The order is not in a state that allows the operation — for example, modifying or canceling an order after submission.                                                  |
| `412`  | Submission preconditions are not met — the order is invalid or a `REQUIRED` requirement is unfulfilled. Re-fetch the order and inspect `validation` and `requirements`. |
| `429`  | Rate limited — back off and retry.                                                                                                                                      |
| `500`  | Unexpected server error — safe to retry.                                                                                                                                |

Every order endpoint that changes something accepts an `X-Idempotency-Key` header. Send one
unique key per logical operation, and a retry becomes safe. The same key on the same request
returns the original result. The same key on a modified request gets a `409`. A key expires
after 24 hours.

## Next steps

- [Order fulfillment](/developer-guide/use-cases/order-fulfillment.md) — Follow the order after submission: provisioning, activation, and fulfillment monitoring
- [Payment processing](/developer-guide/use-cases/payment-processing.md) — Create payment sessions and payment profiles to satisfy order requirements
