# Creating Orders

Canonical URL: https://api-docs.test-printeers.com/guides/creating-orders

Learn how to create orders through the API, including image uploads, shipping options, and address handling.

## Overview

Creating an order is a two-step process: upload print images, then place the order. The flow looks like this:

```
                Upload print image (JPEG/PNG)
                        POST /images
                             │
         Browse catalog      │
         GET /products       │
                 │           │
                 ▼           ▼
           Product_xxx + Image_xxx + quantity
                   POST /orders
                         │
                         ▼
                    Order_xxx
                         │
            ┌────────────┴────────────┐
            ▼                         ▼
       "dropship"                "combined"
   Ships to customer         Ships to your location
```

## Images

Print products require a print image. Upload each image as a `POST /images` request with the raw image bytes in the body.

```bash
curl -X POST https://api.printeers.com/v2/images \
  -H "X-Printeers-Secret-Key: $KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @design.png
```

```json
{
  "image": {
    "reference": "Image_pdcptbw3k48sv03wy338bb8jzm"
  }
}
```

Accepted formats are JPEG and PNG. Images larger than 7000x7000 pixels are automatically downsized. Uploading the same image twice returns the same reference. Image uploads count against the `uploads` rate-limit bucket; see the [Rate Limiting](https://api-docs.test-printeers.com/guides/rate-limiting.md) guide.

Orderlines with non-print products (e.g. a screenprotector) must not have an image reference.

## Shipping Kinds

Every order has a shipping kind that determines where the package is sent.

| Kind | Destination | Use case |
|------|-------------|----------|
| `dropship` | Customer address you provide | Direct-to-consumer fulfillment |
| `combined` | Your warehouse | You handle final distribution |

Dropship orders ship directly to your customer. You provide the destination address and can optionally set a minimal shipping level and preferred packaging.

Combined orders are consolidated with other orders and shipped to your business address. No address or shipping options are needed; they use the combined address configured on your store.

## Quoting Shipping Costs

Before placing a dropship order you can ask which shipping levels it can ship with, and what each costs, with `POST /quote`. Nothing is stored. The request takes only the fields of `POST /orders` that determine shipping, under the same names; leave out the other order fields, such as images, annotations and the custom identifier.

```bash
curl -X POST https://api.printeers.com/v2/quote \
  -H "X-Printeers-Secret-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "quote": {
      "storeReference": "Store_9jmn3dewwvy13rv12k7aannqzy",
      "shippingKind": "dropship",
      "dropshipAddress": {
        "company": "",
        "firstname": "John",
        "lastname": "Doe",
        "streetname": "Koelmalaan",
        "housenumber": "330",
        "housenumberAddition": "",
        "additionalInfo": "",
        "state": "",
        "zipcode": "1432 PV",
        "city": "Aalsmeer",
        "countryCode": "NL",
        "phonenumber": "+31612345678",
        "email": "john@example.com"
      },
      "orderlines": [
        {
          "productReference": "Product_4j8k2m5n7p9q1r3s6t8v0w2x4y",
          "quantity": 1
        }
      ]
    }
  }'
```

```json
{
  "shippingOptions": [
    { "level": "basic", "price": 395 },
    { "level": "tracked", "price": 695 },
    { "level": "express", "price": 1295 }
  ]
}
```

Prices are in EUR cents. The first option is the lowest level the order can ship with: the `dropshipMinimalShippingLevel` you asked for (default `basic`), raised or lowered where the destination, the weight or the value of the order requires it. The higher levels follow as alternatives, and only levels the order can actually ship with are listed: a Packstation, for example, is served by `tracked` only, so the level you asked for is not guaranteed. Pass the level your customer picks as `dropshipMinimalShippingLevel` when you create the order.

Combined orders ship to your business address together with other orders and have no shipping options of their own, so their quote has an empty `shippingOptions` list.

A quote validates the store, the products and the destination country the way order creation does, and returns the same error codes, for example `store_not_writable_via_api`, `product_not_orderable` or `invalid_address_denied_country`. The remaining address checks (phone number, email address, field lengths) happen when the order is created.

## Creating an Order

### Dropship

```bash
curl -X POST https://api.printeers.com/v2/orders \
  -H "X-Printeers-Secret-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order": {
      "storeReference": "Store_9jmn3dewwvy13rv12k7aannqzy",
      "shippingKind": "dropship",
      "customIdentifier": "my-order-123",
      "dropshipAddress": {
        "company": "",
        "firstname": "John",
        "lastname": "Doe",
        "streetname": "Koelmalaan",
        "housenumber": "330",
        "housenumberAddition": "",
        "additionalInfo": "",
        "state": "",
        "zipcode": "1432 PV",
        "city": "Aalsmeer",
        "countryCode": "NL",
        "phonenumber": "+31612345678",
        "email": "john@example.com"
      },
      "dropshipMinimalShippingLevel": "tracked",
      "orderlines": [
        {
          "productReference": "Product_4j8k2m5n7p9q1r3s6t8v0w2x4y",
          "imageReference": "Image_pdcptbw3k48sv03wy338bb8jzm",
          "quantity": 1
        }
      ]
    }
  }'
```

```json
{
  "order": {
    "reference": "Order_5jcavdegg8n9pvgxq887jq0zbc"
  }
}
```

### Combined

```bash
curl -X POST https://api.printeers.com/v2/orders \
  -H "X-Printeers-Secret-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order": {
      "storeReference": "Store_9jmn3dewwvy13rv12k7aannqzy",
      "shippingKind": "combined",
      "orderlines": [
        {
          "productReference": "Product_4j8k2m5n7p9q1r3s6t8v0w2x4y",
          "imageReference": "Image_pdcptbw3k48sv03wy338bb8jzm",
          "quantity": 5
        }
      ]
    }
  }'
```

```json
{
  "order": {
    "reference": "Order_j2rznt7qr6ra78zb0aq2hsp3fd"
  }
}
```

When order creation fails, the response includes an error `code` and a human-readable `message`:

```json
{
  "code": "missing_address",
  "message": "Dropship address is required for dropship orders"
}
```

Common errors include `missing_address` (dropship order without a destination address) and `missing_image_reference` (print product without an uploaded image). For the full list of request fields and error codes, see the [Orders API Reference](https://api-docs.test-printeers.com/reference/v2#tag/orders).

### Annotations

Each orderline accepts up to 10 key-value `annotations`, for example to keep your own line identifiers or variant details with the order:

```json
{
  "productReference": "Product_4j8k2m5n7p9q1r3s6t8v0w2x4y",
  "quantity": 1,
  "annotations": [
    { "key": "shop-line-id", "value": "4711" },
    { "key": "erp-article-number", "value": "ART-20481" }
  ]
}
```

Annotations are stored with the orderline and returned unchanged in the `annotations` array of each orderline in `GET /orders/{orderReference}`. The array is always present and empty for orderlines that were created without annotations.

## Canceling Orderlines

As long as an order is not in production, you can cancel (part of) the quantity of its orderlines with `POST /orders/{orderReference}/cancel`.

The endpoint uses absolute totals: for every orderline you list, `canceledQuantity` is the desired total canceled quantity of that line, exactly as returned by `GET /orders/{orderReference}`. It is not the amount to cancel on top of the current value. Orderlines you do not list keep their current canceled quantity.

A typical flow is to fetch the order first and then submit the new totals:

```bash
curl https://api.printeers.com/v2/orders/Order_5jcavdegg8n9pvgxq887jq0zbc \
  -H "X-Printeers-Secret-Key: $KEY"
```

Response:

```json
{
  "order": {
    …
    "orderlines": [
      { "index": 1, "quantity": 5, "canceledQuantity": 0, "shippedQuantity": 0, … },
      { "index": 2, "quantity": 2, "canceledQuantity": 0, "shippedQuantity": 0, … }
    ]
  }
}
```

To cancel 3 pieces of the first line and the complete second line:

```bash
curl -X POST https://api.printeers.com/v2/orders/Order_5jcavdegg8n9pvgxq887jq0zbc/cancel \
  -H "X-Printeers-Secret-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "orderlines": [
      { "index": 1, "canceledQuantity": 3 },
      { "index": 2, "canceledQuantity": 2 }
    ]
  }'
```

A successful request returns `204 No Content`. Because the request describes the desired end state, sending the same request again also returns `204` without canceling anything further. This makes retries after a timeout safe.

The request is atomic: when any listed orderline fails validation, nothing is canceled.

Canceling cannot be undone. If you send a `canceledQuantity` below the current canceled quantity of a line, the request is rejected with `409 canceled_quantity_below_current`. This usually means the order changed since you last fetched it, for example because our support team canceled quantity on your behalf. Fetch the order again to see the current state.

Other things to know:

- Once a quantity has been shipped it can no longer be canceled. The canceled quantity is limited to `quantity - shippedQuantity` per line (`409 canceled_quantity_exceeds_shippable`).
- Orders that are in production cannot be canceled (`409 already_in_production`).
- Orders in stores backed by an upstream platform such as Shopify or WooCommerce must be canceled in that platform instead (`403 store_not_writable_via_api`).

For the full list of error codes, see the [Orders API Reference](https://api-docs.test-printeers.com/reference/v2#tag/orders).

## Changing the Dropship Address

As long as a dropship order is open, you can replace its destination address with `PUT /orders/{orderReference}/dropship-address`. An order is open until every orderline has been shipped or canceled.

The request carries a complete address, validated with the same rules as `POST /orders`. It replaces the previous address entirely: there is no partial update, so send every field, including the ones that stay the same.

```bash
curl -X PUT https://api.printeers.com/v2/orders/Order_5jcavdegg8n9pvgxq887jq0zbc/dropship-address \
  -H "X-Printeers-Secret-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dropshipAddress": {
      "company": "",
      "firstname": "Jane",
      "lastname": "Doe",
      "streetname": "Laan van Alkmaar",
      "housenumber": "12",
      "housenumberAddition": "A",
      "additionalInfo": "",
      "state": "",
      "zipcode": "1811 AA",
      "city": "Alkmaar",
      "countryCode": "NL",
      "phonenumber": "+31612345678",
      "email": "jane@domain.example"
    }
  }'
```

A successful request returns `204 No Content`. The order now carries a new `dropshipAddressReference`, which `GET /orders/{orderReference}` returns. The previous address is no longer linked to the order, so its reference stops resolving through `GET /addresses/{addressReference}` unless a shipment of the order was already created with it. Fetch the order again to pick up the new reference.

Other things to know:

- A shipment that was already created keeps the address it was created with. Change the address before the order ships; once every orderline has shipped or been canceled the request is rejected (`409 order_completed`).
- Combined orders have no destination address (`409 order_not_dropship`).
- Replacement orders cannot change their address (`409 replacement_address_locked`): they are sent to the destination of the claim they settle.
- Address validation errors use the same `400` codes as order creation, for example `invalid_address_missing_email` or `invalid_address_denied_country`.

For the full list of error codes, see the [Orders API Reference](https://api-docs.test-printeers.com/reference/v2#tag/orders).

## Typical Integration

A typical order creation flow follows these steps:

1. **Browse the catalog**: call `GET /products` to find available products and their references.
2. **Upload images**: send each print image to `POST /images` and store the returned references.
3. **Create the order**: call `POST /orders` with the product references, image references, quantities, and shipping details.
4. **Track the order**: use `GET /orders/{orderReference}` to check order status and retrieve shipment references.
5. **Track shipments**: once shipped, use `GET /shipments/{shipmentReference}` for tracking information. See the [Shipments and Tracking](https://api-docs.test-printeers.com/guides/shipments-and-tracking.md) guide.
