> ## Documentation Index
> Fetch the complete documentation index at: https://docs.snappy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Place Orders

> Place direct-fulfillment orders via POST /v3/orders. Covers address validation, idempotency, bulk placement, and the Swag variant.

The Place Orders pattern creates direct-fulfillment orders through the V3 orders API. A single `POST /v3/orders` call creates an order that Snappy fulfills and ships to the recipient — no two-step gift/claim flow required.

## Prerequisites

* API key with `orders:create` scope
* A configured billing method under the ordering account
* `accountId`, `billingMethodId`, and `variantId` ready

## Pre-flight: validate the address

Before placing an order, run the recipient's address through Snappy's validation endpoint. This catches undeliverable addresses before the order is created.

```text theme={null}
POST /v3/orders/addresses/validate
```

```javascript JavaScript theme={null}
async function validateAddress(address) {
  const res = await fetch(`${SNAPPY}/v3/orders/addresses/validate`, {
    method: "POST",
    headers,
    body: JSON.stringify({ address }),
  });
  const result = await res.json();
  if (!result.valid) throw new Error(`Invalid address: ${result.message}`);
  return result.normalizedAddress;
}
```

For address autocomplete as the user types, use `GET /v3/orders/addresses/autocomplete?filter[address]=...&filter[country]=US`.

## Place the order

```text theme={null}
POST /v3/orders
```

The account is scoped via header, not body:

```javascript JavaScript theme={null}
async function placeOrder({ variantId, recipient, shippingAddress, idempotencyKey, tags, metadata }) {
  const res = await fetch(`${SNAPPY}/v3/orders`, {
    method: "POST",
    headers: {
      ...headers,
      "Snappy-Account-Id": process.env.SNAPPY_ACCOUNT_ID,
    },
    body: JSON.stringify({
      billingMethodId: process.env.SNAPPY_BILLING_METHOD_ID,
      variantId,
      recipient: {
        firstName: recipient.firstName,
        lastName: recipient.lastName,
        email: recipient.email,
        phone: recipient.phone,           // E.164, required
      },
      shippingAddress: {
        address1: shippingAddress.address1,
        address2: shippingAddress.address2,   // optional
        city: shippingAddress.city,
        provinceCode: shippingAddress.provinceCode,
        postalCode: shippingAddress.postalCode,
        countryCode: shippingAddress.countryCode,
      },
      idempotencyKey,    // top-level, 1–120 chars, stable per order attempt
      tags,              // optional string[]: for your reporting and filtering
      metadata,          // optional object: roundtrips on status-changed webhooks
    }),
  });
  if (!res.ok) throw new Error(`Order failed ${res.status}: ${await res.text()}`);
  const { data } = await res.json();
  return data; // { id, status: "active", trackingLink }
}
```

```python Python theme={null}
def place_order(variant_id, recipient, shipping_address, idempotency_key, tags=None, metadata=None):
    payload = {
        "billingMethodId": os.environ["SNAPPY_BILLING_METHOD_ID"],
        "variantId": variant_id,
        "recipient": {
            "firstName": recipient["first_name"],
            "lastName": recipient["last_name"],
            "email": recipient["email"],
            "phone": recipient["phone"],
        },
        "shippingAddress": {
            "address1": shipping_address["address1"],
            "address2": shipping_address.get("address2"),
            "city": shipping_address["city"],
            "provinceCode": shipping_address["province_code"],
            "postalCode": shipping_address["postal_code"],
            "countryCode": shipping_address["country_code"],
        },
        "idempotencyKey": idempotency_key,
    }
    if tags:     payload["tags"] = tags
    if metadata: payload["metadata"] = metadata

    order_headers = {**headers, "Snappy-Account-Id": os.environ["SNAPPY_ACCOUNT_ID"]}
    res = requests.post(f"{SNAPPY}/v3/orders", headers=order_headers, json=payload)
    res.raise_for_status()
    return res.json()["data"]
```

## Idempotency

The `idempotencyKey` (top-level field, 1–120 chars) makes retries safe. Sending the same key twice returns the original order — no duplicate, no double charge.

**Derive from stable identifiers:**

```javascript JavaScript theme={null}
// Good: stable per user per redemption event
const key = `user-${userId}-redemption-${redemptionId}`;

// Bad: changes on retry
const key = crypto.randomUUID();
```

Reuse the same key on all retry attempts for the same order.

## Cancel an order

```text theme={null}
DELETE /v3/orders/{orderId}
```

Orders can only be canceled before fulfillment begins. Check `status` on the order before attempting cancellation.

## Bulk placement

For placing multiple orders (e.g., a batch send), use bounded concurrency to stay within rate limits:

```javascript JavaScript theme={null}
async function placeOrdersBatch(orderRequests, { concurrency = 5 } = {}) {
  const results = [];
  for (let i = 0; i < orderRequests.length; i += concurrency) {
    const batch = orderRequests.slice(i, i + concurrency);
    const batchResults = await Promise.all(batch.map(req => placeOrder(req)));
    results.push(...batchResults);
  }
  return results;
}
```

## Swag orders

Swag orders use the same `POST /v3/orders` endpoint but require:

* `Snappy-Account-Id` header (same as standard orders)
* A `variantId` from a swag product (fetched with `filter[catalog]=swag`)

No additional fields are needed — the swag context is inferred from the variant.

## Failure table

| HTTP  | Meaning                                                | Action                                                  |
| :---- | :----------------------------------------------------- | :------------------------------------------------------ |
| `422` | Insufficient funds                                     | Alert your ops team; this is a billing issue            |
| `422` | Variant not available in country                       | Offer alternatives via recommendations endpoint         |
| `404` | Variant, billing method, or account not found          | Config bug on your side — log and fix                   |
| `400` | Invalid body (bad phone, malformed address)            | Validate at form entry; catch before reaching this call |
| `409` | Duplicate order (same idempotency key, different body) | Check your key derivation — keys must be stable         |

## Related

* [Real-Time Catalog Access](/patterns/real-time-catalog-access) — fetching the variant ID before placing the order
* [Track Order Fulfillment](/patterns/track-order-fulfillment) — subscribe to webhooks after placing
* [Build a Rewards Experience](/pages/marketplace) — end-to-end recipe using this pattern
