API v1 preview

# Create an order

`POST /api/v1/orders`

## Overview

Accept an open quote and one shipping option to create an order with fixed terms, status awaiting_payment and expires_at 24 hours after creation. Payment starts separately. An Idempotency-Key is required; replays return the original order.

Requires `orders:write`.

[Order and payment guide](https://ottomfg.com/docs/orders.md)

[Errors and retries](https://ottomfg.com/docs/errors.md)

Request

```
#!/usr/bin/env bash
# Bash + curl + jq. Requires orders:write.
set -euo pipefail
: "${API_BASE_URL:?Set API_BASE_URL}"
: "${OTTOMFG_API_KEY:?Set OTTOMFG_API_KEY}"
: "${ORDER_REQUEST_KEY:?Set ORDER_REQUEST_KEY once; save and reuse on retry}"
: "${QUOTE_ID:?Set QUOTE_ID}"
: "${SHIPPING_OPTION_ID:?Set SHIPPING_OPTION_ID}"
body=$(jq -n --arg quote_id "$QUOTE_ID" --arg shipping_option_id "$SHIPPING_OPTION_ID" '{quote_id:$quote_id, shipping_option_id:$shipping_option_id}')
result=$(curl --fail-with-body --silent --show-error --max-time 60 -X POST "${API_BASE_URL%/}/api/v1/orders" \
  -H "Authorization: Bearer $OTTOMFG_API_KEY" \
  -H "Idempotency-Key: $ORDER_REQUEST_KEY" \
  -H "Content-Type: application/json" --data "$body") || { printf '%s\n' "$result"; exit 1; }
printf '%s\n' "$result" | jq .
```

Set API_BASE_URL and OTTOMFG_API_KEY. Save request keys before creating an order or starting payment; reuse the same key and body on retry. See the guide for permissions and recovery.

[Order setup and recovery](https://ottomfg.com/docs/orders.md)

## Parameters

`Idempotency-Key` header · required

Account, method and path scoped retry key, retained for 24 hours from first use. Same body replays the original successful status and body. A changed body returns 409 idempotency_key_reused; an in-flight request returns 409 idempotency_request_in_progress with Retry-After. Retry transient failures with the same key and body.

string

## Request body

application/json

`quote_id` string · required

Server-generated public quote ID: quote_ plus 32 lowercase hexadecimal digits representing an opaque random value. Immutable and opaque; not a storage path or content hash.

Min length: 38 · Max length: 38

`shipping_option_id` string · required

Opaque service-generated ID for an option within a particular quote. Not a carrier rate ID. Cannot be transferred to another quote.

`external_id` string · optional

Optional customer reference, without uniqueness or deduplication guarantees.

Max length: 255

`purchase_order_number` string · optional

Optional buyer purchase-order reference; not payment authorization.

Max length: 255

`metadata` object · optional

Optional customer key/value strings. Up to 50 keys, each 1–40 characters; values up to 500 characters. Unicode category C characters (including control and format characters) are rejected in keys and values. No secrets. Not interpreted for pricing, permissions or deduplication.

## Responses

Order with complete inline event history and one optional shipment.

200 response

```
{
  "id": "OM-XMF1NT2E",
  "created_at": "2026-09-18T18:00:00.000000Z",
  "quote_id": "quote_6c82bd3e319b4bb099310d54a761879e",
  "currency": "usd",
  "items": [
    {
      "model_id": "MD-739YQ549",
      "quantity": 2,
      "unit_price": 3000,
      "subtotal": 6000,
      "repair": {
        "required": false,
        "applied": false,
        "reason": null,
        "status": "not_applied",
        "fee": 0,
        "covered_by_order_id": null
      },
      "longest_dimension_mm": 100,
      "dimensions_mm": {
        "x": 50,
        "y": 100,
        "z": 40
      }
    }
  ],
  "shipping_address": {
    "name": "Alex Example",
    "company": "Example Retail",
    "line1": "123 Example Street",
    "city": "Seattle",
    "state": "WA",
    "postal_code": "98101",
    "country": "US"
  },
  "buyer": {
    "company": "Example Store",
    "email": "orders@example.com"
  },
  "shipping_option": {
    "id": "shipopt_6c82bd3e319b4bb099310d54a761879e",
    "name": "Standard",
    "totals": {
      "subtotal": 6000,
      "shipping": 1000,
      "total": 7000
    },
    "estimated_transit_business_days": {
      "min": 3,
      "max": 5
    },
    "estimated_delivery": null
  },
  "external_id": "checkout-123",
  "purchase_order_number": "PO-2026-0042",
  "metadata": {
    "project": "launch"
  },
  "updated_at": "2026-09-18T18:00:00.000000Z",
  "status": "awaiting_payment",
  "events": [
    {
      "id": "evt_00000000000000000000000000000001",
      "sequence": 1,
      "type": "order.created",
      "at": "2026-09-18T18:00:00.000000Z",
      "reason": null,
      "data": {
        "status": "awaiting_payment"
      }
    }
  ],
  "estimated_ship_date": null,
  "estimated_delivery": null,
  "hold": null,
  "payment": {
    "status": "unpaid",
    "amount": 7000,
    "currency": "usd",
    "method": null,
    "paid_at": null,
    "receipt_url": null
  },
  "shipment": null,
  "pricing_version": "pv_2026_09_a",
  "preparation": {
    "status": "completed",
    "items": [
      {
        "model_id": "MD-739YQ549",
        "status": "completed",
        "longest_dimension_mm": 100
      }
    ]
  },
  "expires_at": "2026-09-19T18:00:00.000000Z"
}
```

Returns [the Order object](https://ottomfg.com/docs/order-object.md).

**Response headers**

`X-Request-ID`

Request identifier for support. Send X-Request-ID with 8–64 ASCII letters, digits or hyphens to reuse that value; otherwise the server generates a UUID4 hex value. Include the returned X-Request-ID in support requests. Do not put secrets or account IDs in this value.

`Idempotent-Replayed`

Present when the original successful response is replayed.

## Errors

| HTTP | Code | What to do |
| --- | --- | --- |
| 400 Bad Request | `invalid_argument` | A field, query parameter or header is invalid. Fix the field named in details, then send the request again. |
| 400 Bad Request | `quote_not_found` | No quote with this ID exists in the account, or it is past retention. Check the ID. |
| 401 Unauthorized | `unauthenticated` | The API key is missing, invalid or revoked. Send a valid business API key in Authorization: Bearer. |
| 403 Forbidden | `permission_denied` | The key lacks the endpoint's scope, or payments lack spending consent. Check the scope listed on the endpoint. |
| 409 Conflict | `idempotency_key_reused` | This Idempotency-Key was already used with a different body. Retry the original body, or use a new key for a new operation. |
| 409 Conflict | `idempotency_request_in_progress` | A request with this Idempotency-Key is still running. Wait Retry-After seconds, then retry the same key and body. |
| 409 Conflict | `model_repair_estimate_pending` | The repair fee is still being estimated. Wait until the model status is ready, then retry. |
| 409 Conflict | `quote_accepted` | An order already uses this quote. Find it with List orders and quote_id; do not create another. |
| 409 Conflict | `quote_expired` | The quote is older than 30 minutes. Create a new quote, then order it with a new Idempotency-Key. |
| 409 Conflict | `quote_unavailable` | A model, its price or its repair changed after quoting. Create and review a new quote. |
| 409 Conflict | `repair_payment_pending` | Another order has an unpaid repair fee for this file revision. Pay or cancel that order, then quote again. |
| 413 Content Too Large | `request_too_large` | The JSON body is over this endpoint's size limit. Send a smaller body. |
| 415 Unsupported Media Type | `unsupported_media_type` | The body is not JSON. Send Content-Type: application/json. |
| 429 Too Many Requests | `resource_exhausted` | A rate or account limit was reached. Wait at least Retry-After seconds, then retry. |
| 500 Internal Server Error | `internal` | The server failed unexpectedly. Retry with backoff; contact support with X-Request-ID if it persists. |
| 503 Service Unavailable | `unavailable` | A required service is briefly unavailable. Retry with backoff, reusing the same Idempotency-Key. |

[Idempotent requests](https://ottomfg.com/docs/idempotency.md)

API v1 preview

Source: https://ottomfg.com/docs/create-order
