API v1 preview

# Create a quote

`POST /api/v1/quotes`

## Overview

Create a quote for inspected models, sizes, quantities and a US address. Returns manufacturing prices and USPS/UPS shipping options, without tax, valid for 30 minutes. No order, payment or repair job is created. Unordered quotes are kept for 30 days after expiry. A retry with the same Idempotency-Key returns the original quote for 24 hours.

Requires `quotes:write`.

[Shipping quote guide](https://ottomfg.com/docs/quotes.md)

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

Request

```
#!/usr/bin/env bash
# Bash + curl + jq. Key needs quotes:write and quotes:read.
set -euo pipefail
: "${API_BASE_URL:?Set your API origin}"
: "${OTTOMFG_API_KEY:?Set your server-side key}"
: "${MODEL_ID:?Set an inspected model ID}"
: "${LONGEST_DIMENSION_MM:?Set print size from 30 to 200 mm}"
: "${SHIPPING_ADDRESS_FILE:?Set the path to your recipient address JSON file}"
: "${QUOTE_REQUEST_KEY:?Generate once with uuidgen; save and reuse on retry}"
body=$(jq -n --arg model "$MODEL_ID" --argjson size "$LONGEST_DIMENSION_MM" \
  --slurpfile address "$SHIPPING_ADDRESS_FILE" \
  '{items:[{model_id:$model,longest_dimension_mm:$size,quantity:2}],shipping_address:$address[0]}')
quote=$(curl --fail-with-body --silent --show-error --max-time 30 \
  -X POST "${API_BASE_URL%/}/api/v1/quotes" \
  -H "Authorization: Bearer $OTTOMFG_API_KEY" \
  -H "Idempotency-Key: $QUOTE_REQUEST_KEY" \
  -H 'Content-Type: application/json' --data "$body") || { printf '%s\n' "$quote"; exit 1; }
printf '%s\n' "$quote" | jq .
quote_id=$(printf '%s\n' "$quote" | jq -er .id)
curl --fail-with-body --silent --show-error --max-time 30 \
  "${API_BASE_URL%/}/api/v1/quotes/$quote_id" \
  -H "Authorization: Bearer $OTTOMFG_API_KEY"
# Review normalized address, shipping options, status and expiry. No order or charge.
# Same-key replay preserves terms and expiry; status is recalculated.
# Use a new key for a new quote.
```

Use quotes:write/read. Set MODEL_ID, LONGEST_DIMENSION_MM, SHIPPING_ADDRESS_FILE and a saved QUOTE_REQUEST_KEY; see the guide for setup.

[Quote setup and examples](https://ottomfg.com/docs/quotes.md)

## Parameters

`Idempotency-Key` header · optional

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

`items` array · required

1–50 inspected model items, up to 1,000 prints in total. Matching model/size entries merge; combined quantity cannot exceed 500 per model and size.

**items item fields**

`model_id` string · required

Immutable model ID: MD- followed by eight uppercase Crockford Base32 characters. Use this same ID in the website, URLs and API; pass it unchanged.

Min length: 11 · Max length: 11

`quantity` integer · required

Minimum: 1 · Maximum: 500

`longest_dimension_mm` number · required

Longest bounding-box dimension in millimeters, 30–200 inclusive. Fractional values are accepted. Uniformly scale all axes. At model creation this is the default and inspection reference size; each pricing/quote item explicitly selects its own size. Callers convert other units to millimeters.

Minimum: 30 · Maximum: 200

`shipping_address` object · required

US recipient address. Supply a five-digit ZIP code as a string, preserving leading zeroes; ZIP+4 and other lengths are rejected. Text must be nonblank and contain no Unicode category C characters. Postal verification may standardize the address. No client parcel weight, dimensions or origin.

**shipping_address fields**

`name` string · required

Delivery recipient; may differ from the buyer.

Min length: 1 · Max length: 255

`company` string · optional

Optional recipient company.

Min length: 1 · Max length: 255

`line1` string · required

Street address.

Min length: 1 · Max length: 200

`line2` string · optional

Optional apartment, suite, or additional address line.

Min length: 1 · Max length: 200

`city` string · required

Destination city/locality.

Min length: 1 · Max length: 100

`state` string · required

Supported two-letter US state or territory code, or AA/AE/AP military region.

Min length: 1 · Max length: 2

`postal_code` string · required

Exactly five digits, for example 02108. ZIP+4 is not accepted.

Min length: 5 · Max length: 5

`country` string · required

Literal US.

Always: `"US"`

Min length: 1 · Max length: 2

`phone` string · optional

Recipient contact; required where the selected carrier/service requires it.

Min length: 1 · Max length: 50

`email` string (email) · optional

Min length: 1 · Max length: 254

`buyer` object · optional

Optional buyer details, separate from the delivery recipient. The authenticated account owns the quote and order.

**buyer fields**

`name` string · optional

Buyer name.

Min length: 1 · Max length: 255

`company` string · optional

Buyer company.

Min length: 1 · Max length: 255

`email` string (email) · optional

Min length: 1 · Max length: 254

## Responses

Saved quote with manufacturing and shipping totals in USD cents. Tax excluded; no charge is made. Shipping prices in this example are illustrative.

200 response

JSON

```
{
  "id": "quote_6c82bd3e319b4bb099310d54a761879e",
  "created_at": "2026-09-17T18:00:00.000000Z",
  "expires_at": "2026-09-17T18:30:00.000000Z",
  "currency": "usd",
  "items": [
    {
      "model_id": "MD-739YQ549",
      "longest_dimension_mm": 100,
      "quantity": 2,
      "dimensions_mm": {
        "x": 100,
        "y": 50,
        "z": 20
      },
      "unit_price": 2460,
      "subtotal": 4920,
      "repair": {
        "required": false,
        "applied": false,
        "reason": null,
        "status": "not_applied",
        "fee": 0,
        "covered_by_order_id": null
      }
    }
  ],
  "shipping_address": {
    "name": "Test Recipient",
    "line1": "123 EXAMPLE ST",
    "city": "SEATTLE",
    "state": "WA",
    "postal_code": "98101",
    "country": "US"
  },
  "buyer": {
    "company": "Example Store",
    "email": "orders@example.com"
  },
  "estimated_production_business_days": {
    "min": 3,
    "max": 3
  },
  "shipping_options": [
    {
      "id": "shipopt_6c82bd3e319b4bb099310d54a761879e",
      "name": "UPS Ground",
      "totals": {
        "subtotal": 4920,
        "shipping": 1000,
        "total": 5920
      },
      "estimated_transit_business_days": {
        "min": 3,
        "max": 3
      },
      "estimated_ship_date": "2026-09-21",
      "estimated_delivery": {
        "earliest": "2026-09-24",
        "latest": "2026-09-24"
      },
      "carrier": "UPS"
    },
    {
      "id": "shipopt_8c1f25d94e3142f8b9a70635d42c81e0",
      "name": "USPS Priority Mail Express",
      "totals": {
        "subtotal": 4920,
        "shipping": 2000,
        "total": 6920
      },
      "estimated_transit_business_days": {
        "min": 1,
        "max": 1
      },
      "estimated_ship_date": "2026-09-21",
      "estimated_delivery": {
        "earliest": "2026-09-22",
        "latest": "2026-09-22"
      },
      "carrier": "USPS"
    }
  ],
  "pricing_version": "pv_20261001_model_repair_01",
  "estimates_valid_until": "2026-09-17T21:00:00.000000Z",
  "status": "open"
}
```

Returns [the Quote object](https://ottomfg.com/docs/quote-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 | `model_not_found` | No model with this ID exists in the account, or it was deleted. Check the ID. |
| 400 Bad Request | `pricing_unavailable` | The model cannot be priced, usually because processing failed. Read the model's status_reasons; repeating the request does not rerun inspection. |
| 400 Bad Request | `shipping_unavailable` | No shipping service accepts this destination or basket. Check the address, or split an oversized basket. |
| 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_not_ready` | The model is still processing. Wait until its status is ready, then retry. |
| 409 Conflict | `model_repair_estimate_pending` | The repair fee is still being estimated. Wait until the model status is ready, then retry. |
| 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 | `shipping_rates_unavailable` | Shipping rates could not be fetched. Retry with backoff and the same Idempotency-Key. |
| 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-quote
